asyncronise executor (#115)

* Intital async of executor

The execture now sends websocket message instead of calling functions
directly from the engine, When it does so it holds onto the id.
The engine is still returning geo/polys directly but I'm working make it
so that the UI doesn't need to know about that, so that we can switch
over the streaming ui.

Things left to do:
- it is still making both direct function calls and websockets, and the former should be removed.
- It does highlighting of segments and sourceRanges not through websockets and that needs to be fixed.
- Tests have not been adjusted for these changes.
- Selecting the head of a segment is not working correctly again yet.

* Rough engine prep changes (#135)

* rough changes for engine prep

* mouse movements working again

* connect to engine for startsketch, line, close and extrude
This commit is contained in:
Kurt Hutten
2023-06-22 16:43:33 +10:00
committed by GitHub
parent dd3117cf03
commit 2d3c73d46a
39 changed files with 1798 additions and 2443 deletions

View File

@ -15,6 +15,7 @@ jobs:
- run: yarn install
- run: yarn build:wasm:ci
- run: yarn simpleserver:ci
- run: npm pkg delete type
- run: yarn test:nowatch
- run: yarn test:cov
- run: yarn test:rust

View File

@ -5,8 +5,6 @@
"dependencies": {
"@codemirror/lang-javascript": "^6.1.1",
"@headlessui/react": "^1.7.13",
"@react-three/drei": "^9.42.0",
"@react-three/fiber": "^8.9.1",
"@tauri-apps/api": "^1.3.0",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
@ -17,6 +15,7 @@
"@types/react-dom": "^18.0.0",
"@uiw/react-codemirror": "^4.15.1",
"allotment": "^1.17.0",
"crypto-js": "^4.1.1",
"http-server": "^14.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
@ -25,8 +24,8 @@
"react-scripts": "5.0.1",
"sketch-helpers": "^0.0.2",
"swr": "^2.0.4",
"three": "^0.146.0",
"toml": "^3.0.0",
"ts-node": "^10.9.1",
"typescript": "^4.4.2",
"util": "^0.12.5",
"uuid": "^9.0.0",
@ -41,13 +40,13 @@
"build:both": "react-scripts build",
"build:both:local": "yarn build:wasm && react-scripts build",
"test": "react-scripts test",
"test:nowatch": "react-scripts test --watchAll=false",
"test:nowatch": "react-scripts test --watchAll=false --forceExit",
"test:rust": "(cd src/wasm-lib && cargo test && cargo clippy)",
"test:cov": "react-scripts test --watchAll=false --coverage=true",
"test:cov": "react-scripts test --watchAll=false --coverage=true --forceExit",
"simpleserver:ci": "http-server ./public --cors -p 3000 &",
"simpleserver": "http-server ./public --cors -p 3000",
"eject": "react-scripts eject",
"fmt": "prettier --write ./src/**/*.{ts,tsx,js}",
"fmt": "prettier --write ./src/**/*.{ts,tsx,js} && prettier --write ./src/**/**/*.{ts,tsx,js}",
"remove-importmeta": "sed -i '' 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\"",
"remove-importmeta:ci": "sed -i 's/import.meta.url//g' \"./src/wasm-lib/pkg/wasm_lib.js\"",
"add-missing-import": "echo \"import util from 'util'; if (typeof window !== 'undefined' && !window.TextEncoder) { window.TextEncoder = util.TextEncoder; window.TextDecoder = util.TextDecoder}\" | cat - ./src/wasm-lib/pkg/wasm_lib.js > temp && mv temp ./src/wasm-lib/pkg/wasm_lib.js",
@ -92,8 +91,7 @@
"devDependencies": {
"@tauri-apps/cli": "^1.3.1",
"@types/crypto-js": "^4.1.1",
"@types/three": "^0.146.0",
"@types/uuid": "^9.0.2",
"@types/uuid": "^9.0.1",
"autoprefixer": "^10.4.13",
"postcss": "^8.4.19",
"prettier": "^2.8.0",

View File

@ -1,6 +1,5 @@
import React from 'react'
import { render, screen } from '@testing-library/react'
import App from './App'
import { App } from './App'
let listener: ((rect: any) => void) | undefined = undefined
;(global as any).ResizeObserver = class ResizeObserver {

View File

@ -1,10 +1,8 @@
import { useRef, useState, useEffect } from 'react'
import { Canvas } from '@react-three/fiber'
import { useRef, useEffect, useMemo } from 'react'
import { Allotment } from 'allotment'
import { OrbitControls, OrthographicCamera } from '@react-three/drei'
import { asyncLexer } from './lang/tokeniser'
import { abstractSyntaxTree } from './lang/abstractSyntaxTree'
import { executor, ExtrudeGroup, SketchGroup } from './lang/executor'
import { _executor, ExtrudeGroup, SketchGroup } from './lang/executor'
import CodeMirror from '@uiw/react-codemirror'
import { javascript } from '@codemirror/lang-javascript'
import { ViewUpdate } from '@codemirror/view'
@ -12,29 +10,25 @@ import {
lineHighlightField,
addLineHighlight,
} from './editor/highlightextension'
import { useStore } from './useStore'
import { Selections, useStore } from './useStore'
import { Toolbar } from './Toolbar'
import { BasePlanes } from './components/BasePlanes'
import { SketchPlane } from './components/SketchPlane'
import { Logs } from './components/Logs'
import { AxisIndicator } from './components/AxisIndicator'
import { RenderViewerArtifacts } from './components/RenderViewerArtifacts'
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'
import { EngineCommandManager } from './lang/std/engineConnection'
import { isOverlap } from './lib/utils'
const OrrthographicCamera = OrthographicCamera as any
function App() {
export function App() {
const cam = useRef()
useHotKeyListener()
const {
editorView,
setEditorView,
setSelectionRanges,
selectionRanges: selectionRange,
selectionRanges,
guiMode,
lastGuiMode,
addLog,
@ -46,6 +40,13 @@ function App() {
setProgramMemory,
resetLogs,
selectionRangeTypeMap,
setArtifactMap,
engineCommandManager: _engineCommandManager,
setEngineCommandManager,
setHighlightRange,
setCursor2,
sourceRangeMap,
setMediaStream,
} = useStore((s) => ({
editorView: s.editorView,
setEditorView: s.setEditorView,
@ -63,6 +64,15 @@ function App() {
setProgramMemory: s.setProgramMemory,
resetLogs: s.resetLogs,
selectionRangeTypeMap: s.selectionRangeTypeMap,
setArtifactMap: s.setArtifactNSourceRangeMaps,
engineCommandManager: s.engineCommandManager,
setEngineCommandManager: s.setEngineCommandManager,
setHighlightRange: s.setHighlightRange,
isShiftDown: s.isShiftDown,
setCursor: s.setCursor,
setCursor2: s.setCursor2,
sourceRangeMap: s.sourceRangeMap,
setMediaStream: s.setMediaStream
}))
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (value: string, viewUpdate: ViewUpdate) => {
@ -78,18 +88,17 @@ function App() {
const ranges = viewUpdate.state.selection.ranges
const isChange =
ranges.length !== selectionRange.codeBasedSelections.length ||
ranges.length !== selectionRanges.codeBasedSelections.length ||
ranges.some(({ from, to }, i) => {
return (
from !== selectionRange.codeBasedSelections[i].range[0] ||
to !== selectionRange.codeBasedSelections[i].range[1]
from !== selectionRanges.codeBasedSelections[i].range[0] ||
to !== selectionRanges.codeBasedSelections[i].range[1]
)
})
if (!isChange) return
setSelectionRanges({
otherSelections: [],
codeBasedSelections: ranges.map(({ from, to }, i) => {
const codeBasedSelections: Selections['codeBasedSelections'] = ranges.map(
({ from, to }) => {
if (selectionRangeTypeMap[to]) {
return {
type: selectionRangeTypeMap[to],
@ -100,15 +109,39 @@ function App() {
type: 'default',
range: [from, to],
}
}),
}
)
const idBasedSelections = codeBasedSelections
.map(({ type, range }) => {
const hasOverlap = Object.entries(sourceRangeMap).filter(
([_, sourceRange]) => {
return isOverlap(sourceRange, range)
}
)
if (hasOverlap.length) {
return {
type,
id: hasOverlap[0][0],
}
}
})
.filter(Boolean) as any
_engineCommandManager?.cusorsSelected({
otherSelections: [],
idBasedSelections,
})
setSelectionRanges({
otherSelections: [],
codeBasedSelections,
})
}
const [geoArray, setGeoArray] = useState<(ExtrudeGroup | SketchGroup)[]>([])
const engineCommandManager = useMemo(() => new EngineCommandManager(setMediaStream), [])
useEffect(() => {
const asyncWrap = async () => {
try {
if (!code) {
setGeoArray([])
setAst(null)
return
}
@ -116,60 +149,86 @@ function App() {
const _ast = abstractSyntaxTree(tokens)
setAst(_ast)
resetLogs()
const programMemory = executor(_ast, {
root: {
log: {
type: 'userVal',
value: (a: any) => {
addLog(a)
},
__meta: [
{
pathToNode: [],
sourceRange: [0, 0],
if (_engineCommandManager) {
_engineCommandManager.endSession()
}
engineCommandManager.startNewSession()
setEngineCommandManager(engineCommandManager)
_executor(
_ast,
{
root: {
log: {
type: 'userVal',
value: (a: any) => {
addLog(a)
},
],
},
_0: {
type: 'userVal',
value: 0,
__meta: [],
},
_90: {
type: 'userVal',
value: 90,
__meta: [],
},
_180: {
type: 'userVal',
value: 180,
__meta: [],
},
_270: {
type: 'userVal',
value: 270,
__meta: [],
__meta: [
{
pathToNode: [],
sourceRange: [0, 0],
},
],
},
_0: {
type: 'userVal',
value: 0,
__meta: [],
},
_90: {
type: 'userVal',
value: 90,
__meta: [],
},
_180: {
type: 'userVal',
value: 180,
__meta: [],
},
_270: {
type: 'userVal',
value: 270,
__meta: [],
},
},
pendingMemory: {},
},
_sketch: [],
})
setProgramMemory(programMemory)
const geos = programMemory?.return
?.map(({ name }: { name: string }) => {
const artifact = programMemory?.root?.[name]
if (
artifact.type === 'extrudeGroup' ||
artifact.type === 'sketchGroup'
) {
return artifact
}
return null
})
.filter((a) => a) as (ExtrudeGroup | SketchGroup)[]
engineCommandManager,
{ bodyType: 'root' },
[]
).then(async (programMemory) => {
const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands()
setGeoArray(geos)
console.log(programMemory)
setError()
setArtifactMap({ artifactMap, sourceRangeMap })
engineCommandManager.onHover((id) => {
if (!id) {
setHighlightRange([0, 0])
} else {
const sourceRange = sourceRangeMap[id]
setHighlightRange(sourceRange)
}
})
engineCommandManager.onClick(({ id, type }) => {
setCursor2({ range: sourceRangeMap[id], type })
})
setProgramMemory(programMemory)
const geos = programMemory?.return
?.map(({ name }: { name: string }) => {
const artifact = programMemory?.root?.[name]
if (
artifact.type === 'extrudeGroup' ||
artifact.type === 'sketchGroup'
) {
return artifact
}
return null
})
.filter((a) => a) as (ExtrudeGroup | SketchGroup)[]
// console.log(programMemory)
setError()
})
} catch (e: any) {
setError('problem')
console.log(e)
@ -209,52 +268,9 @@ function App() {
<MemoryPanel />
<Logs />
</Allotment>
<Allotment vertical defaultSizes={[1, 400]} minSize={20}>
<div className="h-full">
<PanelHeader title="Drafting Board" />
<Allotment vertical defaultSizes={[40, 400]} minSize={20}>
<div>
<Toolbar />
<div className="border h-full border-gray-300 relative">
<div className="absolute inset-0">
<Canvas>
<OrbitControls
enableDamping={false}
enablePan
enableRotate={
!(
guiMode.mode === 'canEditSketch' ||
guiMode.mode === 'sketch'
)
}
enableZoom
reverseOrbit={false}
/>
<OrrthographicCamera
ref={cam}
makeDefault
position={[0, 0, 1000]}
zoom={100}
rotation={[0, 0, 0]}
far={2000}
/>
<ambientLight />
<pointLight position={[10, 10, 10]} />
<RenderViewerArtifacts artifacts={geoArray} />
<BasePlanes />
<SketchPlane />
<AxisIndicator />
</Canvas>
</div>
{errorState.isError && (
<div className="absolute inset-0 bg-gray-700/20">
<pre>
{'last first: \n\n' +
JSON.stringify(lastGuiMode, null, 2) +
'\n\n' +
JSON.stringify(guiMode)}
</pre>
</div>
)}
</div>
</div>
<Stream />
</Allotment>
@ -262,5 +278,3 @@ function App() {
</div>
)
}
export default App

View File

@ -1,7 +1,7 @@
import useSWR from 'swr'
import fetcher from './lib/fetcher'
import withBaseUrl from './lib/withBaseURL'
import App from './App'
import { App } from './App'
export const Auth = () => {
const { data: user } = useSWR(withBaseUrl('/user'), fetcher) as any

View File

@ -96,11 +96,14 @@ export function useCalc({
newVariableInsertIndex: number
setNewVariableName: (a: string) => void
} {
const { ast, programMemory, selectionRange } = useStore((s) => ({
ast: s.ast,
programMemory: s.programMemory,
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
}))
const { ast, programMemory, selectionRange, engineCommandManager } = useStore(
(s) => ({
ast: s.ast,
programMemory: s.programMemory,
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
engineCommandManager: s.engineCommandManager,
})
)
const inputRef = useRef<HTMLInputElement>(null)
const [availableVarInfo, setAvailableVarInfo] = useState<
ReturnType<typeof findAllPreviousVariables>
@ -141,6 +144,7 @@ export function useCalc({
}, [ast, programMemory, selectionRange])
useEffect(() => {
if (!engineCommandManager) return
try {
const code = `const __result__ = ${value}\nshow(__result__)`
const ast = abstractSyntaxTree(lexer(code))
@ -148,18 +152,19 @@ export function useCalc({
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
})
const programMemory = executor(ast, _programMem)
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&
a.declarations?.[0]?.id?.name === '__result__'
)
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init
const result = programMemory?.root?.__result__?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
executor(ast, _programMem, engineCommandManager).then((programMemory) => {
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&
a.declarations?.[0]?.id?.name === '__result__'
)
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init
const result = programMemory?.root?.__result__?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
})
} catch (e) {
setCalcResult('NAN')
setValueNode(null)

View File

@ -1,16 +0,0 @@
export const AxisIndicator = () => (
<>
<mesh position={[0.5, 0, 0]}>
<boxBufferGeometry args={[1, 0.05, 0.05]} />
<meshStandardMaterial color="red" />
</mesh>
<mesh position={[0, 0.5, 0]}>
<boxBufferGeometry args={[0.05, 1, 0.05]} />
<meshStandardMaterial color="blue" />
</mesh>
<mesh position={[0, 0, 0.5]}>
<boxBufferGeometry args={[0.05, 0.05, 1]} />
<meshStandardMaterial color="green" />
</mesh>
</>
)

View File

@ -1,121 +0,0 @@
import { useState } from 'react'
import { DoubleSide, Vector3 } from 'three'
import { useStore } from '../useStore'
import { Intersection } from '@react-three/fiber'
import { Text } from '@react-three/drei'
import { addSketchTo } from '../lang/modifyAst'
import { Program } from '../lang/abstractSyntaxTree'
import { Quaternion } from 'three'
const opacity = 0.1
export const BasePlanes = () => {
const [axisIndex, setAxisIndex] = useState<null | number>(null)
const { setGuiMode, guiMode, ast, updateAst } = useStore(
({ guiMode, setGuiMode, ast, updateAst }) => ({
guiMode,
setGuiMode,
ast,
updateAst,
})
)
const onPointerEvent = ({
intersections,
}: {
intersections: Intersection[]
}) => {
if (!intersections.length) {
setAxisIndex(null)
return
}
let closestIntersection = intersections[0]
intersections.forEach((intersection) => {
if (intersection.distance < closestIntersection.distance)
closestIntersection = intersection
})
const smallestIndex = Number(closestIntersection.eventObject.name)
setAxisIndex(smallestIndex)
}
const onClick = () => {
if (guiMode.mode !== 'sketch') {
return null
}
if (guiMode.sketchMode !== 'selectFace') {
return null
}
let _ast: Program = ast
? ast
: {
type: 'Program',
start: 0,
end: 0,
body: [],
nonCodeMeta: {},
}
const axis = axisIndex === 0 ? 'xy' : axisIndex === 1 ? 'xz' : 'yz'
const quaternion = new Quaternion()
if (axisIndex === 1) {
quaternion.setFromAxisAngle(new Vector3(1, 0, 0), Math.PI / 2)
} else if (axisIndex === 2) {
quaternion.setFromAxisAngle(new Vector3(0, 1, 0), Math.PI / 2)
}
const { modifiedAst, id, pathToNode } = addSketchTo(_ast, axis)
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
rotation: quaternion.toArray() as [number, number, number, number],
position: [0, 0, 0],
pathToNode,
})
updateAst(modifiedAst)
}
if (guiMode.mode !== 'sketch') {
return null
}
if (guiMode.sketchMode !== 'selectFace') {
return null
}
return (
<>
{Array.from({ length: 3 }).map((_, index) => (
<mesh
key={index}
rotation-x={index === 1 ? -Math.PI / 2 : 0}
rotation-y={index === 2 ? -Math.PI / 2 : 0}
onPointerMove={onPointerEvent}
onPointerOut={onPointerEvent}
onClick={onClick}
name={`${index}`}
>
<planeGeometry args={[5, 5]} />
<meshStandardMaterial
color="blue"
side={DoubleSide}
transparent
opacity={opacity + (axisIndex === index ? 0.3 : 0)}
/>
<Text
fontSize={1}
color="#555"
position={[1, 1, 0.01]}
font={'/roboto.woff'}
>
{index === 0 ? 'xy' : index === 1 ? 'xz' : 'yz'}
</Text>
<Text
fontSize={1}
color="#555"
position={[1, 1, -0.01]}
font={'/roboto.woff'}
>
{index === 0 ? 'xy' : index === 1 ? 'xz' : 'yz'}
</Text>
</mesh>
))}
</>
)
}

View File

@ -1,13 +1,13 @@
import { processMemory } from './MemoryPanel'
import { lexer } from '../lang/tokeniser'
import { abstractSyntaxTree } from '../lang/abstractSyntaxTree'
import { executor } from '../lang/executor'
import { executor } from '../lib/testHelpers'
import { initPromise } from '../lang/rust'
beforeAll(() => initPromise)
describe('processMemory', () => {
it('should grab the values and remove and geo data', () => {
it('should grab the values and remove and geo data', async () => {
const code = `
const myVar = 5
const myFn = (a) => {
@ -28,7 +28,7 @@ describe('processMemory', () => {
show(theExtrude, theSketch)`
const tokens = lexer(code)
const ast = abstractSyntaxTree(tokens)
const programMemory = executor(ast, {
const programMemory = await executor(ast, {
root: {
log: {
type: 'userVal',
@ -38,7 +38,7 @@ describe('processMemory', () => {
__meta: [],
},
},
_sketch: [],
pendingMemory: {},
})
const output = processMemory(programMemory)
expect(output.myVar).toEqual(5)

View File

@ -1,646 +0,0 @@
import { useRef, useState, useEffect, useMemo } from 'react'
import {
CallExpression,
ArrayExpression,
PipeExpression,
} from '../lang/abstractSyntaxTree'
import {
getNodePathFromSourceRange,
getNodeFromPath,
getNodeFromPathCurry,
} from '../lang/queryAst'
import { changeSketchArguments } from '../lang/std/sketch'
import {
ExtrudeGroup,
ExtrudeSurface,
SketchGroup,
Path,
Rotation,
Position,
PathToNode,
SourceRange,
} from '../lang/executor'
import { BufferGeometry } from 'three'
import { useStore } from '../useStore'
import { isOverlap, roundOff } from '../lib/utils'
import { Vector3, DoubleSide, Quaternion } from 'three'
import { useSetCursor } from '../hooks/useSetCursor'
import { getConstraintLevelFromSourceRange } from '../lang/std/sketchcombos'
import { createCallExpression, createPipeSubstitution } from '../lang/modifyAst'
function LineEnd({
geo,
sourceRange,
editorCursor,
rotation,
position,
from,
}: {
geo: BufferGeometry
sourceRange: [number, number]
editorCursor: boolean
rotation: Rotation
position: Position
from: [number, number]
}) {
const ref = useRef<BufferGeometry | undefined>() as any
const detectionPlaneRef = useRef<BufferGeometry | undefined>() as any
const lastPointerRef = useRef<Vector3>(new Vector3())
const point2DRef = useRef<Vector3>(new Vector3())
const [hovered, setHover] = useState(false)
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,
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
programMemory: s.programMemory,
}))
const { originalXY } = useMemo(() => {
if (ast) {
const thePath = getNodePathFromSourceRange(ast, sourceRange)
const { node: callExpression } = getNodeFromPath<CallExpression>(
ast,
thePath
)
const [xArg, yArg] =
guiMode.mode === 'sketch'
? callExpression?.arguments || []
: (callExpression?.arguments?.[0] as ArrayExpression)?.elements || []
const x = xArg?.type === 'Literal' ? xArg.value : -1
const y = yArg?.type === 'Literal' ? yArg.value : -1
return {
originalXY: [x, y],
}
}
return {
originalXY: [-1, -1],
}
}, [ast])
useEffect(() => {
const handleMouseUp = () => {
if (isMouseDown && ast) {
const current2d = point2DRef.current.clone()
const inverseQuaternion = new Quaternion()
if (
guiMode.mode === 'canEditSketch' ||
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
) {
inverseQuaternion.set(...guiMode.rotation)
inverseQuaternion.invert()
}
current2d.sub(
new Vector3(...position).applyQuaternion(inverseQuaternion)
)
let [x, y] = [roundOff(current2d.x, 2), roundOff(current2d.y, 2)]
let theNewPoints: [number, number] = [x, y]
const { modifiedAst } = changeSketchArguments(
ast,
programMemory,
sourceRange,
theNewPoints,
guiMode,
from
)
if (!(current2d.x === 0 && current2d.y === 0 && current2d.z === 0))
updateAst(modifiedAst)
ref.current.position.set(...position)
}
setIsMouseDown(false)
}
window.addEventListener('mouseup', handleMouseUp)
return () => {
window.removeEventListener('mouseup', handleMouseUp)
}
}, [isMouseDown])
const inEditMode =
guiMode.mode === 'canEditSketch' ||
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
let clickDetectPlaneQuaternion = new Quaternion()
if (inEditMode) {
clickDetectPlaneQuaternion = new Quaternion(...rotation)
}
return (
<>
<mesh
position={position}
quaternion={rotation}
ref={ref}
onPointerOver={(event) => {
inEditMode && setHover(true)
setHighlightRange(sourceRange)
}}
onPointerOut={(event) => {
setHover(false)
setHighlightRange([0, 0])
}}
onPointerDown={() => {
inEditMode && setIsMouseDown(true)
setCursor()
}}
>
<primitive object={geo} scale={hovered ? 2 : 1} />
<meshStandardMaterial
color={hovered ? 'hotpink' : editorCursor ? 'skyblue' : baseColor}
/>
</mesh>
{isMouseDown && (
<mesh
quaternion={clickDetectPlaneQuaternion}
onPointerMove={(a) => {
const point = a.point
const transformedPoint = point.clone()
const inverseQuaternion = new Quaternion()
if (
guiMode.mode === 'canEditSketch' ||
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
) {
inverseQuaternion.set(...guiMode.rotation)
inverseQuaternion.invert()
}
transformedPoint.applyQuaternion(inverseQuaternion)
point2DRef.current.copy(transformedPoint)
if (
lastPointerRef.current.x === 0 &&
lastPointerRef.current.y === 0 &&
lastPointerRef.current.z === 0
) {
lastPointerRef.current.set(point.x, point.y, point.z)
return
}
if (guiMode.mode)
if (ref.current) {
const diff = new Vector3().subVectors(
point.clone().applyQuaternion(inverseQuaternion),
lastPointerRef.current
.clone()
.applyQuaternion(inverseQuaternion)
)
if (originalXY[0] === -1) {
// x arg is not a literal and should be locked
diff.x = 0
}
if (originalXY[1] === -1) {
// y arg is not a literal and should be locked
diff.y = 0
}
ref.current.position.add(
diff.applyQuaternion(inverseQuaternion.invert())
)
lastPointerRef.current.copy(point.clone())
}
}}
>
<planeGeometry args={[50, 50]} ref={detectionPlaneRef} />
<meshStandardMaterial
side={DoubleSide}
color="blue"
transparent
opacity={0}
/>
</mesh>
)}
</>
)
}
export function RenderViewerArtifacts({
artifacts,
}: {
artifacts: (ExtrudeGroup | SketchGroup)[]
}) {
useSetAppModeFromCursorLocation(artifacts)
return (
<>
{artifacts?.map((artifact, i) => (
<RenderViewerArtifact key={i} artifact={artifact} />
))}
</>
)
}
function RenderViewerArtifact({
artifact,
}: {
artifact: ExtrudeGroup | SketchGroup
}) {
// const { selectionRange, guiMode, ast, setGuiMode } = useStore(
// ({ selectionRange, guiMode, ast, setGuiMode }) => ({
// selectionRange,
// guiMode,
// ast,
// setGuiMode,
// })
// )
// const [editorCursor, setEditorCursor] = useState(false)
// useEffect(() => {
// const shouldHighlight = isOverlapping(
// artifact.__meta.slice(-1)[0].sourceRange,
// selectionRange
// )
// setEditorCursor(shouldHighlight)
// }, [selectionRange, artifact.__meta])
if (artifact.type === 'sketchGroup') {
return (
<>
{artifact.start && (
<PathRender
geoInfo={artifact.start}
forceHighlight={false}
rotation={artifact.rotation}
position={artifact.position}
/>
)}
{artifact.value.map((geoInfo, key) => (
<PathRender
geoInfo={geoInfo}
key={key}
forceHighlight={false}
rotation={artifact.rotation}
position={artifact.position}
/>
))}
</>
)
}
if (artifact.type === 'extrudeGroup') {
return (
<>
{artifact.value.map((geoInfo, key) => (
<WallRender
geoInfo={geoInfo}
key={key}
forceHighlight={false}
rotation={artifact.rotation}
position={artifact.position}
/>
))}
</>
)
}
return null
}
function WallRender({
geoInfo,
forceHighlight = false,
rotation,
position,
}: {
geoInfo: ExtrudeSurface
forceHighlight?: boolean
rotation: Rotation
position: Position
}) {
const { setHighlightRange, selectionRanges } = useStore(
({ setHighlightRange, selectionRanges }) => ({
setHighlightRange,
selectionRanges,
})
)
const onClick = useSetCursor(geoInfo.__geoMeta.sourceRange)
// This reference will give us direct access to the mesh
const ref = useRef<BufferGeometry | undefined>() as any
const [hovered, setHover] = useState(false)
const [editorCursor, setEditorCursor] = useState(false)
useEffect(() => {
const shouldHighlight = selectionRanges.codeBasedSelections.some(
({ range }) => isOverlap(geoInfo.__geoMeta.sourceRange, range)
)
setEditorCursor(shouldHighlight)
}, [selectionRanges, geoInfo])
return (
<>
<mesh
quaternion={rotation}
position={position}
ref={ref}
onPointerOver={(event) => {
setHover(true)
setHighlightRange(geoInfo.__geoMeta.sourceRange)
}}
onPointerOut={(event) => {
setHover(false)
setHighlightRange([0, 0])
}}
onClick={onClick}
>
<primitive object={geoInfo.__geoMeta.geo} />
<meshStandardMaterial
side={DoubleSide}
color={
hovered
? 'hotpink'
: forceHighlight || editorCursor
? 'skyblue'
: 'orange'
}
/>
</mesh>
</>
)
}
function PathRender({
geoInfo,
forceHighlight = false,
rotation,
position,
}: {
geoInfo: Path
forceHighlight?: boolean
rotation: Rotation
position: Position
}) {
const { selectionRanges, updateAstAsync, ast, guiMode } = useStore((s) => ({
selectionRanges: s.selectionRanges,
updateAstAsync: s.updateAstAsync,
ast: s.ast,
guiMode: s.guiMode,
}))
const [editorCursor, setEditorCursor] = useState(false)
const [editorLineCursor, setEditorLineCursor] = useState(false)
useEffect(() => {
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 (
<>
{geoInfo.__geoMeta.geos.map((meta, i) => {
if (meta.type === 'line')
return (
<LineRender
key={i}
geo={meta.geo}
sourceRange={geoInfo.__geoMeta.sourceRange}
forceHighlight={editorLineCursor}
rotation={rotation}
position={position}
/>
)
if (meta.type === 'lineEnd')
return (
<LineEnd
key={i}
geo={meta.geo}
from={geoInfo.from}
sourceRange={geoInfo.__geoMeta.sourceRange}
editorCursor={forceHighlight || editorCursor}
rotation={rotation}
position={position}
/>
)
if (meta.type === 'sketchBase')
return (
<LineRender
key={i}
geo={meta.geo}
sourceRange={geoInfo.__geoMeta.sourceRange}
forceHighlight={forceHighlight || editorLineCursor}
rotation={rotation}
position={position}
onClick={() => {
if (
!ast ||
!(guiMode.mode === 'sketch' && guiMode.sketchMode === 'line')
)
return
const path = getNodePathFromSourceRange(
ast,
geoInfo.__geoMeta.sourceRange
)
const getNode = getNodeFromPathCurry(ast, path)
const maybeStartSketchAt =
getNode<CallExpression>('CallExpression')
const pipe = getNode<PipeExpression>('PipeExpression')
if (
maybeStartSketchAt?.node.callee.name === 'startSketchAt' &&
pipe.node &&
pipe.node.body.length > 2
) {
const modifiedAst = JSON.parse(JSON.stringify(ast))
const _pipe = getNodeFromPath<PipeExpression>(
modifiedAst,
path,
'PipeExpression'
)
_pipe.node.body.push(
createCallExpression('close', [createPipeSubstitution()])
)
updateAstAsync(modifiedAst)
}
}}
/>
)
})}
</>
)
}
function LineRender({
geo,
sourceRange,
forceHighlight = false,
rotation,
position,
onClick: _onClick = () => {},
}: {
geo: BufferGeometry
sourceRange: [number, number]
forceHighlight?: boolean
rotation: Rotation
position: Position
onClick?: () => void
}) {
const { setHighlightRange } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
}))
const onClick = useSetCursor(sourceRange)
// This reference will give us direct access to the mesh
const ref = useRef<BufferGeometry | undefined>() as any
const [hovered, setHover] = useState(false)
const baseColor = useConstraintColors(sourceRange)
return (
<>
<mesh
quaternion={rotation}
position={position}
ref={ref}
onPointerOver={(e) => {
setHover(true)
setHighlightRange(sourceRange)
}}
onPointerOut={(e) => {
setHover(false)
setHighlightRange([0, 0])
}}
onClick={() => {
_onClick()
onClick()
}}
>
<primitive object={geo} />
<meshStandardMaterial
color={hovered ? 'hotpink' : forceHighlight ? 'skyblue' : baseColor}
/>
</mesh>
</>
)
}
type Artifact = ExtrudeGroup | SketchGroup
function useSetAppModeFromCursorLocation(artifacts: Artifact[]) {
const { selectionRanges, guiMode, setGuiMode, ast } = useStore(
({ selectionRanges, guiMode, setGuiMode, ast }) => ({
selectionRanges,
guiMode,
setGuiMode,
ast,
})
)
useEffect(() => {
const artifactsWithinCursorRange: (
| {
parentType: Artifact['type']
isParent: true
pathToNode: PathToNode
sourceRange: SourceRange
rotation: Rotation
position: Position
}
| {
parentType: Artifact['type']
isParent: false
pathToNode: PathToNode
sourceRange: SourceRange
rotation: Rotation
position: Position
}
)[] = []
artifacts?.forEach((artifact) => {
artifact.value.forEach((geo) => {
if (
isOverlap(
geo.__geoMeta.sourceRange,
selectionRanges.codeBasedSelections[0].range
)
) {
artifactsWithinCursorRange.push({
parentType: artifact.type,
isParent: false,
pathToNode: geo.__geoMeta.pathToNode,
sourceRange: geo.__geoMeta.sourceRange,
rotation: artifact.rotation,
position: artifact.position,
})
}
})
artifact.__meta.forEach((meta) => {
if (
isOverlap(
meta.sourceRange,
selectionRanges.codeBasedSelections[0].range
)
) {
artifactsWithinCursorRange.push({
parentType: artifact.type,
isParent: true,
pathToNode: meta.pathToNode,
sourceRange: meta.sourceRange,
rotation: artifact.rotation,
position: artifact.position,
})
}
})
})
const parentArtifacts = artifactsWithinCursorRange.filter((a) => a.isParent)
const hasSketchArtifact = artifactsWithinCursorRange.filter(
({ parentType }) => parentType === 'sketchGroup'
)
const hasExtrudeArtifact = artifactsWithinCursorRange.filter(
({ parentType }) => parentType === 'extrudeGroup'
)
const artifact = parentArtifacts[0]
const shouldHighlight = !!artifact || hasSketchArtifact.length
if (
(guiMode.mode === 'default' || guiMode.mode === 'canEditSketch') &&
ast &&
hasSketchArtifact.length
) {
const pathToNode = getNodePathFromSourceRange(
ast,
hasSketchArtifact[0].sourceRange
)
const { rotation, position } = hasSketchArtifact[0]
setGuiMode({ mode: 'canEditSketch', pathToNode, rotation, position })
} else if (
hasExtrudeArtifact.length &&
(guiMode.mode === 'default' || guiMode.mode === 'canEditExtrude') &&
ast
) {
const pathToNode = getNodePathFromSourceRange(
ast,
hasExtrudeArtifact[0].sourceRange
)
const { rotation, position } = hasExtrudeArtifact[0]
setGuiMode({ mode: 'canEditExtrude', pathToNode, rotation, position })
} else if (
!shouldHighlight &&
(guiMode.mode === 'canEditExtrude' || guiMode.mode === 'canEditSketch')
) {
setGuiMode({ mode: 'default' })
}
}, [artifacts, selectionRanges])
}
function useConstraintColors(sourceRange: [number, number]): string {
const { guiMode, ast } = useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
}))
const [baseColor, setBaseColor] = useState('orange')
useEffect(() => {
if (!ast || guiMode.mode !== 'sketch') {
setBaseColor('orange')
return
}
try {
const level = getConstraintLevelFromSourceRange(sourceRange, ast)
if (level === 'free') {
setBaseColor('orange')
} else if (level === 'partial') {
setBaseColor('IndianRed')
} else if (level === 'full') {
setBaseColor('lightgreen')
}
} catch (e) {
setBaseColor('orange')
}
}, [guiMode, ast, sourceRange])
return baseColor
}

View File

@ -1,181 +0,0 @@
import { useState } from 'react'
import { Selections, useStore } from '../useStore'
import { DoubleSide, Vector3, Quaternion } from 'three'
import { Program } from '../lang/abstractSyntaxTree'
import { addNewSketchLn } from '../lang/std/sketch'
import { roundOff } from '../lib/utils'
export const SketchPlane = () => {
const {
ast,
guiMode,
programMemory,
updateAstAsync,
setSelectionRanges,
selectionRanges,
isShiftDown,
setCursor,
} = useStore((s) => ({
guiMode: s.guiMode,
ast: s.ast,
updateAstAsync: s.updateAstAsync,
programMemory: s.programMemory,
setSelectionRanges: s.setSelectionRanges,
selectionRanges: s.selectionRanges,
isShiftDown: s.isShiftDown,
setCursor: s.setCursor,
}))
const [xHover, setXHover] = useState(false)
const [yHover, setYHover] = useState(false)
if (guiMode.mode !== 'sketch') {
return null
}
if (!(guiMode.sketchMode === 'sketchEdit') && !('isTooltip' in guiMode)) {
return null
}
const sketchGridName = 'sketchGrid'
let clickDetectQuaternion = new Quaternion(...guiMode.rotation)
let temp = new Quaternion().setFromAxisAngle(
new Vector3(1, 0, 0),
Math.PI / 2
)
let position = guiMode.position
const gridQuaternion = new Quaternion().multiplyQuaternions(
new Quaternion(...guiMode.rotation),
temp
)
const onAxisClick = (name: 'y-axis' | 'x-axis') => () => {
const _selectionRanges: Selections = isShiftDown
? selectionRanges
: {
codeBasedSelections: [
{
range: [0, 0],
type: 'default',
},
],
otherSelections: [],
}
if (!isShiftDown) {
setCursor({
..._selectionRanges,
otherSelections: [name],
})
}
setTimeout(() => {
setSelectionRanges({
..._selectionRanges,
otherSelections: [name],
})
}, 100)
}
return (
<>
<mesh
quaternion={clickDetectQuaternion}
position={position}
name={sketchGridName}
onPointerDown={(e) => {
if (!('isTooltip' in guiMode)) {
return
}
const sketchGridIntersection = e.intersections.find(
({ object }) => object.name === sketchGridName
)
const inverseQuaternion = clickDetectQuaternion.clone().invert()
let transformedPoint = sketchGridIntersection?.point.clone()
if (transformedPoint) {
transformedPoint.applyQuaternion(inverseQuaternion)
transformedPoint?.sub(
new Vector3(...position).applyQuaternion(inverseQuaternion)
)
}
const point = roundy(transformedPoint)
let _ast: Program = ast
? ast
: {
type: 'Program',
start: 0,
end: 0,
body: [],
nonCodeMeta: {},
}
const { modifiedAst } = addNewSketchLn({
node: _ast,
programMemory,
to: [point.x, point.y],
fnName: guiMode.sketchMode,
pathToNode: guiMode.pathToNode,
})
updateAstAsync(modifiedAst)
}}
>
<planeGeometry args={[30, 40]} />
<meshStandardMaterial
color="blue"
side={DoubleSide}
opacity={0}
transparent
/>
</mesh>
<gridHelper
args={[50, 50, 'blue', 'hotpink']}
quaternion={gridQuaternion}
position={position}
onClick={() =>
!isShiftDown &&
setSelectionRanges({
...selectionRanges,
otherSelections: [],
})
}
/>
<mesh
onPointerOver={() => setXHover(true)}
onPointerOut={() => setXHover(false)}
onClick={onAxisClick('x-axis')}
>
<boxGeometry args={[50, 0.2, 0.05]} />
<meshStandardMaterial
color={
selectionRanges.otherSelections.includes('x-axis')
? 'skyblue'
: xHover
? '#FF5555'
: '#FF1111'
}
/>
</mesh>
<mesh
onPointerOver={() => setYHover(true)}
onPointerOut={() => setYHover(false)}
onClick={onAxisClick('y-axis')}
>
<boxGeometry args={[0.2, 50, 0.05]} />
<meshStandardMaterial
color={
selectionRanges.otherSelections.includes('y-axis')
? 'skyblue'
: yHover
? '#5555FF'
: '#1111FF'
}
/>
</mesh>
</>
)
}
function roundy({ x, y, z }: any) {
return {
x: roundOff(x, 2),
y: roundOff(y, 2),
z: roundOff(z, 2),
}
}

View File

@ -1,9 +1,16 @@
import { useEffect, useRef } from 'react'
import { MouseEventHandler, useEffect, useRef } from 'react'
import { PanelHeader } from '../components/PanelHeader'
import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore'
import { throttle } from '../lib/utils'
export const Stream = () => {
const videoRef = useRef<HTMLVideoElement>(null)
const cmdId = useRef('')
const { mediaStream, engineCommandManager } = useStore((s) => ({
mediaStream: s.mediaStream,
engineCommandManager: s.engineCommandManager,
}))
useEffect(() => {
if (
@ -11,228 +18,111 @@ export const Stream = () => {
typeof RTCPeerConnection === 'undefined'
)
return
const url = 'wss://api.dev.kittycad.io/ws/modeling/commands'
const file_id = uuidv4()
let currentCmdId: null | string = null
const [pc, socket] = [new RTCPeerConnection(), new WebSocket(url)]
// Connection opened
socket.addEventListener('open', (event) => {
console.log('Connected to websocket, waiting for ICE servers')
})
if (!videoRef.current) return
if (!mediaStream) return
videoRef.current.srcObject = mediaStream
}, [mediaStream, engineCommandManager])
socket.addEventListener('close', (event) => {
console.log('websocket connection closed')
})
const file_id = uuidv4()
socket.addEventListener('error', (event) => {
console.log('websocket connection error')
})
// Listen for messages
socket.addEventListener('message', (event) => {
//console.log('Message from server ', event.data);
if (event.data instanceof Blob) {
const reader = new FileReader()
reader.onload = () => {
//console.log("Result: " + reader.result);
}
reader.readAsText(event.data)
} else {
const message = JSON.parse(event.data)
if (message.type === 'SDPAnswer') {
pc.setRemoteDescription(new RTCSessionDescription(message.answer))
} else if (message.type === 'TrickleIce') {
console.log('got remote trickle ice')
pc.addIceCandidate(message.candidate)
} else if (message.type === 'IceServerInfo') {
console.log('received IceServerInfo')
pc.setConfiguration({
iceServers: message.ice_servers,
iceTransportPolicy: 'relay',
})
pc.ontrack = function (event) {
if (videoRef.current) {
videoRef.current.srcObject = event.streams[0]
videoRef.current.muted = true
videoRef.current.autoplay = true
videoRef.current.controls = false
}
}
pc.oniceconnectionstatechange = (e) =>
console.log(pc.iceConnectionState)
pc.onicecandidate = (event) => {
if (event.candidate === null) {
console.log('sent SDPOffer')
socket.send(
JSON.stringify({
type: 'SDPOffer',
offer: pc.localDescription,
})
)
} else {
console.log('sending trickle ice candidate')
const { candidate } = event
socket.send(
JSON.stringify({
type: 'TrickleIce',
candidate: candidate.toJSON(),
})
)
}
}
// Offer to receive 1 video track
pc.addTransceiver('video', {
direction: 'sendrecv',
})
pc.createOffer()
.then((d) => pc.setLocalDescription(d))
.then(() => {
console.log('sent SDPOffer begin')
socket.send(
JSON.stringify({
type: 'SDPOffer',
offer: pc.localDescription,
})
)
})
.catch(console.log)
}
}
})
const debounceSocketSend = throttle((message) => {
socket.send(JSON.stringify(message))
}, 100)
const handleClick = ({ clientX, clientY }: MouseEvent) => {
if (!videoRef.current) return
const { left, top } = videoRef.current.getBoundingClientRect()
const x = clientX - left
const y = clientY - top
console.log('click', x, y)
if (currentCmdId == null) {
currentCmdId = uuidv4()
debounceSocketSend({
type: 'ModelingCmdReq',
cmd: {
CameraDragStart: {
interaction: 'rotate',
window: {
x: x,
y: y,
},
},
},
cmd_id: uuidv4(),
file_id: file_id,
})
}
}
const handleMouseUp = ({ clientX, clientY }: MouseEvent) => {
if (!videoRef.current) return
const { left, top } = videoRef.current.getBoundingClientRect()
const x = clientX - left
const y = clientY - top
console.log('click', x, y)
if (currentCmdId == null) {
return
}
debounceSocketSend({
type: 'ModelingCmdReq',
cmd: {
CameraDragEnd: {
interaction: 'rotate',
window: {
x: x,
y: y,
},
const debounceSocketSend = throttle((message) => {
engineCommandManager?.sendSceneCommand(message)
}, 100)
const handleMouseMove: MouseEventHandler<HTMLVideoElement> = ({
clientX,
clientY,
}) => {
if (!videoRef.current) return
if (!cmdId.current) return
const { left, top } = videoRef.current.getBoundingClientRect()
const x = clientX - left
const y = clientY - top
debounceSocketSend({
type: 'ModelingCmdReq',
cmd: {
CameraDragMove: {
interaction: 'rotate',
window: {
x: x,
y: y,
},
},
cmd_id: uuidv4(),
file_id: file_id,
})
currentCmdId = null
}
const handleMouseMove = ({ clientX, clientY }: MouseEvent) => {
if (!videoRef.current) return
const { left, top } = videoRef.current.getBoundingClientRect()
const x = clientX - left
const y = clientY - top
if (currentCmdId == null) {
return
} else {
console.log('mouse move', x, y)
debounceSocketSend({
type: 'ModelingCmdReq',
cmd: {
CameraDragMove: {
interaction: 'rotate',
window: {
x: x,
y: y,
},
},
},
cmd_id: uuidv4(),
file_id: file_id,
})
}
const handleMouseDown: MouseEventHandler<HTMLVideoElement> = ({
clientX,
clientY,
}) => {
if (!videoRef.current) return
const { left, top } = videoRef.current.getBoundingClientRect()
const x = clientX - left
const y = clientY - top
console.log('click', x, y)
const newId = uuidv4()
cmdId.current = newId
engineCommandManager?.sendSceneCommand({
type: 'ModelingCmdReq',
cmd: {
CameraDragStart: {
interaction: 'rotate',
window: {
x: x,
y: y,
},
cmd_id: uuidv4(),
file_id: file_id,
})
}
}
if (videoRef.current) {
videoRef.current.addEventListener('mousemove', handleMouseMove)
videoRef.current.addEventListener('mousedown', handleClick)
videoRef.current.addEventListener('mouseup', handleMouseUp)
},
},
cmd_id: newId,
file_id,
})
}
const handleMouseUp: MouseEventHandler<HTMLVideoElement> = ({
clientX,
clientY,
}) => {
if (!videoRef.current) return
const { left, top } = videoRef.current.getBoundingClientRect()
const x = clientX - left
const y = clientY - top
if (cmdId.current == null) {
return
}
return () => {
socket.close()
pc.close()
if (!videoRef.current) return
videoRef.current.removeEventListener('mousemove', handleMouseMove)
videoRef.current.removeEventListener('mousedown', handleClick)
videoRef.current.removeEventListener('mouseup', handleMouseUp)
}
}, [])
engineCommandManager?.sendSceneCommand({
type: 'ModelingCmdReq',
cmd: {
CameraDragEnd: {
interaction: 'rotate',
window: {
x: x,
y: y,
},
},
},
cmd_id: uuidv4(),
file_id: file_id,
})
cmdId.current = ''
}
return (
<div>
<PanelHeader title="Stream" />
<video ref={videoRef} />
<video
ref={videoRef}
muted
autoPlay
controls={false}
onMouseMove={handleMouseMove}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
/>
</div>
)
}
function throttle(
func: (...args: any[]) => any,
wait: number
): (...args: any[]) => any {
let timeout: ReturnType<typeof setTimeout> | null
let latestArgs: any[]
let latestTimestamp: number
function later() {
timeout = null
func(...latestArgs)
}
function throttled(...args: any[]) {
const currentTimestamp = Date.now()
latestArgs = args
if (!latestTimestamp || currentTimestamp - latestTimestamp >= wait) {
latestTimestamp = currentTimestamp
func(...latestArgs)
} else if (!timeout) {
timeout = setTimeout(later, wait - (currentTimestamp - latestTimestamp))
}
}
return throttled
}

View File

@ -58,7 +58,6 @@ export const Intersect = () => {
selectionRanges.codeBasedSelections?.[1]?.type !== 'line-end' &&
previousSegment &&
previousSegment.isParallelAndConstrained
console.log(shouldUsePreviousSegment)
const _forcedSelectionRanges: typeof selectionRanges = {
...selectionRanges,

View File

@ -15,7 +15,7 @@ export const lineHighlightField = StateField.define({
for (let e of tr.effects) {
if (e.is(addLineHighlight)) {
lines = Decoration.none
const [from, to] = e.value
const [from, to] = e.value || [0, 0]
if (!(from === to && from === 0)) {
lines = lines.update({ add: [matchDeco.range(from, to)] })
deco.push(matchDeco.range(from, to))

View File

@ -1,21 +0,0 @@
import { useStore, Selection, Selections } from '../useStore'
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 selections: Selections = {
...selectionRanges,
codeBasedSelections: isShiftDown
? [...selectionRanges.codeBasedSelections, { range: sourceRange, type }]
: [{ range: sourceRange, type }],
}
setCursor(selections)
}
}

View File

@ -1,19 +1,20 @@
import { abstractSyntaxTree } from './abstractSyntaxTree'
import { lexer } from './tokeniser'
import { executor, SketchGroup, ExtrudeGroup } from './executor'
import { SketchGroup, ExtrudeGroup } from './executor'
import { initPromise } from './rust'
import { executor } from '../lib/testHelpers'
beforeAll(() => initPromise)
describe('testing artifacts', () => {
test('sketch artifacts', () => {
test('sketch artifacts', async () => {
const code = `
const mySketch001 = startSketchAt([0, 0])
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
|> rx(45, %)
show(mySketch001)`
const programMemory = executor(abstractSyntaxTree(lexer(code)))
const programMemory = await executor(abstractSyntaxTree(lexer(code)))
const geos = programMemory?.return?.map(
(a) => programMemory?.root?.[a.name]
)
@ -26,6 +27,7 @@ show(mySketch001)`
to: [0, 0],
from: [0, 0],
__geoMeta: {
id: '66366561-6465-4734-a463-366330356563',
sourceRange: [21, 42],
pathToNode: [],
geos: ['sketchBase'],
@ -38,6 +40,7 @@ show(mySketch001)`
from: [0, 0],
__geoMeta: {
sourceRange: [48, 73],
id: '30366338-6462-4330-a364-303935626163',
pathToNode: [],
geos: ['line', 'lineEnd'],
},
@ -48,6 +51,7 @@ show(mySketch001)`
from: [-1.59, -1.54],
__geoMeta: {
sourceRange: [79, 103],
id: '32653334-6331-4231-b162-663334363535',
pathToNode: [],
geos: ['line', 'lineEnd'],
},
@ -62,7 +66,7 @@ show(mySketch001)`
},
])
})
test('extrude artifacts', () => {
test('extrude artifacts', async () => {
const code = `
const mySketch001 = startSketchAt([0, 0])
|> lineTo([-1.59, -1.54], %)
@ -70,7 +74,7 @@ const mySketch001 = startSketchAt([0, 0])
|> rx(45, %)
|> extrude(2, %)
show(mySketch001)`
const programMemory = executor(abstractSyntaxTree(lexer(code)))
const programMemory = await executor(abstractSyntaxTree(lexer(code)))
const geos = programMemory?.return?.map(
(a) => programMemory?.root?.[a.name]
)
@ -87,7 +91,9 @@ show(mySketch001)`
0.8563498075401887,
],
__geoMeta: {
geo: 'PlaneGeometry',
id: expect.anything(),
geo: undefined,
refId: '30366338-6462-4330-a364-303935626163',
sourceRange: [48, 73],
pathToNode: [],
},
@ -102,7 +108,9 @@ show(mySketch001)`
0.4923604609001174,
],
__geoMeta: {
geo: 'PlaneGeometry',
id: expect.anything(),
geo: undefined,
refId: '32653334-6331-4231-b162-663334363535',
sourceRange: [79, 103],
pathToNode: [],
},
@ -118,7 +126,7 @@ show(mySketch001)`
},
])
})
test('sketch extrude and sketch on one of the faces', () => {
test('sketch extrude and sketch on one of the faces', async () => {
const code = `
const sk1 = startSketchAt([0, 0])
|> lineTo([-2.5, 0], %)
@ -138,7 +146,7 @@ const sk2 = startSketchAt([0, 0])
show(theExtrude, sk2)`
const programMemory = executor(abstractSyntaxTree(lexer(code)))
const programMemory = await executor(abstractSyntaxTree(lexer(code)))
const geos = programMemory?.return?.map(
(a) => programMemory?.root?.[a.name]
)
@ -155,7 +163,9 @@ show(theExtrude, sk2)`
0.9230002039112793,
],
__geoMeta: {
geo: 'PlaneGeometry',
id: expect.anything(), // todo figure out why isn't deterministic
geo: undefined,
refId: '36613364-6238-4330-b766-613131633135',
sourceRange: [40, 60],
pathToNode: [],
},
@ -170,7 +180,9 @@ show(theExtrude, sk2)`
-0.5362616571538269,
],
__geoMeta: {
geo: 'PlaneGeometry',
id: expect.anything(),
geo: undefined,
refId: '32313832-3531-4933-b839-316634316237',
sourceRange: [66, 102],
pathToNode: [],
},
@ -186,7 +198,9 @@ show(theExtrude, sk2)`
0.5997895323824204,
],
__geoMeta: {
geo: 'PlaneGeometry',
id: expect.anything(),
geo: undefined,
refId: '31356564-3364-4562-a438-653732633238',
sourceRange: [108, 127],
pathToNode: [],
},
@ -216,7 +230,9 @@ show(theExtrude, sk2)`
-0.20351996751370383,
],
__geoMeta: {
geo: 'PlaneGeometry',
id: expect.anything(),
geo: undefined,
refId: '31623462-6433-4233-b361-303837663464',
sourceRange: [317, 337],
pathToNode: [],
},
@ -231,7 +247,9 @@ show(theExtrude, sk2)`
-0.5818075544860157,
],
__geoMeta: {
geo: 'PlaneGeometry',
id: expect.anything(),
geo: undefined,
refId: '66363230-3430-4961-b831-646363376538',
sourceRange: [343, 378],
pathToNode: [],
},
@ -247,7 +265,9 @@ show(theExtrude, sk2)`
-0.7544557394170275,
],
__geoMeta: {
geo: 'PlaneGeometry',
id: expect.anything(),
geo: undefined,
refId: '62366564-3261-4061-b533-623433336531',
sourceRange: [384, 403],
pathToNode: [],
},
@ -270,14 +290,17 @@ show(theExtrude, sk2)`
function removeGeo(arts: (SketchGroup | ExtrudeGroup)[]): any {
return arts.map((art) => {
if (art.type === 'extrudeGroup') {
if (!art) {
return {}
}
if (art?.type === 'extrudeGroup') {
return {
...art,
value: art.value.map((v) => ({
...v,
__geoMeta: {
...v.__geoMeta,
geo: v.__geoMeta.geo.type,
geo: (v?.__geoMeta as any)?.geo?.type,
},
})),
}

View File

@ -1,158 +0,0 @@
import {
BoxGeometry,
SphereGeometry,
BufferGeometry,
PlaneGeometry,
Quaternion,
Euler,
CylinderGeometry,
} from 'three'
import { Rotation, Position } from './executor'
export function baseGeo({ from }: { from: [number, number, number] }) {
const baseSphere = new SphereGeometry(0.25)
baseSphere.translate(from[0], from[1], from[2])
return baseSphere
}
function trigCalcs({
from,
to,
}: {
from: [number, number, number]
to: [number, number, number]
}) {
const sq = (a: number): number => a * a
const centre = [
(from[0] + to[0]) / 2,
(from[1] + to[1]) / 2,
(from[2] + to[2]) / 2,
]
const Hypotenuse3d = Math.sqrt(
sq(from[0] - to[0]) + sq(from[1] - to[1]) + sq(from[2] - to[2])
)
const ry = Math.atan2(from[2] - to[2], from[0] - to[0])
const Hypotenuse2d = Math.sqrt(sq(from[0] - to[0]) + sq(from[2] - to[2]))
const rz =
Math.abs(Math.atan((to[1] - from[1]) / Hypotenuse2d)) *
Math.sign(to[1] - from[1]) *
(Math.sign(to[0] - from[0]) || 1)
const sign = ry === 0 ? 1 : -1
return {
centre,
Hypotenuse: Hypotenuse3d,
ry,
rz,
sign,
}
}
export function lineGeo({
from,
to,
}: {
from: [number, number, number]
to: [number, number, number]
}): {
line: BufferGeometry
tip: BufferGeometry
centre: BufferGeometry
} {
const {
centre,
Hypotenuse: Hypotenuse3d,
ry,
rz,
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 - 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, lineEndLength + 0.05, 4)
lineEnd1.translate(0, -0.1, 0)
lineEnd1.rotateY(Math.PI / 4)
lineEnd1.rotateZ(rz)
const _sign = to[0] === from[0] ? -sign : sign
lineEnd1.rotateZ((_sign * Math.PI) / 2)
lineEnd1.translate(to[0], to[1], to[2])
const centreSphere = new SphereGeometry(0.15)
centreSphere.translate(centre[0], centre[1], centre[2])
// const lineEnd2 = new SphereGeometry(0.15);
// lineEnd2.translate(from[0], from[1], from[2])
return {
line: lineBody,
tip: lineEnd1,
centre: centreSphere,
}
}
export function sketchBaseGeo({ to }: { to: [number, number, number] }): {
base: BufferGeometry
} {
return { base: new SphereGeometry(0.25).translate(to[0], to[1], to[2]) }
}
export interface extrudeWallGeo {
line: BufferGeometry
tip: BufferGeometry
centre: BufferGeometry
}
export function extrudeGeo({
from,
to,
length,
extrusionDirection = 1,
}: {
from: [number, number, number]
to: [number, number, number]
length: number
extrusionDirection?: number
}): {
geo: BufferGeometry
position: Position
rotation: Rotation
} {
const {
centre,
Hypotenuse: Hypotenuse3d,
ry,
rz,
sign,
} = trigCalcs({ from, to })
const face = new PlaneGeometry(Hypotenuse3d, length, 2, 2)
face.rotateX(Math.PI * 0.5)
face.translate(Hypotenuse3d * 0.5, 0, -length * 0.5 * sign)
face.rotateY(ry)
face.rotateZ(rz)
face.translate(to[0], to[1], to[2])
const quat = new Quaternion()
const otherSign = ry === 0 ? 1 : -1 // don't ask questions, it works okay
const euler = new Euler(
(Math.PI * otherSign * extrusionDirection) / 2,
ry,
rz * sign * -otherSign,
'XYZ'
)
quat.setFromEuler(euler)
return {
geo: face,
position: [centre[0], centre[1], centre[2]],
rotation: quat.toArray() as Rotation,
}
}

View File

@ -2,33 +2,34 @@ import fs from 'node:fs'
import { abstractSyntaxTree } from './abstractSyntaxTree'
import { lexer } from './tokeniser'
import { executor, ProgramMemory, Path, SketchGroup } from './executor'
import { ProgramMemory, Path, SketchGroup } from './executor'
import { initPromise } from './rust'
import { executor } from '../lib/testHelpers'
beforeAll(() => initPromise)
describe('test', () => {
it('test assigning two variables, the second summing with the first', () => {
describe('test executor', () => {
it('test assigning two variables, the second summing with the first', async () => {
const code = `const myVar = 5
const newVar = myVar + 1`
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(5)
expect(root.newVar.value).toBe(6)
})
it('test assigning a var with a string', () => {
it('test assigning a var with a string', async () => {
const code = `const myVar = "a str"`
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe('a str')
})
it('test assigning a var by cont concatenating two strings string execute', () => {
it('test assigning a var by cont concatenating two strings string execute', async () => {
const code = fs.readFileSync(
'./src/lang/testExamples/variableDeclaration.cado',
'utf-8'
)
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe('a str another str')
})
it('test with function call', () => {
it('test with function call', async () => {
const code = `
const myVar = "hello"
log(5, myVar)`
@ -44,15 +45,15 @@ log(5, myVar)`
],
},
}
const { root } = executor(abstractSyntaxTree(lexer(code)), {
const { root } = await executor(abstractSyntaxTree(lexer(code)), {
root: programMemoryOverride,
_sketch: [],
pendingMemory: {},
})
expect(root.myVar.value).toBe('hello')
expect(programMemoryOverride.log.value).toHaveBeenCalledWith(5, 'hello')
})
it('fn funcN = () => {} execute', () => {
const { root } = exe(
it('fn funcN = () => {} execute', async () => {
const { root } = await exe(
[
'fn funcN = (a, b) => {',
' return a + b',
@ -64,7 +65,7 @@ log(5, myVar)`
expect(root.theVar.value).toBe(60)
expect(root.magicNum.value).toBe(69)
})
it('sketch declaration', () => {
it('sketch declaration', async () => {
let code = `const mySketch = startSketchAt([0,0])
|> lineTo({to: [0,2], tag: "myPath"}, %)
|> lineTo([2,3], %)
@ -72,7 +73,7 @@ log(5, myVar)`
// |> close(%)
show(mySketch)
`
const { root, return: _return } = exe(code)
const { root, return: _return } = await exe(code)
// geo is three js buffer geometry and is very bloated to have in tests
const minusGeo = removeGeoFromPaths(root.mySketch.value)
expect(minusGeo).toEqual([
@ -82,6 +83,7 @@ show(mySketch)
from: [0, 0],
__geoMeta: {
sourceRange: [43, 80],
id: '37333036-3033-4432-b530-643030303837',
pathToNode: [],
geos: ['line', 'lineEnd'],
},
@ -93,6 +95,7 @@ show(mySketch)
from: [0, 2],
__geoMeta: {
sourceRange: [86, 102],
id: '32343136-3330-4134-a462-376437386365',
pathToNode: [],
geos: ['line', 'lineEnd'],
},
@ -103,6 +106,7 @@ show(mySketch)
from: [2, 3],
__geoMeta: {
sourceRange: [108, 151],
id: '32306132-6130-4138-b832-636363326330',
pathToNode: [],
geos: ['line', 'lineEnd'],
},
@ -120,16 +124,16 @@ show(mySketch)
])
})
it('pipe binary expression into call expression', () => {
it('pipe binary expression into call expression', async () => {
const code = [
'fn myFn = (a) => { return a + 1 }',
'const myVar = 5 + 1 |> myFn(%)',
].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(7)
})
it('rotated sketch', () => {
it('rotated sketch', async () => {
const code = [
'const mySk1 = startSketchAt([0,0])',
' |> lineTo([1,1], %)',
@ -137,7 +141,7 @@ show(mySketch)
' |> lineTo([1, 1], %)',
'const rotated = rx(90, mySk1)',
].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.mySk1.value).toHaveLength(3)
expect(root?.rotated?.type).toBe('sketchGroup')
if (
@ -154,7 +158,7 @@ show(mySketch)
])
})
it('execute pipe sketch into call expression', () => {
it('execute pipe sketch into call expression', async () => {
const code = [
'const mySk1 = startSketchAt([0,0])',
' |> lineTo([1,1], %)',
@ -162,7 +166,7 @@ show(mySketch)
' |> lineTo([1,1], %)',
' |> rx(90, %)',
].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
const striptVersion = removeGeoFromSketch(root.mySk1 as SketchGroup)
expect(striptVersion).toEqual({
type: 'sketchGroup',
@ -171,6 +175,7 @@ show(mySketch)
to: [0, 0],
from: [0, 0],
__geoMeta: {
id: '37663863-3664-4366-a637-623739336334',
sourceRange: [14, 34],
pathToNode: [],
geos: ['sketchBase'],
@ -183,6 +188,7 @@ show(mySketch)
from: [0, 0],
__geoMeta: {
sourceRange: [40, 56],
id: '34356231-3362-4363-b935-393033353034',
pathToNode: [],
geos: ['line', 'lineEnd'],
},
@ -193,6 +199,7 @@ show(mySketch)
from: [1, 1],
__geoMeta: {
sourceRange: [62, 100],
id: '39623339-3538-4366-b633-356630326639',
pathToNode: [],
geos: ['line', 'lineEnd'],
},
@ -204,6 +211,7 @@ show(mySketch)
from: [0, 1],
__geoMeta: {
sourceRange: [106, 122],
id: '30636135-6232-4335-b665-366562303161',
pathToNode: [],
geos: ['line', 'lineEnd'],
},
@ -217,11 +225,11 @@ show(mySketch)
],
})
})
it('execute array expression', () => {
it('execute array expression', async () => {
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
'\n'
)
const { root } = exe(code)
const { root } = await exe(code)
// TODO path to node is probably wrong here, zero indexes are not correct
expect(root).toEqual({
three: {
@ -268,12 +276,12 @@ show(mySketch)
},
})
})
it('execute object expression', () => {
it('execute object expression', async () => {
const code = [
'const three = 3',
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.yo).toEqual({
type: 'userVal',
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
@ -291,11 +299,11 @@ show(mySketch)
],
})
})
it('execute memberExpression', () => {
it('execute memberExpression', async () => {
const code = ["const yo = {a: {b: '123'}}", "const myVar = yo.a['b']"].join(
'\n'
)
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar).toEqual({
type: 'userVal',
value: '123',
@ -316,96 +324,96 @@ show(mySketch)
})
describe('testing math operators', () => {
it('it can sum', () => {
it('it can sum', async () => {
const code = ['const myVar = 1 + 2'].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(3)
})
it('it can subtract', () => {
it('it can subtract', async () => {
const code = ['const myVar = 1 - 2'].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(-1)
})
it('it can multiply', () => {
it('it can multiply', async () => {
const code = ['const myVar = 1 * 2'].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(2)
})
it('it can divide', () => {
it('it can divide', async () => {
const code = ['const myVar = 1 / 2'].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(0.5)
})
it('it can modulus', () => {
it('it can modulus', async () => {
const code = ['const myVar = 5 % 2'].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(1)
})
it('it can do multiple operations', () => {
it('it can do multiple operations', async () => {
const code = ['const myVar = 1 + 2 * 3'].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(7)
})
it('big example with parans', () => {
it('big example with parans', async () => {
const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(7.4)
})
it('with identifier', () => {
it('with identifier', async () => {
const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(3)
})
it('with identifier', () => {
it('with identifier', async () => {
const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(12.5)
})
it('with callExpression at start', () => {
it('with callExpression at start', async () => {
const code = 'const myVar = min(4, 100) + 2'
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(6)
})
it('with callExpression at end', () => {
it('with callExpression at end', async () => {
const code = 'const myVar = 2 + min(4, 100)'
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(6)
})
it('with nested callExpression', () => {
it('with nested callExpression', async () => {
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(6)
})
it('with unaryExpression', () => {
it('with unaryExpression', async () => {
const code = 'const myVar = -min(100, 3)'
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(-3)
})
it('with unaryExpression in callExpression', () => {
it('with unaryExpression in callExpression', async () => {
const code = 'const myVar = min(-legLen(5, 4), 5)'
const code2 = 'const myVar = min(5 , -legLen(5, 4))'
const { root } = exe(code)
const { root: root2 } = exe(code2)
const { root } = await exe(code)
const { root: root2 } = await exe(code2)
expect(root.myVar.value).toBe(-3)
expect(root.myVar.value).toBe(root2.myVar.value)
})
it('with unaryExpression in ArrayExpression', () => {
it('with unaryExpression in ArrayExpression', async () => {
const code = 'const myVar = [1,-legLen(5, 4)]'
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toEqual([1, -3])
})
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', () => {
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
const code = [
'const part001 = startSketchAt([0, 0])',
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
const sketch = removeGeoFromSketch(root.part001 as SketchGroup)
// result of `-legLen(5, min(3, 999))` should be -4
const yVal = sketch.value?.[0]?.to?.[1]
expect(yVal).toBe(-4)
})
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', () => {
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
const code = [
`const myVar = 3`,
`const part001 = startSketchAt([0, 0])`,
@ -417,7 +425,7 @@ describe('testing math operators', () => {
``,
`show(part001)`,
].join('\n')
const { root } = exe(code)
const { root } = await exe(code)
const sketch = removeGeoFromSketch(root.part001 as SketchGroup)
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
expect(sketch.value?.[1]?.from).toEqual([3, 4])
@ -426,29 +434,31 @@ describe('testing math operators', () => {
`-legLen(segLen('seg01', %), myVar)`,
`legLen(segLen('seg01', %), myVar)`
)
const { root: removedUnaryExpRoot } = exe(removedUnaryExp)
const { root: removedUnaryExpRoot } = await exe(removedUnaryExp)
const removedUnaryExpRootSketch = removeGeoFromSketch(
removedUnaryExpRoot.part001 as SketchGroup
)
// without the minus sign, the y value should be 8
expect(removedUnaryExpRootSketch.value?.[1]?.to).toEqual([6, 8])
})
it('with nested callExpression and binaryExpression', () => {
it('with nested callExpression and binaryExpression', async () => {
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
const { root } = exe(code)
const { root } = await exe(code)
expect(root.myVar.value).toBe(5)
})
})
// helpers
function exe(
async function exe(
code: string,
programMemory: ProgramMemory = { root: {}, _sketch: [] }
programMemory: ProgramMemory = { root: {}, pendingMemory: {} }
) {
const tokens = lexer(code)
const ast = abstractSyntaxTree(tokens)
return executor(ast, programMemory)
const result = await executor(ast, programMemory)
return result
}
function removeGeoFromSketch(sketch: SketchGroup): SketchGroup {

View File

@ -12,7 +12,11 @@ import {
} from './abstractSyntaxTree'
import { InternalFnNames } from './std/stdTypes'
import { internalFns } from './std/std'
import { BufferGeometry } from 'three'
import {
EngineCommandManager,
ArtifactMap,
SourceRangeMap,
} from './std/engineConnection'
export type SourceRange = [number, number]
export type PathToNode = [string | number, string][] // [pathKey, nodeType][]
@ -28,8 +32,8 @@ interface BasePath {
to: [number, number]
name?: string
__geoMeta: {
id: string
geos: {
geo: BufferGeometry
type: 'line' | 'lineEnd' | 'sketchBase'
}[]
sourceRange: SourceRange
@ -59,7 +63,8 @@ export interface AngledLineTo extends BasePath {
interface GeoMeta {
__geoMeta: {
geo: BufferGeometry
id: string
refId?: string
sourceRange: SourceRange
pathToNode: PathToNode
}
@ -69,6 +74,7 @@ export type Path = ToPoint | HorizontalLineTo | AngledLineTo | Base
export interface SketchGroup {
type: 'sketchGroup'
id: string
value: Path[]
start?: Base
position: Position
@ -102,33 +108,116 @@ export interface UserVal {
__meta: Metadata[]
}
type MemoryItem = UserVal | SketchGroup | ExtrudeGroup
interface Memory {
[key: string]: UserVal | SketchGroup | ExtrudeGroup // | Memory
[key: string]: MemoryItem
}
interface PendingMemory {
[key: string]: Promise<MemoryItem>
}
export interface ProgramMemory {
root: Memory
pendingMemory: Partial<PendingMemory>
return?: Identifier[]
_sketch?: Path[]
}
export const executor = (
const addItemToMemory = (
programMemory: ProgramMemory,
key: string,
value: MemoryItem | Promise<MemoryItem>
) => {
const _programMemory = programMemory
if (_programMemory.root[key] || _programMemory.pendingMemory[key]) {
throw new Error(`Memory item ${key} already exists`)
}
if (value instanceof Promise) {
_programMemory.pendingMemory[key] = value
value.then((resolvedValue) => {
_programMemory.root[key] = resolvedValue
delete _programMemory.pendingMemory[key]
})
} else {
_programMemory.root[key] = value
}
return _programMemory
}
const promisifyMemoryItem = async (obj: MemoryItem) => {
if (obj.value instanceof Promise) {
const resolvedGuy = await obj.value
return {
...obj,
value: resolvedGuy,
}
}
return obj
}
const getMemoryItem = async (
programMemory: ProgramMemory,
key: string
): Promise<MemoryItem> => {
if (programMemory.root[key]) {
return programMemory.root[key]
}
if (programMemory.pendingMemory[key]) {
return programMemory.pendingMemory[key] as Promise<MemoryItem>
}
throw new Error(`Memory item ${key} not found`)
}
export const executor = async (
node: Program,
programMemory: ProgramMemory = { root: {}, _sketch: [] },
programMemory: ProgramMemory = { root: {}, pendingMemory: {} },
engineCommandManager: EngineCommandManager,
options: { bodyType: 'root' | 'sketch' | 'block' } = { bodyType: 'root' },
previousPathToNode: PathToNode = [],
// work around while the gemotry is still be stored on the frontend
// will be removed when the stream UI is added.
tempMapCallback: (a: {
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
}) => void = () => {}
): Promise<ProgramMemory> => {
engineCommandManager.startNewSession()
const _programMemory = await _executor(
node,
programMemory,
engineCommandManager,
options,
previousPathToNode
)
const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands()
tempMapCallback({ artifactMap, sourceRangeMap })
engineCommandManager.endSession()
return _programMemory
}
export const _executor = async (
node: Program,
programMemory: ProgramMemory = { root: {}, pendingMemory: {} },
engineCommandManager: EngineCommandManager,
options: { bodyType: 'root' | 'sketch' | 'block' } = { bodyType: 'root' },
previousPathToNode: PathToNode = []
): ProgramMemory => {
const _programMemory: ProgramMemory = {
): Promise<ProgramMemory> => {
let _programMemory: ProgramMemory = {
root: {
...programMemory.root,
},
_sketch: [],
pendingMemory: {
...programMemory.pendingMemory,
},
return: programMemory.return,
}
const { body } = node
body.forEach((statement, bodyIndex) => {
const proms: Promise<any>[] = []
body.forEach(async (statement, bodyIndex) => {
if (statement.type === 'VariableDeclaration') {
statement.declarations.forEach((declaration, index) => {
statement.declarations.forEach(async (declaration, index) => {
const variableName = declaration.id.name
const pathToNode: PathToNode = [
...previousPathToNode,
@ -150,73 +239,115 @@ export const executor = (
]
if (declaration.init.type === 'PipeExpression') {
const value = getPipeExpressionResult(
const prom = getPipeExpressionResult(
declaration.init,
_programMemory,
engineCommandManager,
pathToNode
)
proms.push(prom)
const value = await prom
if (value?.type === 'sketchGroup' || value?.type === 'extrudeGroup') {
_programMemory.root[variableName] = value
_programMemory = addItemToMemory(
_programMemory,
variableName,
value
)
} else {
_programMemory.root[variableName] = {
_programMemory = addItemToMemory(_programMemory, variableName, {
type: 'userVal',
value,
__meta,
}
})
}
} else if (declaration.init.type === 'Identifier') {
_programMemory.root[variableName] = {
_programMemory = addItemToMemory(_programMemory, variableName, {
type: 'userVal',
value: _programMemory.root[declaration.init.name].value,
__meta,
}
})
} else if (declaration.init.type === 'Literal') {
_programMemory.root[variableName] = {
_programMemory = addItemToMemory(_programMemory, variableName, {
type: 'userVal',
value: declaration.init.value,
__meta,
}
})
} else if (declaration.init.type === 'BinaryExpression') {
_programMemory.root[variableName] = {
type: 'userVal',
value: getBinaryExpressionResult(declaration.init, _programMemory),
__meta,
}
const prom = getBinaryExpressionResult(
declaration.init,
_programMemory,
engineCommandManager
)
proms.push(prom)
_programMemory = addItemToMemory(
_programMemory,
variableName,
promisifyMemoryItem({
type: 'userVal',
value: prom,
__meta,
})
)
} else if (declaration.init.type === 'UnaryExpression') {
_programMemory.root[variableName] = {
type: 'userVal',
value: getUnaryExpressionResult(declaration.init, _programMemory),
__meta,
}
const prom = getUnaryExpressionResult(
declaration.init,
_programMemory,
engineCommandManager
)
proms.push(prom)
_programMemory = addItemToMemory(
_programMemory,
variableName,
promisifyMemoryItem({
type: 'userVal',
value: prom,
__meta,
})
)
} else if (declaration.init.type === 'ArrayExpression') {
const valueInfo: { value: any; __meta?: Metadata }[] =
const valueInfo: Promise<{ value: any; __meta?: Metadata }>[] =
declaration.init.elements.map(
(element): { value: any; __meta?: Metadata } => {
async (element): Promise<{ value: any; __meta?: Metadata }> => {
if (element.type === 'Literal') {
return {
value: element.value,
}
} else if (element.type === 'BinaryExpression') {
const prom = getBinaryExpressionResult(
element,
_programMemory,
engineCommandManager
)
proms.push(prom)
return {
value: getBinaryExpressionResult(element, _programMemory),
value: await prom,
}
} else if (element.type === 'PipeExpression') {
const prom = getPipeExpressionResult(
element,
_programMemory,
engineCommandManager,
pathToNode
)
proms.push(prom)
return {
value: getPipeExpressionResult(
element,
_programMemory,
pathToNode
),
value: await prom,
}
} else if (element.type === 'Identifier') {
const node = _programMemory.root[element.name]
const node = await getMemoryItem(_programMemory, element.name)
return {
value: node.value,
__meta: node.__meta[node.__meta.length - 1],
}
} else if (element.type === 'UnaryExpression') {
const prom = getUnaryExpressionResult(
element,
_programMemory,
engineCommandManager
)
proms.push(prom)
return {
value: getUnaryExpressionResult(element, _programMemory),
value: await prom,
}
} else {
throw new Error(
@ -225,73 +356,116 @@ export const executor = (
}
}
)
const meta = valueInfo
const awaitedValueInfo = await Promise.all(valueInfo)
const meta = awaitedValueInfo
.filter(({ __meta }) => __meta)
.map(({ __meta }) => __meta) as Metadata[]
_programMemory.root[variableName] = {
_programMemory = addItemToMemory(_programMemory, variableName, {
type: 'userVal',
value: valueInfo.map(({ value }) => value),
value: awaitedValueInfo.map(({ value }) => value),
__meta: [...__meta, ...meta],
}
})
} else if (declaration.init.type === 'ObjectExpression') {
_programMemory.root[variableName] = {
type: 'userVal',
value: executeObjectExpression(_programMemory, declaration.init),
__meta,
}
const prom = executeObjectExpression(
_programMemory,
declaration.init,
engineCommandManager
)
proms.push(prom)
_programMemory = addItemToMemory(
_programMemory,
variableName,
promisifyMemoryItem({
type: 'userVal',
value: prom,
__meta,
})
)
} else if (declaration.init.type === 'FunctionExpression') {
const fnInit = declaration.init
_programMemory.root[declaration.id.name] = {
type: 'userVal',
value: (...args: any[]) => {
const fnMemory: ProgramMemory = {
root: {
..._programMemory.root,
},
_sketch: [],
}
if (args.length > fnInit.params.length) {
throw new Error(
`Too many arguments passed to function ${declaration.id.name}`
)
} else if (args.length < fnInit.params.length) {
throw new Error(
`Too few arguments passed to function ${declaration.id.name}`
)
}
fnInit.params.forEach((param, index) => {
fnMemory.root[param.name] = {
type: 'userVal',
value: args[index],
__meta,
_programMemory = addItemToMemory(
_programMemory,
declaration.id.name,
{
type: 'userVal',
value: async (...args: any[]) => {
let fnMemory: ProgramMemory = {
root: {
..._programMemory.root,
},
pendingMemory: {
..._programMemory.pendingMemory,
},
}
})
return executor(fnInit.body, fnMemory, { bodyType: 'block' })
.return
},
__meta,
}
if (args.length > fnInit.params.length) {
throw new Error(
`Too many arguments passed to function ${declaration.id.name}`
)
} else if (args.length < fnInit.params.length) {
throw new Error(
`Too few arguments passed to function ${declaration.id.name}`
)
}
fnInit.params.forEach((param, index) => {
fnMemory = addItemToMemory(fnMemory, param.name, {
type: 'userVal',
value: args[index],
__meta,
})
})
const prom = _executor(
fnInit.body,
fnMemory,
engineCommandManager,
{
bodyType: 'block',
}
)
proms.push(prom)
const result = (await prom).return
return result
},
__meta,
}
)
} else if (declaration.init.type === 'MemberExpression') {
_programMemory.root[variableName] = {
type: 'userVal',
value: getMemberExpressionResult(declaration.init, _programMemory),
__meta,
}
await Promise.all([...proms]) // TODO wait for previous promises, does that makes sense?
const prom = getMemberExpressionResult(
declaration.init,
_programMemory
)
proms.push(prom)
_programMemory = addItemToMemory(
_programMemory,
variableName,
promisifyMemoryItem({
type: 'userVal',
value: prom,
__meta,
})
)
} else if (declaration.init.type === 'CallExpression') {
const result = executeCallExpression(
const prom = executeCallExpression(
_programMemory,
declaration.init,
engineCommandManager,
previousPathToNode
)
_programMemory.root[variableName] =
result?.type === 'sketchGroup' || result?.type === 'extrudeGroup'
? result
: {
type: 'userVal',
value: result,
__meta,
}
proms.push(prom)
_programMemory = addItemToMemory(
_programMemory,
variableName,
prom.then((a) => {
return a?.type === 'sketchGroup' || a?.type === 'extrudeGroup'
? a
: {
type: 'userVal',
value: a,
__meta,
}
})
)
} else {
throw new Error(
'Unsupported declaration type: ' + declaration.init.type
@ -306,7 +480,7 @@ export const executor = (
if (arg.type === 'Literal') {
return arg.value
} else if (arg.type === 'Identifier') {
return _programMemory.root[arg.name].value
return _programMemory.root[arg.name]?.value
}
})
if ('show' === functionName) {
@ -320,13 +494,17 @@ export const executor = (
}
} else if (statement.type === 'ReturnStatement') {
if (statement.argument.type === 'BinaryExpression') {
_programMemory.return = getBinaryExpressionResult(
const prom = getBinaryExpressionResult(
statement.argument,
_programMemory
_programMemory,
engineCommandManager
)
proms.push(prom)
_programMemory.return = await prom
}
}
})
await Promise.all(proms)
return _programMemory
}
@ -342,13 +520,14 @@ function getMemberExpressionResult(
const object: any =
expression.object.type === 'MemberExpression'
? getMemberExpressionResult(expression.object, programMemory)
: programMemory.root[expression.object.name].value
return object[propertyName]
: programMemory.root[expression.object.name]?.value
return object?.[propertyName]
}
function getBinaryExpressionResult(
async function getBinaryExpressionResult(
expression: BinaryExpression,
programMemory: ProgramMemory,
engineCommandManager: EngineCommandManager,
pipeInfo: {
isInPipe: boolean
previousResults: any[]
@ -366,8 +545,18 @@ function getBinaryExpressionResult(
...pipeInfo,
isInPipe: false,
}
const left = getBinaryPartResult(expression.left, programMemory, _pipeInfo)
const right = getBinaryPartResult(expression.right, programMemory, _pipeInfo)
const left = await getBinaryPartResult(
expression.left,
programMemory,
engineCommandManager,
_pipeInfo
)
const right = await getBinaryPartResult(
expression.right,
programMemory,
engineCommandManager,
_pipeInfo
)
if (expression.operator === '+') return left + right
if (expression.operator === '-') return left - right
if (expression.operator === '*') return left * right
@ -375,9 +564,10 @@ function getBinaryExpressionResult(
if (expression.operator === '%') return left % right
}
function getBinaryPartResult(
async function getBinaryPartResult(
part: BinaryPart,
programMemory: ProgramMemory,
engineCommandManager: EngineCommandManager,
pipeInfo: {
isInPipe: boolean
previousResults: any[]
@ -390,7 +580,7 @@ function getBinaryPartResult(
expressionIndex: 0,
body: [],
}
): any {
): Promise<any> {
const _pipeInfo = {
...pipeInfo,
isInPipe: false,
@ -400,15 +590,30 @@ function getBinaryPartResult(
} else if (part.type === 'Identifier') {
return programMemory.root[part.name].value
} else if (part.type === 'BinaryExpression') {
return getBinaryExpressionResult(part, programMemory, _pipeInfo)
const prom = getBinaryExpressionResult(
part,
programMemory,
engineCommandManager,
_pipeInfo
)
const result = await prom
return result
} else if (part.type === 'CallExpression') {
return executeCallExpression(programMemory, part, [], _pipeInfo)
const result = await executeCallExpression(
programMemory,
part,
engineCommandManager,
[],
_pipeInfo
)
return result
}
}
function getUnaryExpressionResult(
async function getUnaryExpressionResult(
expression: UnaryExpression,
programMemory: ProgramMemory,
engineCommandManager: EngineCommandManager,
pipeInfo: {
isInPipe: boolean
previousResults: any[]
@ -422,50 +627,64 @@ function getUnaryExpressionResult(
body: [],
}
) {
return -getBinaryPartResult(expression.argument, programMemory, {
...pipeInfo,
isInPipe: false,
})
return -(await getBinaryPartResult(
expression.argument,
programMemory,
engineCommandManager,
{
...pipeInfo,
isInPipe: false,
}
))
}
function getPipeExpressionResult(
async function getPipeExpressionResult(
expression: PipeExpression,
programMemory: ProgramMemory,
engineCommandManager: EngineCommandManager,
previousPathToNode: PathToNode = []
) {
const executedBody = executePipeBody(
const executedBody = await executePipeBody(
expression.body,
programMemory,
engineCommandManager,
previousPathToNode
)
const result = executedBody[executedBody.length - 1]
return result
}
function executePipeBody(
async function executePipeBody(
body: PipeExpression['body'],
programMemory: ProgramMemory,
engineCommandManager: EngineCommandManager,
previousPathToNode: PathToNode = [],
expressionIndex = 0,
previousResults: any[] = []
): any[] {
): Promise<any[]> {
if (expressionIndex === body.length) {
return previousResults
}
const expression = body[expressionIndex]
if (expression.type === 'BinaryExpression') {
const result = getBinaryExpressionResult(expression, programMemory)
const result = await getBinaryExpressionResult(
expression,
programMemory,
engineCommandManager
)
return executePipeBody(
body,
programMemory,
engineCommandManager,
previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
} else if (expression.type === 'CallExpression') {
return executeCallExpression(
return await executeCallExpression(
programMemory,
expression,
engineCommandManager,
previousPathToNode,
{
isInPipe: true,
@ -479,9 +698,10 @@ function executePipeBody(
throw new Error('Invalid pipe expression')
}
function executeObjectExpression(
async function executeObjectExpression(
_programMemory: ProgramMemory,
objExp: ObjectExpression,
engineCommandManager: EngineCommandManager,
pipeInfo: {
isInPipe: boolean
previousResults: any[]
@ -500,43 +720,67 @@ function executeObjectExpression(
isInPipe: false,
}
const obj: { [key: string]: any } = {}
objExp.properties.forEach((property) => {
const proms: Promise<any>[] = []
objExp.properties.forEach(async (property) => {
if (property.type === 'ObjectProperty') {
if (property.value.type === 'Literal') {
obj[property.key.name] = property.value.value
} else if (property.value.type === 'BinaryExpression') {
obj[property.key.name] = getBinaryExpressionResult(
const prom = getBinaryExpressionResult(
property.value,
_programMemory,
engineCommandManager,
_pipeInfo
)
proms.push(prom)
obj[property.key.name] = await prom
} else if (property.value.type === 'PipeExpression') {
obj[property.key.name] = getPipeExpressionResult(
const prom = getPipeExpressionResult(
property.value,
_programMemory
_programMemory,
engineCommandManager
)
proms.push(prom)
obj[property.key.name] = await prom
} else if (property.value.type === 'Identifier') {
obj[property.key.name] = _programMemory.root[property.value.name].value
obj[property.key.name] = (
await getMemoryItem(_programMemory, property.value.name)
).value
} else if (property.value.type === 'ObjectExpression') {
obj[property.key.name] = executeObjectExpression(
_programMemory,
property.value
)
} else if (property.value.type === 'ArrayExpression') {
const result = executeArrayExpression(_programMemory, property.value)
obj[property.key.name] = result
} else if (property.value.type === 'CallExpression') {
obj[property.key.name] = executeCallExpression(
const prom = executeObjectExpression(
_programMemory,
property.value,
engineCommandManager
)
proms.push(prom)
obj[property.key.name] = await prom
} else if (property.value.type === 'ArrayExpression') {
const prom = executeArrayExpression(
_programMemory,
property.value,
engineCommandManager
)
proms.push(prom)
obj[property.key.name] = await prom
} else if (property.value.type === 'CallExpression') {
const prom = executeCallExpression(
_programMemory,
property.value,
engineCommandManager,
[],
_pipeInfo
)
proms.push(prom)
const result = await prom
obj[property.key.name] = result
} else if (property.value.type === 'UnaryExpression') {
obj[property.key.name] = getUnaryExpressionResult(
const prom = getUnaryExpressionResult(
property.value,
_programMemory
_programMemory,
engineCommandManager
)
proms.push(prom)
obj[property.key.name] = await prom
} else {
throw new Error(
`Unexpected property type ${property.value.type} in object expression`
@ -548,12 +792,14 @@ function executeObjectExpression(
)
}
})
await Promise.all(proms)
return obj
}
function executeArrayExpression(
async function executeArrayExpression(
_programMemory: ProgramMemory,
arrExp: ArrayExpression,
engineCommandManager: EngineCommandManager,
pipeInfo: {
isInPipe: boolean
previousResults: any[]
@ -571,36 +817,50 @@ function executeArrayExpression(
...pipeInfo,
isInPipe: false,
}
return arrExp.elements.map((el) => {
if (el.type === 'Literal') {
return el.value
} else if (el.type === 'Identifier') {
return _programMemory.root?.[el.name]?.value
} else if (el.type === 'BinaryExpression') {
return getBinaryExpressionResult(el, _programMemory, _pipeInfo)
} else if (el.type === 'ObjectExpression') {
return executeObjectExpression(_programMemory, el)
} else if (el.type === 'CallExpression') {
const result: any = executeCallExpression(
_programMemory,
el,
[],
_pipeInfo
)
return result
} else if (el.type === 'UnaryExpression') {
return getUnaryExpressionResult(el, _programMemory, {
...pipeInfo,
isInPipe: false,
})
}
throw new Error('Invalid argument type')
})
return await Promise.all(
arrExp.elements.map((el) => {
if (el.type === 'Literal') {
return el.value
} else if (el.type === 'Identifier') {
return _programMemory.root?.[el.name]?.value
} else if (el.type === 'BinaryExpression') {
return getBinaryExpressionResult(
el,
_programMemory,
engineCommandManager,
_pipeInfo
)
} else if (el.type === 'ObjectExpression') {
return executeObjectExpression(_programMemory, el, engineCommandManager)
} else if (el.type === 'CallExpression') {
const result: any = executeCallExpression(
_programMemory,
el,
engineCommandManager,
[],
_pipeInfo
)
return result
} else if (el.type === 'UnaryExpression') {
return getUnaryExpressionResult(
el,
_programMemory,
engineCommandManager,
{
...pipeInfo,
isInPipe: false,
}
)
}
throw new Error('Invalid argument type')
})
)
}
function executeCallExpression(
async function executeCallExpression(
programMemory: ProgramMemory,
expression: CallExpression,
engineCommandManager: EngineCommandManager,
previousPathToNode: PathToNode = [],
pipeInfo: {
isInPipe: boolean
@ -627,59 +887,87 @@ function executeCallExpression(
...pipeInfo,
isInPipe: false,
}
const fnArgs = expression?.arguments?.map((arg) => {
if (arg.type === 'Literal') {
return arg.value
} else if (arg.type === 'Identifier') {
const temp = programMemory.root[arg.name]
return temp?.type === 'userVal' ? temp.value : temp
} else if (arg.type === 'PipeSubstitution') {
return previousResults[expressionIndex - 1]
} else if (arg.type === 'ArrayExpression') {
return executeArrayExpression(programMemory, arg, pipeInfo)
} else if (arg.type === 'CallExpression') {
const result: any = executeCallExpression(
programMemory,
arg,
previousPathToNode,
_pipeInfo
)
return result
} else if (arg.type === 'ObjectExpression') {
return executeObjectExpression(programMemory, arg, _pipeInfo)
} else if (arg.type === 'UnaryExpression') {
return getUnaryExpressionResult(arg, programMemory, _pipeInfo)
} else if (arg.type === 'BinaryExpression') {
return getBinaryExpressionResult(arg, programMemory, _pipeInfo)
}
throw new Error('Invalid argument type in function call')
})
const fnArgs = await Promise.all(
expression?.arguments?.map(async (arg) => {
if (arg.type === 'Literal') {
return arg.value
} else if (arg.type === 'Identifier') {
await new Promise((r) => setTimeout(r)) // push into next even loop, but also probably should fix this
const temp = await getMemoryItem(programMemory, arg.name)
return temp?.type === 'userVal' ? temp.value : temp
} else if (arg.type === 'PipeSubstitution') {
return previousResults[expressionIndex - 1]
} else if (arg.type === 'ArrayExpression') {
return await executeArrayExpression(
programMemory,
arg,
engineCommandManager,
pipeInfo
)
} else if (arg.type === 'CallExpression') {
const result: any = await executeCallExpression(
programMemory,
arg,
engineCommandManager,
previousPathToNode,
_pipeInfo
)
return result
} else if (arg.type === 'ObjectExpression') {
return await executeObjectExpression(
programMemory,
arg,
engineCommandManager,
_pipeInfo
)
} else if (arg.type === 'UnaryExpression') {
return getUnaryExpressionResult(
arg,
programMemory,
engineCommandManager,
_pipeInfo
)
} else if (arg.type === 'BinaryExpression') {
return getBinaryExpressionResult(
arg,
programMemory,
engineCommandManager,
_pipeInfo
)
}
throw new Error('Invalid argument type in function call')
})
)
if (functionName in internalFns) {
const fnNameWithSketchOrExtrude = functionName as InternalFnNames
const result = internalFns[fnNameWithSketchOrExtrude](
const result = await internalFns[fnNameWithSketchOrExtrude](
{
programMemory,
sourceRange: sourceRangeOverride || [expression.start, expression.end],
engineCommandManager,
code: JSON.stringify(expression),
},
fnArgs[0],
fnArgs[1],
fnArgs[2]
)
return isInPipe
? executePipeBody(
? await executePipeBody(
body,
programMemory,
engineCommandManager,
previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
: result
}
const result = programMemory.root[functionName].value(...fnArgs)
const result = await programMemory.root[functionName].value(...fnArgs)
return isInPipe
? executePipeBody(
? await executePipeBody(
body,
programMemory,
engineCommandManager,
previousPathToNode,
expressionIndex + 1,
[...previousResults, result]

View File

@ -16,7 +16,7 @@ import {
import { recast } from './recast'
import { lexer } from './tokeniser'
import { initPromise } from './rust'
import { executor } from './executor'
import { executor } from '../lib/testHelpers'
beforeAll(() => initPromise)
@ -194,9 +194,9 @@ const part001 = startSketchAt([-1.2, 4.83])
const yo = 5 + 6
const yo2 = hmm([identifierGuy + 5])
show(part001)`
it('should move a value into a new variable', () => {
it('should move a value into a new variable', async () => {
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const programMemory = await executor(ast)
const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
@ -208,9 +208,9 @@ show(part001)`
expect(newCode).toContain(`const newVar = 100 + 100`)
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a value into a new variable', () => {
it('should move a value into a new variable', async () => {
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const programMemory = await executor(ast)
const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
@ -222,9 +222,9 @@ show(part001)`
expect(newCode).toContain(`const newVar = 2.8`)
expect(newCode).toContain(`line([newVar, 0], %)`)
})
it('should move a value into a new variable', () => {
it('should move a value into a new variable', async () => {
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const programMemory = await executor(ast)
const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable(
ast,
@ -236,9 +236,9 @@ show(part001)`
expect(newCode).toContain(`const newVar = def('yo')`)
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a value into a new variable', () => {
it('should move a value into a new variable', async () => {
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const programMemory = await executor(ast)
const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
@ -250,9 +250,9 @@ show(part001)`
expect(newCode).toContain(`const newVar = jkl('yo') + 2`)
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
})
it('should move a value into a new variable', () => {
it('should move a value into a new variable', async () => {
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const programMemory = await executor(ast)
const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,

View File

@ -7,7 +7,7 @@ import {
} from './queryAst'
import { lexer } from './tokeniser'
import { initPromise } from './rust'
import { executor } from './executor'
import { executor } from '../lib/testHelpers'
import {
createArrayExpression,
createCallExpression,
@ -19,7 +19,7 @@ import { recast } from './recast'
beforeAll(() => initPromise)
describe('findAllPreviousVariables', () => {
it('should find all previous variables', () => {
it('should find all previous variables', async () => {
const code = `const baseThick = 1
const armAngle = 60
@ -38,7 +38,7 @@ const variableBelowShouldNotBeIncluded = 3
show(part001)`
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const programMemory = await executor(ast)
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
ast,

View File

@ -64,7 +64,7 @@ log(5, myVar)`
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('sketch declaration', () => {
it('recast sketch declaration', () => {
let code = `const mySketch = startSketchAt([0, 0])
|> lineTo({ to: [0, 1], tag: "myPath" }, %)
|> lineTo([1, 1], %)

View File

@ -0,0 +1,331 @@
import { SourceRange } from '../executor'
import { Selections } from '../../useStore'
interface ResultCommand {
type: 'result'
data: any
}
interface PendingCommand {
type: 'pending'
promise: Promise<any>
resolve: (val: any) => void
}
export interface ArtifactMap {
[key: string]: ResultCommand | PendingCommand
}
export interface SourceRangeMap {
[key: string]: SourceRange
}
interface SelectionsArgs {
id: string
type: Selections['codeBasedSelections'][number]['type']
}
interface CursorSelectionsArgs {
otherSelections: Selections['otherSelections']
idBasedSelections: { type: string; id: string }[]
}
// TODO these types should be in the openApi spec, and therefore in @kittycad/lib
interface MouseStuff {
interaction: 'rotate'
window: {
x: number
y: number
}
}
type uuid = string
interface XYZ {
x: number
y: number
z: number
}
interface EngineCommand {
type: 'ModelingCmdReq'
cmd: {
StartPath?: {}
MovePathPen?: {
path: uuid
to: XYZ
}
ExtendPath?: {
path: uuid
segment: {
Line: {
end: XYZ
}
}
}
ClosePath?: {
path_id: uuid
}
Extrude?: {
target: uuid
distance: number
cap: boolean
}
CameraDragMove?: MouseStuff
CameraDragStart?: MouseStuff
CameraDragEnd?: MouseStuff
}
cmd_id: uuid
file_id: uuid
}
export class EngineCommandManager {
artifactMap: ArtifactMap = {}
sourceRangeMap: SourceRangeMap = {}
socket?: WebSocket
pc?: RTCPeerConnection
onHoverCallback: (id?: string) => void = () => {}
onClickCallback: (selection: SelectionsArgs) => void = () => {}
onCursorsSelectedCallback: (selections: CursorSelectionsArgs) => void =
() => {}
constructor(setMediaStream: (stream: MediaStream) => void) {
const url = 'wss://api.dev.kittycad.io/ws/modeling/commands'
this.socket = new WebSocket(url)
this.pc = new RTCPeerConnection()
this.socket.addEventListener('open', (event) => {
console.log('Connected to websocket, waiting for ICE servers')
})
this.socket.addEventListener('close', (event) => {
console.log('websocket connection closed')
})
this.socket.addEventListener('error', (event) => {
console.log('websocket connection error')
})
this?.socket?.addEventListener('message', (event) => {
if (!this.pc || !this.socket) return
//console.log('Message from server ', event.data);
if (event.data instanceof Blob) {
const reader = new FileReader()
reader.onload = () => {
//console.log("Result: " + reader.result);
}
reader.readAsText(event.data)
} else if (
typeof event.data === 'string' &&
event.data.toLocaleLowerCase().startsWith('error')
) {
console.warn('something went wrong: ', event.data)
} else {
const message = JSON.parse(event.data)
if (message.type === 'SDPAnswer') {
this.pc.setRemoteDescription(
new RTCSessionDescription(message.answer)
)
} else if (message.type === 'IceServerInfo') {
console.log('received IceServerInfo')
this.pc.setConfiguration({
iceServers: message.ice_servers,
})
this.pc.addEventListener('track', (event) => {
console.log('received track', event)
const mediaStream = event.streams[0]
setMediaStream(mediaStream)
})
this.pc.addEventListener('connectionstatechange', (e) =>
console.log(this?.pc?.iceConnectionState)
)
this.pc.addEventListener('icecandidate', (event) => {
if (!this.pc || !this.socket) return
if (event.candidate === null) {
console.log('sent SDPOffer')
this.socket.send(
JSON.stringify({
type: 'SDPOffer',
offer: this.pc.localDescription,
})
)
}
})
// Offer to receive 1 video track
this.pc.addTransceiver('video', {
direction: 'sendrecv',
})
this.pc
.createOffer()
.then(async (descriptionInit) => {
await this?.pc?.setLocalDescription(descriptionInit)
console.log('sent SDPOffer begin')
const msg = JSON.stringify({
type: 'SDPOffer',
offer: this.pc?.localDescription,
})
this.socket?.send(msg)
})
.catch(console.log)
}
// TODO talk to the gang about this
// the following message types are made up
// and are placeholders
else if (message.type === 'hover') {
this.onHoverCallback(message.id)
} else if (message.type === 'click') {
this.onClickCallback(message)
} else {
console.log('other message', message)
}
}
})
}
startNewSession() {
this.artifactMap = {}
this.sourceRangeMap = {}
// socket.on('command', ({ id, data }: any) => {
// const command = this.artifactMap[id]
// const geos: any = {}
// if (data.geo) {
// geos.position = data.position
// geos.rotation = data.rotation
// geos.originalId = data.originalId
// try {
// geos.geo = stlLoader.parse(data.geo)
// } catch (e) {}
// } else {
// Object.entries(data).forEach(([key, val]: [string, any]) => {
// let bufferGeometry = new BufferGeometry()
// try {
// bufferGeometry = stlLoader.parse(val)
// } catch (e) {
// console.log('val', val)
// }
// geos[key] = bufferGeometry
// })
// }
// if (command && command.type === 'pending') {
// const resolve = command.resolve
// this.artifactMap[id] = {
// type: 'result',
// data: geos,
// }
// resolve({
// id,
// geo: geos,
// })
// } else {
// this.artifactMap[id] = {
// type: 'result',
// data: geos,
// }
// }
// })
}
endSession() {
// this.socket?.close()
// socket.off('command')
}
onHover(callback: (id?: string) => void) {
// It's when the user hovers over a part in the 3d scene, and so the engine should tell the
// frontend about that (with it's id) so that the FE can highlight code associated with that id
this.onHoverCallback = callback
}
onClick(callback: (selection: SelectionsArgs) => void) {
// TODO talk to the gang about this
// It's when the user clicks on a part in the 3d scene, and so the engine should tell the
// frontend about that (with it's id) so that the FE can put the user's cursor on the right
// line of code
this.onClickCallback = callback
}
cusorsSelected(selections: {
otherSelections: Selections['otherSelections']
idBasedSelections: { type: string; id: string }[]
}) {
// TODO talk to the gang about this
// Really idBasedSelections is the only part that's relevant to the server, but it's when
// the user puts their cursor over a line of code, and there is a engine-id associated with
// it, so we want to tell the engine to change it's color or something
if (this.socket?.readyState === 0) {
console.log('socket not open')
return
}
console.log('sending cursorsSelected')
this.socket?.send(
JSON.stringify({ command: 'cursorsSelected', body: selections })
)
}
sendSceneCommand(command: EngineCommand) {
if (this.socket?.readyState === 0) {
console.log('socket not ready')
return
}
this.socket?.send(JSON.stringify(command))
}
sendModellingCommand({
id,
params,
range,
command,
}: {
id: string
params: any
range: SourceRange
command: EngineCommand
}): Promise<any> {
if (!this.socket?.OPEN) {
console.log('socket not open')
return new Promise(() => {})
}
this.sourceRangeMap[id] = range
// return early if the socket is still in CONNECTING state
if (this.socket?.readyState === 0) {
console.log('socket not ready')
return new Promise(() => {})
}
console.log('sending command', {
id,
data: params,
command,
})
this.socket?.send(JSON.stringify(command))
let resolve: (val: any) => void = () => {}
const promise = new Promise((_resolve, reject) => {
resolve = _resolve
})
this.artifactMap[id] = {
type: 'pending',
promise,
resolve,
}
return promise
}
commandResult(id: string): Promise<any> {
const command = this.artifactMap[id]
if (!command) {
throw new Error('No command found')
}
if (command.type === 'result') {
return command.data
}
return command.promise
}
async waitForAllCommands(): Promise<{
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
}> {
const pendingCommands = Object.values(this.artifactMap).filter(
({ type }) => type === 'pending'
) as PendingCommand[]
const proms = pendingCommands.map(({ promise }) => promise)
await Promise.all(proms)
return {
artifactMap: this.artifactMap,
sourceRangeMap: this.sourceRangeMap,
}
}
}

View File

@ -1,3 +1,4 @@
import { v4 as uuidv4 } from 'uuid'
import { InternalFn } from './stdTypes'
import {
ExtrudeGroup,
@ -6,68 +7,56 @@ import {
Position,
Rotation,
} from '../executor'
import { Quaternion, Vector3 } from 'three'
import { clockwiseSign } from './std'
import { extrudeGeo } from '../engine'
import { generateUuidFromHashSeed } from '../../lib/uuid'
export const extrude: InternalFn = (
{ sourceRange },
{ sourceRange, engineCommandManager, code },
length: number,
sketchVal: SketchGroup
): ExtrudeGroup => {
const getSketchGeo = (sketchVal: SketchGroup): SketchGroup => {
return sketchVal
}
const sketch = getSketchGeo(sketchVal)
const sketch = sketchVal
const { position, rotation } = sketchVal
const id = generateUuidFromHashSeed(
JSON.stringify({
code,
sourceRange,
date: {
length,
sketchVal,
},
})
)
const extrudeSurfaces: ExtrudeSurface[] = []
const extrusionDirection = clockwiseSign(sketch.value.map((line) => line.to))
sketch.value.map((line, index) => {
if (line.type === 'toPoint') {
let from: [number, number] = line.from
const to = line.to
const {
geo,
position: facePosition,
rotation: faceRotation,
} = extrudeGeo({
from: [from[0], from[1], 0],
to: [to[0], to[1], 0],
engineCommandManager.sendModellingCommand({
id,
params: [
{
length,
extrusionDirection,
})
const groupQuaternion = new Quaternion(...rotation)
const currentWallQuat = new Quaternion(...faceRotation)
const unifiedQuit = new Quaternion().multiplyQuaternions(
currentWallQuat,
groupQuaternion.clone().invert()
)
const facePositionVector = new Vector3(...facePosition)
facePositionVector.applyQuaternion(groupQuaternion.clone())
const unifiedPosition = new Vector3().addVectors(
facePositionVector,
new Vector3(...position)
)
const surface: ExtrudeSurface = {
type: 'extrudePlane',
position: unifiedPosition.toArray() as Position,
rotation: unifiedQuit.toArray() as Rotation,
__geoMeta: {
geo,
sourceRange: line.__geoMeta.sourceRange,
pathToNode: line.__geoMeta.pathToNode,
},
}
line.name && (surface.name = line.name)
extrudeSurfaces.push(surface)
}
extrusionDirection: extrusionDirection,
},
],
range: sourceRange,
command: {
type: 'ModelingCmdReq',
cmd: {
Extrude: {
target: sketch.id,
distance: length,
cap: true,
}
},
cmd_id: id,
file_id: uuidv4(),
},
})
return {
type: 'extrudeGroup',
value: extrudeSurfaces,
value: extrudeSurfaces, // TODO, this is just an empty array now, should be deleted.
height: length,
position,
rotation,
@ -92,7 +81,7 @@ export const getExtrudeWallTransform: InternalFn = (
position: Position
quaternion: Rotation
} => {
const path = extrudeGroup.value.find((path) => path.name === pathName)
const path = extrudeGroup?.value.find((path) => path.name === pathName)
if (!path) throw new Error(`Could not find path with name ${pathName}`)
return {
position: path.position,

View File

@ -9,7 +9,7 @@ import { lexer } from '../tokeniser'
import { abstractSyntaxTree } from '../abstractSyntaxTree'
import { getNodePathFromSourceRange } from '../queryAst'
import { recast } from '../recast'
import { executor } from '../executor'
import { executor } from '../../lib/testHelpers'
import { initPromise } from '../rust'
beforeAll(() => initPromise)
@ -96,7 +96,7 @@ describe('testing getXComponent', () => {
describe('testing changeSketchArguments', () => {
const lineToChange = 'lineTo([-1.59, -1.54], %)'
const lineAfterChange = 'lineTo([2, 3], %)'
test('changeSketchArguments', () => {
test('changeSketchArguments', async () => {
const genCode = (line: string) => `
const mySketch001 = startSketchAt([0, 0])
|> ${line}
@ -106,7 +106,7 @@ show(mySketch001)`
const code = genCode(lineToChange)
const expectedCode = genCode(lineAfterChange)
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const programMemory = await executor(ast)
const sourceStart = code.indexOf(lineToChange)
const { modifiedAst } = changeSketchArguments(
ast,
@ -135,7 +135,7 @@ show(mySketch001)`
describe('testing addNewSketchLn', () => {
const lineToChange = 'lineTo([-1.59, -1.54], %)'
const lineAfterChange = 'lineTo([2, 3], %)'
test('addNewSketchLn', () => {
test('addNewSketchLn', async () => {
const code = `
const mySketch001 = startSketchAt([0, 0])
|> rx(45, %)
@ -143,7 +143,7 @@ const mySketch001 = startSketchAt([0, 0])
|> lineTo([0.46, -5.82], %)
show(mySketch001)`
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const programMemory = await executor(ast)
const sourceStart = code.indexOf(lineToChange)
const { modifiedAst } = addNewSketchLn({
node: ast,
@ -170,7 +170,7 @@ show(mySketch001)`
})
describe('testing addTagForSketchOnFace', () => {
it('needs to be in it', () => {
it('needs to be in it', async () => {
const originalLine = 'lineTo([-1.59, -1.54], %)'
const genCode = (line: string) => `
const mySketch001 = startSketchAt([0, 0])
@ -180,7 +180,7 @@ describe('testing addTagForSketchOnFace', () => {
show(mySketch001)`
const code = genCode(originalLine)
const ast = abstractSyntaxTree(lexer(code))
const programMemory = executor(ast)
const programMemory = await executor(ast)
const sourceStart = code.indexOf(originalLine)
const sourceRange: [number, number] = [
sourceStart,

View File

@ -19,9 +19,10 @@ import {
getNodeFromPathCurry,
getNodePathFromSourceRange,
} from '../queryAst'
import { lineGeo, sketchBaseGeo } from '../engine'
import { GuiModes, toolTips, TooTip } from '../../useStore'
import { splitPathAtPipeExpression } from '../modifyAst'
import { generateUuidFromHashSeed } from '../../lib/uuid'
import { v4 as uuidv4 } from 'uuid'
import {
SketchLineHelper,
@ -102,9 +103,21 @@ export function createFirstArg(
throw new Error('all sketch line types should have been covered')
}
type LineData = {
from: [number, number, number]
to: [number, number, number]
}
function makeId(seed: string | any) {
if (typeof seed === 'string') {
return generateUuidFromHashSeed(seed)
}
return generateUuidFromHashSeed(JSON.stringify(seed))
}
export const lineTo: SketchLineHelper = {
fn: (
{ sourceRange },
{ sourceRange, engineCommandManager, code },
data:
| [number, number]
| {
@ -118,25 +131,35 @@ export const lineTo: SketchLineHelper = {
const sketchGroup = { ...previousSketch }
const from = getCoordsFromPaths(sketchGroup, sketchGroup.value.length - 1)
const to = 'to' in data ? data.to : data
const geo = lineGeo({
const lineData: LineData = {
from: [...from, 0],
to: [...to, 0],
}
const id = makeId({
code,
sourceRange,
data,
})
// engineCommandManager.sendModellingCommand({
// id,
// params: [lineData, previousSketch],
// range: sourceRange,
// })
const currentPath: Path = {
type: 'toPoint',
to,
from,
__geoMeta: {
sourceRange,
id,
pathToNode: [], // TODO
geos: [
{
type: 'line',
geo: geo.line,
},
{
type: 'lineEnd',
geo: geo.tip,
},
],
},
@ -217,7 +240,7 @@ export const lineTo: SketchLineHelper = {
export const line: SketchLineHelper = {
fn: (
{ sourceRange },
{ sourceRange, engineCommandManager, code },
data:
| [number, number]
| 'default'
@ -239,25 +262,53 @@ export const line: SketchLineHelper = {
}
const to: [number, number] = [from[0] + args[0], from[1] + args[1]]
const geo = lineGeo({
const lineData: LineData = {
from: [...from, 0],
to: [...to, 0],
}
const id = makeId({
code,
sourceRange,
data,
})
engineCommandManager.sendModellingCommand({
id,
params: [lineData, previousSketch],
range: sourceRange,
command: {
type: 'ModelingCmdReq',
cmd: {
ExtendPath: {
path: sketchGroup.id,
segment: {
Line: {
end: {
x: lineData.to[0],
y: lineData.to[1],
z: 0,
},
},
},
},
},
cmd_id: id,
file_id: uuidv4(),
}
})
const currentPath: Path = {
type: 'toPoint',
to,
from,
__geoMeta: {
id,
sourceRange,
pathToNode: [], // TODO
geos: [
{
type: 'line',
geo: geo.line,
},
{
type: 'lineEnd',
geo: geo.tip,
},
],
},
@ -623,7 +674,7 @@ export const yLine: SketchLineHelper = {
export const angledLine: SketchLineHelper = {
fn: (
{ sourceRange, programMemory },
{ sourceRange, engineCommandManager, code },
data:
| [number, number]
| {
@ -641,25 +692,34 @@ export const angledLine: SketchLineHelper = {
from[0] + length * Math.cos((angle * Math.PI) / 180),
from[1] + length * Math.sin((angle * Math.PI) / 180),
]
const geo = lineGeo({
const lineData: LineData = {
from: [...from, 0],
to: [...to, 0],
}
const id = makeId({
code,
sourceRange,
data,
})
// engineCommandManager.sendModellingCommand({
// id,
// params: [lineData, previousSketch],
// range: sourceRange,
// })
const currentPath: Path = {
type: 'toPoint',
to,
from,
__geoMeta: {
id,
sourceRange,
pathToNode: [], // TODO
geos: [
{
type: 'line',
geo: geo.line,
},
{
type: 'lineEnd',
geo: geo.tip,
},
],
},
@ -740,7 +800,7 @@ export const angledLine: SketchLineHelper = {
export const angledLineOfXLength: SketchLineHelper = {
fn: (
{ sourceRange, programMemory },
{ sourceRange, programMemory, engineCommandManager, code },
data:
| [number, number]
| {
@ -754,7 +814,7 @@ export const angledLineOfXLength: SketchLineHelper = {
const [angle, length, tag] =
'angle' in data ? [data.angle, data.length, data.tag] : data
return line.fn(
{ sourceRange, programMemory },
{ sourceRange, programMemory, engineCommandManager, code },
{ to: getYComponent(angle, length), tag },
previousSketch
)
@ -833,7 +893,7 @@ export const angledLineOfXLength: SketchLineHelper = {
export const angledLineOfYLength: SketchLineHelper = {
fn: (
{ sourceRange, programMemory },
{ sourceRange, programMemory, engineCommandManager, code },
data:
| [number, number]
| {
@ -847,7 +907,7 @@ export const angledLineOfYLength: SketchLineHelper = {
const [angle, length, tag] =
'angle' in data ? [data.angle, data.length, data.tag] : data
return line.fn(
{ sourceRange, programMemory },
{ sourceRange, programMemory, engineCommandManager, code },
{ to: getXComponent(angle, length), tag },
previousSketch
)
@ -927,7 +987,7 @@ export const angledLineOfYLength: SketchLineHelper = {
export const angledLineToX: SketchLineHelper = {
fn: (
{ sourceRange, programMemory },
{ sourceRange, programMemory, engineCommandManager, code },
data:
| [number, number]
| {
@ -948,7 +1008,7 @@ export const angledLineToX: SketchLineHelper = {
const yComponent = xComponent * Math.tan((angle * Math.PI) / 180)
const yTo = from[1] + yComponent
return lineTo.fn(
{ sourceRange, programMemory },
{ sourceRange, programMemory, engineCommandManager, code },
{ to: [xTo, yTo], tag },
previousSketch
)
@ -1023,7 +1083,7 @@ export const angledLineToX: SketchLineHelper = {
export const angledLineToY: SketchLineHelper = {
fn: (
{ sourceRange, programMemory },
{ sourceRange, programMemory, engineCommandManager, code },
data:
| [number, number]
| {
@ -1044,7 +1104,7 @@ export const angledLineToY: SketchLineHelper = {
const xComponent = yComponent / Math.tan((angle * Math.PI) / 180)
const xTo = from[0] + xComponent
return lineTo.fn(
{ sourceRange, programMemory },
{ sourceRange, programMemory, engineCommandManager, code },
{ to: [xTo, yTo], tag },
previousSketch
)
@ -1120,7 +1180,7 @@ export const angledLineToY: SketchLineHelper = {
export const angledLineThatIntersects: SketchLineHelper = {
fn: (
{ sourceRange, programMemory },
{ sourceRange, programMemory, engineCommandManager, code },
data: {
angle: number
intersectTag: string
@ -1145,7 +1205,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
line2Angle: data.angle,
})
return lineTo.fn(
{ sourceRange, programMemory },
{ sourceRange, programMemory, engineCommandManager, code },
{ to, tag: data.tag },
previousSketch
)
@ -1513,32 +1573,53 @@ function addTagWithTo(
}
export const close: InternalFn = (
{ sourceRange },
{ sourceRange, engineCommandManager, code },
sketchGroup: SketchGroup
): SketchGroup => {
const from = getCoordsFromPaths(sketchGroup, sketchGroup.value.length - 1)
const to = sketchGroup.start
? sketchGroup.start.from
: getCoordsFromPaths(sketchGroup, 0)
const geo = lineGeo({
const lineData: LineData = {
from: [...from, 0],
to: [...to, 0],
}
const id = makeId({
code,
sourceRange,
data: sketchGroup,
})
engineCommandManager.sendModellingCommand({
id,
params: [lineData],
range: sourceRange,
command: {
type: 'ModelingCmdReq',
cmd: {
ClosePath: {
path_id: sketchGroup.id,
},
},
cmd_id: id,
file_id: uuidv4(),
}
})
const currentPath: Path = {
type: 'toPoint',
to,
from,
__geoMeta: {
id,
sourceRange,
pathToNode: [], // TODO
geos: [
{
type: 'line',
geo: geo.line,
},
{
type: 'lineEnd',
geo: geo.tip,
},
],
},
@ -1552,7 +1633,7 @@ export const close: InternalFn = (
}
export const startSketchAt: InternalFn = (
{ sourceRange, programMemory },
{ sourceRange, programMemory, engineCommandManager, code },
data:
| [number, number]
| 'default'
@ -1569,18 +1650,59 @@ export const startSketchAt: InternalFn = (
to = data
}
const geo = sketchBaseGeo({ to: [...to, 0] })
const lineData: { to: [number, number, number] } = {
to: [...to, 0],
}
const id = makeId({
code,
sourceRange,
data,
})
const pathId = makeId({
code,
sourceRange,
data,
isPath: true,
})
engineCommandManager.sendModellingCommand({
id: pathId,
params: [lineData],
range: sourceRange,
command: {
type: 'ModelingCmdReq',
cmd: {
StartPath: {},
},
cmd_id: pathId,
file_id: uuidv4(),
},
})
engineCommandManager.sendSceneCommand({
type: 'ModelingCmdReq',
cmd: {
MovePathPen: {
path: pathId,
to: {
x: lineData.to[0],
y: lineData.to[1],
z: 0,
},
},
},
cmd_id: id,
file_id: uuidv4(),
})
const currentPath: Path = {
type: 'base',
to,
from: to,
__geoMeta: {
id,
sourceRange,
pathToNode: [], // TODO
geos: [
{
type: 'sketchBase',
geo: geo.base,
},
],
},
@ -1594,6 +1716,7 @@ export const startSketchAt: InternalFn = (
value: [],
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
id: pathId,
__meta: [
{
sourceRange,

View File

@ -1,5 +1,5 @@
import { abstractSyntaxTree } from '../abstractSyntaxTree'
import { executor, SketchGroup } from '../executor'
import { SketchGroup } from '../executor'
import { lexer } from '../tokeniser'
import {
ConstraintType,
@ -10,11 +10,12 @@ import { recast } from '../recast'
import { initPromise } from '../rust'
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import { Selection } from '../../useStore'
import { executor } from '../../lib/testHelpers'
beforeAll(() => initPromise)
// testing helper function
function testingSwapSketchFnCall({
async function testingSwapSketchFnCall({
inputCode,
callToSwap,
constraintType,
@ -22,10 +23,10 @@ function testingSwapSketchFnCall({
inputCode: string
callToSwap: string
constraintType: ConstraintType
}): {
}): Promise<{
newCode: string
originalRange: [number, number]
} {
}> {
const startIndex = inputCode.indexOf(callToSwap)
const range: Selection = {
type: 'default',
@ -33,7 +34,7 @@ function testingSwapSketchFnCall({
}
const tokens = lexer(inputCode)
const ast = abstractSyntaxTree(tokens)
const programMemory = executor(ast)
const programMemory = await executor(ast)
const selections = {
codeBasedSelections: [range],
otherSelections: [],
@ -94,10 +95,10 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
`show(part001)`,
]
const bigExample = bigExampleArr.join('\n')
it('line with tag converts to xLine', () => {
it('line with tag converts to xLine', async () => {
const callToSwap = "line({ to: [-2.04, -0.7], tag: 'abc2' }, %)"
const expectedLine = "xLine({ length: -2.04, tag: 'abc2' }, %)"
const { newCode, originalRange } = testingSwapSketchFnCall({
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap,
constraintType: 'horizontal',
@ -106,10 +107,10 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('line w/o tag converts to xLine', () => {
it('line w/o tag converts to xLine', async () => {
const callToSwap = 'line([0.73, -0.75], %)'
const expectedLine = 'xLine(0.73, %)'
const { newCode, originalRange } = testingSwapSketchFnCall({
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap,
constraintType: 'horizontal',
@ -118,8 +119,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('lineTo with tag converts to xLineTo', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('lineTo with tag converts to xLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: "lineTo({ to: [1, 1], tag: 'abc1' }, %)",
constraintType: 'horizontal',
@ -129,8 +130,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('lineTo w/o tag converts to xLineTo', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('lineTo w/o tag converts to xLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: 'lineTo([2.55, 3.58], %)',
constraintType: 'horizontal',
@ -140,8 +141,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLine with tag converts to xLine', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLine with tag converts to xLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: [
`angledLine({`,
@ -157,8 +158,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLine w/o tag converts to xLine', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLine w/o tag converts to xLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: 'angledLine([63, 1.38], %)',
constraintType: 'horizontal',
@ -168,8 +169,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineOfXLength with tag converts to xLine', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineOfXLength with tag converts to xLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: [
`angledLineOfXLength({`,
@ -186,8 +187,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineOfXLength w/o tag converts to xLine', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineOfXLength w/o tag converts to xLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: 'angledLineOfXLength([319, 1.15], %)',
constraintType: 'horizontal',
@ -197,8 +198,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineOfYLength with tag converts to yLine', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineOfYLength with tag converts to yLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: [
`angledLineOfYLength({`,
@ -214,8 +215,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineOfYLength w/o tag converts to yLine', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineOfYLength w/o tag converts to yLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: 'angledLineOfYLength([50, 1.35], %)',
constraintType: 'vertical',
@ -225,8 +226,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineToX with tag converts to xLineTo', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineToX with tag converts to xLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: "angledLineToX({ angle: 55, to: -2.89, tag: 'abc6' }, %)",
constraintType: 'horizontal',
@ -236,8 +237,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineToX w/o tag converts to xLineTo', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineToX w/o tag converts to xLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: 'angledLineToX([291, 6.66], %)',
constraintType: 'horizontal',
@ -247,8 +248,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineToY with tag converts to yLineTo', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineToY with tag converts to yLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: "angledLineToY({ angle: 330, to: 2.53, tag: 'abc7' }, %)",
constraintType: 'vertical',
@ -258,8 +259,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo', () => {
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineToY w/o tag converts to yLineTo', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineToY w/o tag converts to yLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: 'angledLineToY([228, 2.14], %)',
constraintType: 'vertical',
@ -294,8 +295,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
`show(part001)`,
]
const varExample = variablesExampleArr.join('\n')
it('line keeps variable when converted to xLine', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('line keeps variable when converted to xLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: varExample,
callToSwap: 'line([lineX, 2.13], %)',
constraintType: 'horizontal',
@ -305,8 +306,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('lineTo keeps variable when converted to xLineTo', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('lineTo keeps variable when converted to xLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: varExample,
callToSwap: 'lineTo([lineToX, 2.85], %)',
constraintType: 'horizontal',
@ -316,8 +317,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineOfXLength keeps variable when converted to xLine', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineOfXLength keeps variable when converted to xLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: varExample,
callToSwap: 'angledLineOfXLength([329, angledLineOfXLengthX], %)',
constraintType: 'horizontal',
@ -327,8 +328,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineOfYLength keeps variable when converted to yLine', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineOfYLength keeps variable when converted to yLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: varExample,
callToSwap: 'angledLineOfYLength([222, angledLineOfYLengthY], %)',
constraintType: 'vertical',
@ -338,8 +339,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineToX keeps variable when converted to xLineTo', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineToX keeps variable when converted to xLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: varExample,
callToSwap: 'angledLineToX([330, angledLineToXx], %)',
constraintType: 'horizontal',
@ -349,8 +350,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('angledLineToY keeps variable when converted to yLineTo', () => {
const { newCode, originalRange } = testingSwapSketchFnCall({
it('angledLineToY keeps variable when converted to yLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: varExample,
callToSwap: 'angledLineToY([217, angledLineToYy], %)',
constraintType: 'vertical',
@ -361,14 +362,14 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
})
it('trying to convert angledLineToY to xLineTo should not work because of the variable', () => {
it('trying to convert angledLineToY to xLineTo should not work because of the variable', async () => {
const illegalConvert = () =>
testingSwapSketchFnCall({
inputCode: varExample,
callToSwap: 'angledLineToY([217, angledLineToYy], %)',
constraintType: 'horizontal',
})
expect(illegalConvert).toThrowError()
await expect(illegalConvert).rejects.toThrowError('no callback helper')
})
})
@ -380,8 +381,8 @@ const part001 = startSketchAt([0, 0.04]) // segment-in-start
|> line([2.14, 1.35], %) // normal-segment
|> xLine(3.54, %)
show(part001)`
it('normal case works', () => {
const programMemory = executor(abstractSyntaxTree(lexer(code)))
it('normal case works', async () => {
const programMemory = await executor(abstractSyntaxTree(lexer(code)))
const index = code.indexOf('// normal-segment') - 7
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
programMemory.root['part001'] as SketchGroup,
@ -393,8 +394,8 @@ show(part001)`
from: [3.48, 0.44],
})
})
it('verify it works when the segment is in the `start` property', () => {
const programMemory = executor(abstractSyntaxTree(lexer(code)))
it('verify it works when the segment is in the `start` property', async () => {
const programMemory = await executor(abstractSyntaxTree(lexer(code)))
const index = code.indexOf('// segment-in-start') - 7
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
programMemory.root['part001'] as SketchGroup,

View File

@ -41,7 +41,7 @@ export const segLen: InternalFn = (
segName: string,
sketchGroup: SketchGroup
): number => {
const line = sketchGroup?.value.find((seg) => seg.name === segName)
const line = sketchGroup?.value.find((seg) => seg?.name === segName)
// maybe this should throw, but the language doesn't have a way to handle errors yet
if (!line) return 0
@ -64,8 +64,8 @@ export const segAng: InternalFn = (
function segEndFactory(which: 'x' | 'y'): InternalFn {
return (_, segName: string, sketchGroup: SketchGroup): number => {
const line =
sketchGroup.start?.name === segName
? sketchGroup.start
sketchGroup?.start?.name === segName
? sketchGroup?.start
: sketchGroup?.value.find((seg) => seg.name === segName)
// maybe this should throw, but the language doesn't have a way to handle errors yet
if (!line) return 0

View File

@ -10,7 +10,7 @@ import {
} from './sketchcombos'
import { initPromise } from '../rust'
import { Selections, TooTip } from '../../useStore'
import { executor } from '../../lang/executor'
import { executor } from '../../lib/testHelpers'
import { recast } from '../../lang/recast'
beforeAll(() => initPromise)
@ -196,7 +196,7 @@ const part001 = startSketchAt([0, 0])
|> xLine(segLen('seg01', %), %) // ln-xLineTo-free should convert to xLine
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
show(part001)`
it('It should transform the ast', () => {
it('It should transform the ast', async () => {
const ast = abstractSyntaxTree(lexer(inputScript))
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
@ -210,7 +210,7 @@ show(part001)`
}
})
const programMemory = executor(ast)
const programMemory = await executor(ast)
const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)),
ast,
@ -255,7 +255,7 @@ const part001 = startSketchAt([0, 0])
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
show(part001)`
it('It should transform horizontal lines the ast', () => {
it('It should transform horizontal lines the ast', async () => {
const expectModifiedScript = `const myVar = 2
const myVar2 = 12
const myVar3 = -10
@ -295,7 +295,7 @@ show(part001)`
}
})
const programMemory = executor(ast)
const programMemory = await executor(ast)
const transformInfos = getTransformInfos(
makeSelections(selectionRanges),
ast,
@ -312,7 +312,7 @@ show(part001)`
const newCode = recast(newAst)
expect(newCode).toBe(expectModifiedScript)
})
it('It should transform vertical lines the ast', () => {
it('It should transform vertical lines the ast', async () => {
const expectModifiedScript = `const myVar = 2
const myVar2 = 12
const myVar3 = -10
@ -352,7 +352,7 @@ show(part001)`
}
})
const programMemory = executor(ast)
const programMemory = await executor(ast)
const transformInfos = getTransformInfos(
makeSelections(selectionRanges),
ast,
@ -381,13 +381,13 @@ const part001 = startSketchAt([0, 0])
|> line([myVar, 0.01], %) // xRelative
|> line([0.7, myVar], %) // yRelative
show(part001)`
it('testing for free to horizontal and vertical distance', () => {
const expectedHorizontalCode = helperThing(
it('testing for free to horizontal and vertical distance', async () => {
const expectedHorizontalCode = await helperThing(
inputScript,
['// base selection', '// free'],
'setHorzDistance'
)
const expectedVerticalCode = helperThing(
const expectedVerticalCode = await helperThing(
inputScript,
['// base selection', '// free'],
'setVertDistance'
@ -399,8 +399,8 @@ show(part001)`
`lineTo([1.21, segEndY('seg01', %) + 2.92], %) // free`
)
})
it('testing for xRelative to vertical distance', () => {
const expectedCode = helperThing(
it('testing for xRelative to vertical distance', async () => {
const expectedCode = await helperThing(
inputScript,
['// base selection', '// xRelative'],
'setVertDistance'
@ -410,8 +410,8 @@ show(part001)`
segEndY('seg01', %) + 2.93
], %) // xRelative`)
})
it('testing for yRelative to horizontal distance', () => {
const expectedCode = helperThing(
it('testing for yRelative to horizontal distance', async () => {
const expectedCode = await helperThing(
inputScript,
['// base selection', '// yRelative'],
'setHorzDistance'
@ -424,11 +424,11 @@ show(part001)`
})
})
function helperThing(
async function helperThing(
inputScript: string,
linesOfInterest: string[],
constraint: ConstraintType
): string {
): Promise<string> {
const ast = abstractSyntaxTree(lexer(inputScript))
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
@ -444,7 +444,7 @@ function helperThing(
}
})
const programMemory = executor(ast)
const programMemory = await executor(ast)
const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)),
ast,

View File

@ -1,12 +1,12 @@
import { abstractSyntaxTree } from '../abstractSyntaxTree'
import { executor } from '../executor'
import { executor } from '../../lib/testHelpers'
import { lexer } from '../tokeniser'
import { initPromise } from '../rust'
beforeAll(() => initPromise)
describe('testing angledLineThatIntersects', () => {
it('angledLineThatIntersects should intersect with another line', () => {
it('angledLineThatIntersects should intersect with another line', async () => {
const code = (offset: string) => `const part001 = startSketchAt([0, 0])
|> lineTo({to:[2, 2], tag: "yo"}, %)
|> lineTo([3, 1], %)
@ -18,9 +18,11 @@ describe('testing angledLineThatIntersects', () => {
}, %)
const intersect = segEndX('yo2', part001)
show(part001)`
const { root } = executor(abstractSyntaxTree(lexer(code('-1'))))
const { root } = await executor(abstractSyntaxTree(lexer(code('-1'))))
expect(root.intersect.value).toBe(1 + Math.sqrt(2))
const { root: noOffset } = executor(abstractSyntaxTree(lexer(code('0'))))
const { root: noOffset } = await executor(
abstractSyntaxTree(lexer(code('0')))
)
expect(noOffset.intersect.value).toBeCloseTo(1)
})
})

View File

@ -24,62 +24,61 @@ import {
lastSegX,
lastSegY,
} from './sketchConstraints'
import { extrude, getExtrudeWallTransform } from './extrude'
import { Quaternion, Vector3 } from 'three'
import { getExtrudeWallTransform, extrude } from './extrude'
import { SketchGroup, ExtrudeGroup, Position, Rotation } from '../executor'
import { InternalFn, InternalFnNames, InternalFirstArg } from './stdTypes'
const transform: InternalFn = <T extends SketchGroup | ExtrudeGroup>(
{ sourceRange }: InternalFirstArg,
transformInfo: {
position: Position
quaternion: Rotation
},
sketch: T
): T => {
const quaternionToApply = new Quaternion(...transformInfo.quaternion)
const newQuaternion = new Quaternion(...sketch.rotation).multiply(
quaternionToApply.invert()
)
// const transform: InternalFn = <T extends SketchGroup | ExtrudeGroup>(
// { sourceRange }: InternalFirstArg,
// transformInfo: {
// position: Position
// quaternion: Rotation
// },
// sketch: T
// ): T => {
// const quaternionToApply = new Quaternion(...transformInfo?.quaternion)
// const newQuaternion = new Quaternion(...sketch.rotation).multiply(
// quaternionToApply.invert()
// )
const oldPosition = new Vector3(...sketch.position)
const newPosition = oldPosition
.applyQuaternion(quaternionToApply)
.add(new Vector3(...transformInfo.position))
return {
...sketch,
position: newPosition.toArray(),
rotation: newQuaternion.toArray(),
__meta: [
...sketch.__meta,
{
sourceRange,
pathToNode: [], // TODO
},
],
}
}
// const oldPosition = new Vector3(...sketch?.position)
// const newPosition = oldPosition
// .applyQuaternion(quaternionToApply)
// .add(new Vector3(...transformInfo?.position))
// return {
// ...sketch,
// position: newPosition.toArray(),
// rotation: newQuaternion.toArray(),
// __meta: [
// ...sketch.__meta,
// {
// sourceRange,
// pathToNode: [], // TODO
// },
// ],
// }
// }
const translate: InternalFn = <T extends SketchGroup | ExtrudeGroup>(
{ sourceRange }: InternalFirstArg,
vec3: [number, number, number],
sketch: T
): T => {
const oldPosition = new Vector3(...sketch.position)
const newPosition = oldPosition.add(new Vector3(...vec3))
return {
...sketch,
position: newPosition.toArray(),
__meta: [
...sketch.__meta,
{
sourceRange,
pathToNode: [], // TODO
},
],
}
}
// const translate: InternalFn = <T extends SketchGroup | ExtrudeGroup>(
// { sourceRange }: InternalFirstArg,
// vec3: [number, number, number],
// sketch: T
// ): T => {
// const oldPosition = new Vector3(...sketch.position)
// const newPosition = oldPosition.add(new Vector3(...vec3))
// return {
// ...sketch,
// position: newPosition.toArray(),
// __meta: [
// ...sketch.__meta,
// {
// sourceRange,
// pathToNode: [], // TODO
// },
// ],
// }
// }
const min: InternalFn = (_, a: number, b: number): number => Math.min(a, b)
@ -95,12 +94,13 @@ const legAngY: InternalFn = (_, hypotenuse: number, leg: number): number =>
(Math.asin(Math.min(leg, hypotenuse) / hypotenuse) * 180) / Math.PI
export const internalFns: { [key in InternalFnNames]: InternalFn } = {
rx: rotateOnAxis([1, 0, 0]),
ry: rotateOnAxis([0, 1, 0]),
rz: rotateOnAxis([0, 0, 1]),
// TODO - re-enable these
// rx: rotateOnAxis([1, 0, 0]),
// ry: rotateOnAxis([0, 1, 0]),
// rz: rotateOnAxis([0, 0, 1]),
extrude,
translate,
transform,
// translate,
// transform,
getExtrudeWallTransform,
min,
legLen,
@ -130,35 +130,35 @@ export const internalFns: { [key in InternalFnNames]: InternalFn } = {
close,
}
function rotateOnAxis<T extends SketchGroup | ExtrudeGroup>(
axisMultiplier: [number, number, number]
): InternalFn {
return ({ sourceRange }, rotationD: number, sketch: T): T => {
const rotationR = rotationD * (Math.PI / 180)
const rotateVec = new Vector3(...axisMultiplier)
const quaternion = new Quaternion()
quaternion.setFromAxisAngle(rotateVec, rotationR)
// function rotateOnAxis<T extends SketchGroup | ExtrudeGroup>(
// axisMultiplier: [number, number, number]
// ): InternalFn {
// return ({ sourceRange }, rotationD: number, sketch: T): T => {
// const rotationR = rotationD * (Math.PI / 180)
// const rotateVec = new Vector3(...axisMultiplier)
// const quaternion = new Quaternion()
// quaternion.setFromAxisAngle(rotateVec, rotationR)
const position = new Vector3(...sketch.position)
.applyQuaternion(quaternion)
.toArray()
// const position = new Vector3(...sketch.position)
// .applyQuaternion(quaternion)
// .toArray()
const existingQuat = new Quaternion(...sketch.rotation)
const rotation = quaternion.multiply(existingQuat).toArray()
return {
...sketch,
rotation,
position,
__meta: [
...sketch.__meta,
{
sourceRange,
pathToNode: [], // TODO
},
],
}
}
}
// const existingQuat = new Quaternion(...sketch.rotation)
// const rotation = quaternion.multiply(existingQuat).toArray()
// return {
// ...sketch,
// rotation,
// position,
// __meta: [
// ...sketch.__meta,
// {
// sourceRange,
// pathToNode: [], // TODO
// },
// ],
// }
// }
// }
export function clockwiseSign(points: [number, number][]): number {
let sum = 0

View File

@ -2,11 +2,14 @@ import { ProgramMemory, Path, SourceRange } from '../executor'
import { Program, Value } from '../abstractSyntaxTree'
import { TooTip } from '../../useStore'
import { PathToNode } from '../executor'
import { EngineCommandManager } from './engineConnection'
export interface InternalFirstArg {
programMemory: ProgramMemory
name?: string
sourceRange: SourceRange
engineCommandManager: EngineCommandManager
code: string
}
export interface PathReturn {
@ -17,9 +20,13 @@ export interface PathReturn {
export type InternalFn = (internals: InternalFirstArg, ...args: any[]) => any
export type InternalFnNames =
// TODO re-enable these
// | 'translate'
// | 'transform'
// | 'rx'
// | 'ry'
// | 'rz'
| 'extrude'
| 'translate'
| 'transform'
| 'getExtrudeWallTransform'
| 'min'
| 'legLen'
@ -33,9 +40,6 @@ export type InternalFnNames =
| 'segAng'
| 'angleToMatchLengthX'
| 'angleToMatchLengthY'
| 'rx'
| 'ry'
| 'rz'
| 'lineTo'
| 'yLineTo'
| 'xLineTo'

14
src/lib/testHelpers.ts Normal file
View File

@ -0,0 +1,14 @@
import { Program } from '../lang/abstractSyntaxTree'
import { ProgramMemory, _executor } from '../lang/executor'
import { EngineCommandManager } from '../lang/std/engineConnection'
export async function executor(
ast: Program,
pm: ProgramMemory = { root: {}, pendingMemory: {} }
): Promise<ProgramMemory> {
const engineCommandManager = new EngineCommandManager(() => {})
engineCommandManager.startNewSession()
const programMemory = await _executor(ast, pm, engineCommandManager)
await engineCommandManager.waitForAllCommands()
return programMemory
}

View File

@ -27,3 +27,31 @@ export function normaliseAngle(angle: number): number {
const result = ((angle % 360) + 360) % 360
return result > 180 ? result - 360 : result
}
export function throttle(
func: (...args: any[]) => any,
wait: number
): (...args: any[]) => any {
let timeout: ReturnType<typeof setTimeout> | null
let latestArgs: any[]
let latestTimestamp: number
function later() {
timeout = null
func(...latestArgs)
}
function throttled(...args: any[]) {
const currentTimestamp = Date.now()
latestArgs = args
if (!latestTimestamp || currentTimestamp - latestTimestamp >= wait) {
latestTimestamp = currentTimestamp
func(...latestArgs)
} else if (!timeout) {
timeout = setTimeout(later, wait - (currentTimestamp - latestTimestamp))
}
}
return throttled
}

9
src/lib/uuid.test.ts Normal file
View File

@ -0,0 +1,9 @@
import { generateUuidFromHashSeed } from './uuid'
describe('generateUuidFromHashSeed', () => {
it('generates a UUID from a hash seed', () => {
const inputString = 'Hello, World!'
const uuid = generateUuidFromHashSeed(inputString)
expect(uuid).toEqual('64666664-3630-4231-a262-326264356230')
})
})

10
src/lib/uuid.ts Normal file
View File

@ -0,0 +1,10 @@
import { v4 as uuidv4 } from 'uuid'
import { SHA256 } from 'crypto-js'
export function generateUuidFromHashSeed(seed: string): string {
const hashedSeed = SHA256(seed).toString()
const uuid = uuidv4({
random: hashedSeed.split('').map((c: string) => c.charCodeAt(0)),
})
return uuid
}

View File

@ -3,3 +3,5 @@
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom'
import 'setimmediate'

View File

@ -14,6 +14,12 @@ import { recast } from './lang/recast'
import { asyncLexer } from './lang/tokeniser'
import { EditorSelection } from '@codemirror/state'
import { BaseDirectory } from '@tauri-apps/api/fs'
import { ArtifactMap, SourceRangeMap, EngineCommandManager } from './lang/std/engineConnection'
// import {
// ArtifactMap,
// SourceRangeMap,
// EngineCommandManager,
// } from './lang/std/engineConnection'
export type Selection = {
type: 'default' | 'line-end' | 'line-mid'
@ -100,6 +106,7 @@ export interface StoreState {
highlightRange: [number, number]
setHighlightRange: (range: Selection['range']) => void
setCursor: (selections: Selections) => void
setCursor2: (a: Selection) => void
selectionRanges: Selections
selectionRangeTypeMap: { [key: number]: Selection['type'] }
setSelectionRanges: (range: Selections) => void
@ -131,6 +138,16 @@ export interface StoreState {
setProgramMemory: (programMemory: ProgramMemory) => void
isShiftDown: boolean
setIsShiftDown: (isShiftDown: boolean) => void
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
setArtifactNSourceRangeMaps: (a: {
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
}) => void
engineCommandManager?: EngineCommandManager
setEngineCommandManager: (engineCommandManager: EngineCommandManager) => void
mediaStream?: MediaStream
setMediaStream: (mediaStream: MediaStream) => void
// tauri specific app settings
defaultDir: DefaultDir
@ -168,8 +185,10 @@ export const useStore = create<StoreState>()(
const selectionRangeTypeMap: { [key: number]: Selection['type'] } = {}
set({ selectionRangeTypeMap })
selections.codeBasedSelections.forEach(({ range, type }) => {
ranges.push(EditorSelection.cursor(range[1]))
selectionRangeTypeMap[range[1]] = type
if (range?.[1]) {
ranges.push(EditorSelection.cursor(range[1]))
selectionRangeTypeMap[range[1]] = type
}
})
setTimeout(() => {
editorView.dispatch({
@ -180,6 +199,16 @@ export const useStore = create<StoreState>()(
})
})
},
setCursor2: (codeSelections) => {
const currestSelections = get().selectionRanges
const selections: Selections = {
...currestSelections,
codeBasedSelections: get().isShiftDown
? [...currestSelections.codeBasedSelections, codeSelections]
: [codeSelections],
}
get().setCursor(selections)
},
selectionRangeTypeMap: {},
selectionRanges: {
otherSelections: [],
@ -261,11 +290,17 @@ export const useStore = create<StoreState>()(
setError: (error = '') => {
set({ errorState: { isError: !!error, error } })
},
programMemory: { root: {}, _sketch: [] },
programMemory: { root: {}, pendingMemory: {} },
setProgramMemory: (programMemory) => set({ programMemory }),
isShiftDown: false,
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
artifactMap: {},
sourceRangeMap: {},
setArtifactNSourceRangeMaps: (maps) => set({ ...maps }),
setEngineCommandManager: (engineCommandManager) =>
set({ engineCommandManager }),
setMediaStream: (mediaStream) => set({ mediaStream }),
// tauri specific app settings
defaultDir: {
dir: '',

509
yarn.lock
View File

@ -1233,7 +1233,7 @@
core-js-pure "^3.25.1"
regenerator-runtime "^0.13.10"
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.6", "@babel/runtime@^7.18.9", "@babel/runtime@^7.9.2":
"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.18.9", "@babel/runtime@^7.9.2":
version "7.20.1"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.1.tgz#1148bb33ab252b165a06698fde7576092a78b4a9"
integrity sha512-mrzLkl6U9YLF8qpqI7TB82PESyEGjm/0Ly91jG575eVxMMlb8fYfOXFZIJ8XfLrJZQbm7dlKry2bJmXBUEkdFg==
@ -1319,33 +1319,6 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@chevrotain/cst-dts-gen@10.4.1":
version "10.4.1"
resolved "https://registry.yarnpkg.com/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.4.1.tgz#b5b5656d6121dd6cf21675ffc19641057c005c44"
integrity sha512-wNDw9Rh6dPJKH275er8nijuDIpTcG2GjQANjnG8RaeGkZ3JN99+u6HRtnjKhjoi4NY9rg+udHChHQSskZtlkPw==
dependencies:
"@chevrotain/gast" "10.4.1"
"@chevrotain/types" "10.4.1"
lodash "4.17.21"
"@chevrotain/gast@10.4.1":
version "10.4.1"
resolved "https://registry.yarnpkg.com/@chevrotain/gast/-/gast-10.4.1.tgz#5725a7939ffde52c0f702e38ce9c4b910acb6715"
integrity sha512-HRv66QVbmC7eb/ppwsPCfNH4oZ/VV+thuMZILm7A7W6Q5M0tqiZv0ecdiB8hydmPO8je0aSrXEOCcaA6fuXc3Q==
dependencies:
"@chevrotain/types" "10.4.1"
lodash "4.17.21"
"@chevrotain/types@10.4.1":
version "10.4.1"
resolved "https://registry.yarnpkg.com/@chevrotain/types/-/types-10.4.1.tgz#0663800a3ef949eb512fce5be808d29128351738"
integrity sha512-J8iyZNn/RGYWSyNJdGd3QI01gKFUx4mCSM0+vEqmIw9TXFlxj1IsHteXFahtezSHjgMtBTqWn6hb2YxCLjpHVg==
"@chevrotain/utils@10.4.1":
version "10.4.1"
resolved "https://registry.yarnpkg.com/@chevrotain/utils/-/utils-10.4.1.tgz#42b9e0d222b24395b50d88917d20934ae77f6892"
integrity sha512-vPIgzES8QhHMchb5UaQ4V/c9xmoaECN+4EXpuhWE+pu3LXJUUtAwDn/SEKFgtyiRo269Hxv3b0NbPlQfH0jeVA==
"@codemirror/autocomplete@^6.0.0":
version "6.3.3"
resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.3.3.tgz#f9dba421b9a0d83ecf89a06def37a96e6afb593a"
@ -1433,6 +1406,13 @@
style-mod "^4.0.0"
w3c-keyname "^2.2.4"
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
"@csstools/normalize.css@*":
version "12.0.0"
resolved "https://registry.yarnpkg.com/@csstools/normalize.css/-/normalize.css-12.0.0.tgz#a9583a75c3f150667771f30b60d9f059473e62c4"
@ -1859,6 +1839,11 @@
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
@ -1877,6 +1862,14 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@0.3.9":
version "0.3.9"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.17"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
@ -1965,90 +1958,6 @@
schema-utils "^3.0.0"
source-map "^0.7.3"
"@react-spring/animated@~9.5.5":
version "9.5.5"
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.5.5.tgz#d3bfd0f62ed13a337463a55d2c93bb23c15bbf3e"
integrity sha512-glzViz7syQ3CE6BQOwAyr75cgh0qsihm5lkaf24I0DfU63cMm/3+br299UEYkuaHNmfDfM414uktiPlZCNJbQA==
dependencies:
"@react-spring/shared" "~9.5.5"
"@react-spring/types" "~9.5.5"
"@react-spring/core@~9.5.5":
version "9.5.5"
resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.5.5.tgz#1d8a4c64630ee26b2295361e1eedfd716a85b4ae"
integrity sha512-shaJYb3iX18Au6gkk8ahaF0qx0LpS0Yd+ajb4asBaAQf6WPGuEdJsbsNSgei1/O13JyEATsJl20lkjeslJPMYA==
dependencies:
"@react-spring/animated" "~9.5.5"
"@react-spring/rafz" "~9.5.5"
"@react-spring/shared" "~9.5.5"
"@react-spring/types" "~9.5.5"
"@react-spring/rafz@~9.5.5":
version "9.5.5"
resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.5.5.tgz#62a49c5e294104b79db2a8afdf4f3a274c7f44ca"
integrity sha512-F/CLwB0d10jL6My5vgzRQxCNY2RNyDJZedRBK7FsngdCmzoq3V4OqqNc/9voJb9qRC2wd55oGXUeXv2eIaFmsw==
"@react-spring/shared@~9.5.5":
version "9.5.5"
resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.5.5.tgz#9be0b391d546e3e184a24ecbaf40acbaeab7fc73"
integrity sha512-YwW70Pa/YXPOwTutExHZmMQSHcNC90kJOnNR4G4mCDNV99hE98jWkIPDOsgqbYx3amIglcFPiYKMaQuGdr8dyQ==
dependencies:
"@react-spring/rafz" "~9.5.5"
"@react-spring/types" "~9.5.5"
"@react-spring/three@^9.3.1":
version "9.5.5"
resolved "https://registry.yarnpkg.com/@react-spring/three/-/three-9.5.5.tgz#c6fbee977007d1980406db20a28ac3f5dc2ce153"
integrity sha512-9kTIaSceqFIl5EIrdwM7Z53o5I+9BGNVzbp4oZZYMao+GMAWOosnlQdDG5GeqNsIqfW9fZCEquGqagfKAxftcA==
dependencies:
"@react-spring/animated" "~9.5.5"
"@react-spring/core" "~9.5.5"
"@react-spring/shared" "~9.5.5"
"@react-spring/types" "~9.5.5"
"@react-spring/types@~9.5.5":
version "9.5.5"
resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.5.5.tgz#c8e94f1b9232ca7cb9d860ea67762ec401b1de14"
integrity sha512-7I/qY8H7Enwasxr4jU6WmtNK+RZ4Z/XvSlDvjXFVe7ii1x0MoSlkw6pD7xuac8qrHQRm9BTcbZNyeeKApYsvCg==
"@react-three/drei@^9.42.0":
version "9.42.0"
resolved "https://registry.yarnpkg.com/@react-three/drei/-/drei-9.42.0.tgz#0c81ebdf3bd48d87edddc42a0007a0db3aaf48f5"
integrity sha512-rVVQl+GICK1ny1KkA2Qo3zdnNPeGCobuaNjS4rbIeTELumsVB9s5au76OcjrW5kSpaKxNeQNQQ5NTKme5xvIHw==
dependencies:
"@babel/runtime" "^7.11.2"
"@react-spring/three" "^9.3.1"
"@use-gesture/react" "^10.2.0"
detect-gpu "^4.0.36"
glsl-noise "^0.0.0"
lodash.clamp "^4.0.3"
lodash.omit "^4.5.0"
lodash.pick "^4.4.0"
meshline "^2.0.4"
react-composer "^5.0.3"
react-merge-refs "^1.1.0"
stats.js "^0.17.0"
suspend-react "^0.0.8"
three-mesh-bvh "^0.5.15"
three-stdlib "^2.18.1"
troika-three-text "^0.46.4"
utility-types "^3.10.0"
zustand "^3.5.13"
"@react-three/fiber@^8.9.1":
version "8.9.1"
resolved "https://registry.yarnpkg.com/@react-three/fiber/-/fiber-8.9.1.tgz#54e278148ae1c301a4b516936bfce0d9240a7292"
integrity sha512-xRMO9RGp0DkxSFu5BmmkjCxJ4r0dEpLobtxXdZwI0h2rZZaCnkPM5zThRN8xaZNbZhzRSVICeNOFaZltr9xFyQ==
dependencies:
"@babel/runtime" "^7.17.8"
"@types/react-reconciler" "^0.26.7"
its-fine "^1.0.6"
react-reconciler "^0.27.0"
react-use-measure "^2.1.1"
scheduler "^0.21.0"
suspend-react "^0.0.8"
zustand "^3.7.1"
"@rollup/plugin-babel@^5.2.0":
version "5.3.1"
resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283"
@ -2343,6 +2252,26 @@
resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
"@tsconfig/node10@^1.0.7":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2"
integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==
"@tsconfig/node12@^1.0.7":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d"
integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==
"@tsconfig/node14@^1.0.0":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1"
integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==
"@tsconfig/node16@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e"
integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==
"@types/aria-query@^4.2.0":
version "4.2.2"
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
@ -2562,11 +2491,6 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.3.tgz#d7f7ba828ad9e540270f01ce00d391c54e6e0abc"
integrity sha512-jh6m0QUhIRcZpNv7Z/rpN+ZWXOicUUQbSoWks7Htkbb9IjFQj4kzcX/xFCkjstCj5flMsN8FiSvt+q+Tcs4Llg==
"@types/offscreencanvas@^2019.6.4":
version "2019.7.0"
resolved "https://registry.yarnpkg.com/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz#e4a932069db47bb3eabeb0b305502d01586fa90d"
integrity sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
@ -2604,20 +2528,6 @@
dependencies:
"@types/react" "*"
"@types/react-reconciler@^0.26.7":
version "0.26.7"
resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.26.7.tgz#0c4643f30821ae057e401b0d9037e03e8e9b2a36"
integrity sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==
dependencies:
"@types/react" "*"
"@types/react-reconciler@^0.28.0":
version "0.28.0"
resolved "https://registry.yarnpkg.com/@types/react-reconciler/-/react-reconciler-0.28.0.tgz#513acbed173140e958c909041ca14eb40412077f"
integrity sha512-5cjk9ottZAj7eaTsqzPUIlrVbh3hBAO2YaEL1rkjHKB3xNAId7oU8GhzvAX+gfmlfoxTwJnBjPxEHyxkEA1Ffg==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18.0.0":
version "18.0.25"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.25.tgz#8b1dcd7e56fe7315535a4af25435e0bb55c8ae44"
@ -2683,27 +2593,15 @@
dependencies:
"@types/jest" "*"
"@types/three@^0.146.0":
version "0.146.0"
resolved "https://registry.yarnpkg.com/@types/three/-/three-0.146.0.tgz#83813ba0d2fff6bdc6d7fda3a77993a932bba45f"
integrity sha512-75AgysUrIvTCB054eQa2pDVFurfeFW8CrMQjpzjt3yHBfuuknoSvvsESd/3EhQxPrz9si3+P0wiDUVsWUlljfA==
dependencies:
"@types/webxr" "*"
"@types/trusted-types@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==
"@types/uuid@^9.0.2":
version "9.0.2"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.2.tgz#ede1d1b1e451548d44919dc226253e32a6952c4b"
integrity sha512-kNnC1GFBLuhImSnV7w4njQkUiJi0ZXUycu1rUaouPqiKlXkh77JKgdRnTAp1x5eBwcIwbtI+3otwzuIDEuDoxQ==
"@types/webxr@*":
version "0.5.0"
resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.0.tgz#aae1cef3210d88fd4204f8c33385a0bbc4da07c9"
integrity sha512-IUMDPSXnYIbEO2IereEFcgcqfDREOgmbGqtrMpVPpACTU6pltYLwHgVkrnYv0XhWEcjio9sYEfIEzgn3c7nDqA==
"@types/uuid@^9.0.1":
version "9.0.1"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-9.0.1.tgz#98586dc36aee8dacc98cc396dbca8d0429647aa6"
integrity sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==
"@types/ws@^8.5.1":
version "8.5.3"
@ -2846,18 +2744,6 @@
"@uiw/codemirror-extensions-basic-setup" "4.15.1"
codemirror "^6.0.0"
"@use-gesture/core@10.2.22":
version "10.2.22"
resolved "https://registry.yarnpkg.com/@use-gesture/core/-/core-10.2.22.tgz#90bd543b042e6c3a40a69ce8c2c38ecb67f43a2f"
integrity sha512-Ek0JZFYfk+hicLmoG094gm3YOuDMBNckHb988e59YOZoAkETT8dQSzT+g3QkSHSiP1m5wFXAGPSgxvOuwvGKHQ==
"@use-gesture/react@^10.2.0":
version "10.2.22"
resolved "https://registry.yarnpkg.com/@use-gesture/react/-/react-10.2.22.tgz#2b8a39fe9bab83d73b112c8a5248b95099672c84"
integrity sha512-ECo7ig16SxBE06ENIURO1woKEB6TC8qY3a0rugJjQ2f1o0Tj28xS/eYNyJuqzQB5YT0q5IrF7ZFpbx1p/5ohYA==
dependencies:
"@use-gesture/core" "10.2.22"
"@webassemblyjs/ast@1.11.1":
version "1.11.1"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7"
@ -2979,11 +2865,6 @@
"@webassemblyjs/ast" "1.11.1"
"@xtuc/long" "4.2.2"
"@webgpu/glslang@^0.0.15":
version "0.0.15"
resolved "https://registry.yarnpkg.com/@webgpu/glslang/-/glslang-0.0.15.tgz#f5ccaf6015241e6175f4b90906b053f88483d1f2"
integrity sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q==
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@ -3047,6 +2928,11 @@ acorn-walk@^7.0.0, acorn-walk@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
acorn-walk@^8.1.1:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
acorn@^7.0.0:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
@ -3062,6 +2948,11 @@ acorn@^8.2.4, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
acorn@^8.4.1:
version "8.8.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
address@^1.0.1:
version "1.1.2"
resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6"
@ -3222,6 +3113,11 @@ anymatch@^3.0.3, anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
arg@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
@ -3534,13 +3430,6 @@ bfj@^7.0.2:
hoopy "^0.1.4"
tryer "^1.0.1"
bidi-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/bidi-js/-/bidi-js-1.0.2.tgz#1a497a762c2ddea377429d2649c9ce0f8a91527f"
integrity sha512-rzSy/k7WdX5zOyeHHCOixGXbCHkyogkxPKL2r8QtzHmVQDiWCXUWa18bLdMWT9CYMLOYTjWpTHawuev2ouYJVw==
dependencies:
require-from-string "^2.0.2"
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@ -3773,18 +3662,6 @@ check-types@^11.1.1:
resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.1.2.tgz#86a7c12bf5539f6324eb0e70ca8896c0e38f3e2f"
integrity sha512-tzWzvgePgLORb9/3a0YenggReLKAIb2owL03H2Xdoe5pKcUyWRSEQ8xfCar8t2SIAuEDwtmx2da1YB52YuHQMQ==
chevrotain@^10.1.2:
version "10.4.1"
resolved "https://registry.yarnpkg.com/chevrotain/-/chevrotain-10.4.1.tgz#85571fa23551c1fa440ce62f0a83159616cfc520"
integrity sha512-1y4vnssauVmrrP5MBaJ6DZvsv3BpXLlKVNK5S52fTGQHqg09qxMDBAz0wZbb04Ovc1pBCA4obcCjOlRioIV+cA==
dependencies:
"@chevrotain/cst-dts-gen" "10.4.1"
"@chevrotain/gast" "10.4.1"
"@chevrotain/types" "10.4.1"
"@chevrotain/utils" "10.4.1"
lodash "4.17.21"
regexp-to-ast "0.5.0"
chokidar@^3.4.2, chokidar@^3.5.3:
version "3.5.3"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
@ -4069,6 +3946,11 @@ cosmiconfig@^7.0.0:
path-type "^4.0.0"
yaml "^1.10.0"
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
crelt@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.5.tgz#57c0d52af8c859e354bace1883eb2e1eb182bb94"
@ -4090,6 +3972,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
crypto-js@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==
crypto-random-string@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
@ -4311,11 +4198,6 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
debounce@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
debug@2.6.9, debug@^2.6.0, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -4449,13 +4331,6 @@ destroy@1.2.0:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
detect-gpu@^4.0.36:
version "4.0.49"
resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-4.0.49.tgz#a604b971c3cd1431be21a9842117dc495d82a982"
integrity sha512-o/iuS6Pz2+wOcVm3A47cJM3O8XwPecEyHRc7jTthNL2E1ZcqthLj2GKv4fQ5Zl20L9rm/G39DuPvBV2gS8F6Pg==
dependencies:
webgl-constants "^1.1.1"
detect-newline@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
@ -4498,6 +4373,11 @@ diff-sequences@^29.2.0:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.2.0.tgz#4c55b5b40706c7b5d2c5c75999a50c56d214e8f6"
integrity sha512-413SY5JpYeSBZxmenGEmCVQ8mCgtFJF0w9PROdaS6z987XC2Pd2GOKqOITLtMftmyFZqgtCOb/QA7/Z3ZXfzIw==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@ -4629,11 +4509,6 @@ dotenv@^10.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
draco3d@^1.4.1:
version "1.5.5"
resolved "https://registry.yarnpkg.com/draco3d/-/draco3d-1.5.5.tgz#6bf4bbdd65950e6153e991cb0dcb8a10323f610e"
integrity sha512-JVuNV0EJzD3LBYhGyIXJLeBID/EVtmFO1ZNhAYflTgiMiAJlbhXQmRRda/azjc8MRVMHh0gqGhiqHUo5dIXM8Q==
duplexer@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
@ -5264,11 +5139,6 @@ fbjs@^3.0.0, fbjs@^3.0.1:
setimmediate "^1.0.5"
ua-parser-js "^0.7.30"
fflate@^0.6.9:
version "0.6.10"
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.6.10.tgz#5f40f9659205936a2d18abf88b2e7781662b6d43"
integrity sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
@ -5610,11 +5480,6 @@ globby@^11.0.4, globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
glsl-noise@^0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/glsl-noise/-/glsl-noise-0.0.0.tgz#367745f3a33382c0eeec4cb54b7e99cfc1d7670b"
integrity sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==
gopd@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@ -6322,13 +6187,6 @@ istanbul-reports@^3.1.3:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
its-fine@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/its-fine/-/its-fine-1.0.6.tgz#087b14d71137816dab676d8b57c35a6cd5d2b021"
integrity sha512-VZJZPwVT2kxe5KQv+TxCjojfLiUIut8zXDNLTxcM7gJ/xQ/bSPk5M0neZ+j3myy45KKkltY1mm1jyJgx3Fxsdg==
dependencies:
"@types/react-reconciler" "^0.28.0"
jake@^10.8.5:
version "10.8.5"
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46"
@ -7027,11 +6885,6 @@ klona@^2.0.4, klona@^2.0.5:
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
ktx-parse@^0.4.5:
version "0.4.5"
resolved "https://registry.yarnpkg.com/ktx-parse/-/ktx-parse-0.4.5.tgz#79905e22281a9d3e602b2ff522df1ee7d1813aa6"
integrity sha512-MK3FOody4TXbFf8Yqv7EBbySw7aPvEcPX++Ipt6Sox+/YMFvR5xaTyhfNSk1AEmMy+RYIw81ctN4IMxCB8OAlg==
language-subtag-registry@~0.3.2:
version "0.3.22"
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d"
@ -7116,7 +6969,7 @@ locate-path@^6.0.0:
dependencies:
p-locate "^5.0.0"
lodash.clamp@^4.0.0, lodash.clamp@^4.0.3:
lodash.clamp@^4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash.clamp/-/lodash.clamp-4.0.3.tgz#5c24bedeeeef0753560dc2b4cb4671f90a6ddfaa"
integrity sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==
@ -7151,16 +7004,6 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash.omit@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.omit/-/lodash.omit-4.5.0.tgz#6eb19ae5a1ee1dd9df0b969e66ce0b7fa30b5e60"
integrity sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==
lodash.pick@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==
lodash.sortby@^4.7.0:
version "4.7.0"
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
@ -7171,16 +7014,16 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@4.17.21, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
lodash@^4.17.13:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@ -7228,6 +7071,11 @@ make-dir@^3.0.2:
dependencies:
semver "^6.0.0"
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
makeerror@1.0.x:
version "1.0.11"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
@ -7272,11 +7120,6 @@ merge2@^1.3.0, merge2@^1.4.1:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
meshline@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/meshline/-/meshline-2.0.4.tgz#39c7bcf36b503397642f2312e6211f2a8ecf75c5"
integrity sha512-Jh6DJl/zLqA4xsKvGv5950jr2ukyXQE1wgxs8u94cImHrvL6soVIggqjP+2hVHZXGYaKnWszhtjuCbKNeQyYiw==
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@ -7411,11 +7254,6 @@ mkdirp@~0.5.1:
dependencies:
minimist "^1.2.5"
mmd-parser@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/mmd-parser/-/mmd-parser-1.0.4.tgz#87cc05782cb5974ca854f0303fc5147bc9d690e7"
integrity sha512-Qi0VCU46t2IwfGv5KF0+D/t9cizcDug7qnNoy9Ggk7aucp0tssV8IwTMkBlDbm+VqAf3cdQHTCARKSsuS2MYFg==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -7698,14 +7536,6 @@ opener@^1.5.1:
resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598"
integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
opentype.js@^1.3.3:
version "1.3.4"
resolved "https://registry.yarnpkg.com/opentype.js/-/opentype.js-1.3.4.tgz#1c0e72e46288473cc4a4c6a2dc60fd7fe6020d77"
integrity sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==
dependencies:
string.prototype.codepointat "^0.2.1"
tiny-inflate "^1.0.3"
optionator@^0.8.1:
version "0.8.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
@ -8504,11 +8334,6 @@ postcss@^8.4.18, postcss@^8.4.19:
picocolors "^1.0.0"
source-map-js "^1.0.2"
potpack@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14"
integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@ -8600,7 +8425,7 @@ prompts@^2.4.2:
kleur "^3.0.3"
sisteransi "^1.0.5"
prop-types@^15.6.0, prop-types@^15.8.1:
prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -8710,13 +8535,6 @@ react-base16-styling@^0.6.0:
lodash.flow "^3.3.0"
pure-color "^1.2.0"
react-composer@^5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/react-composer/-/react-composer-5.0.3.tgz#7beb9513da5e8687f4f434ea1333ef36a4f3091b"
integrity sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==
dependencies:
prop-types "^15.6.0"
react-dev-utils@^12.0.1:
version "12.0.1"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-12.0.1.tgz#ba92edb4a1f379bd46ccd6bcd4e7bc398df33e73"
@ -8790,24 +8608,11 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-merge-refs@^1.1.0:
version "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"
integrity sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==
dependencies:
loose-envify "^1.1.0"
scheduler "^0.21.0"
react-refresh@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
@ -8877,13 +8682,6 @@ react-textarea-autosize@^8.3.2:
use-composed-ref "^1.3.0"
use-latest "^1.2.1"
react-use-measure@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-use-measure/-/react-use-measure-2.1.1.tgz#5824537f4ee01c9469c45d5f7a8446177c6cc4ba"
integrity sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==
dependencies:
debounce "^1.2.1"
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
@ -8988,11 +8786,6 @@ regex-parser@^2.2.11:
resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58"
integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q==
regexp-to-ast@0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz#56c73856bee5e1fef7f73a00f1473452ab712a24"
integrity sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==
regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
@ -9239,13 +9032,6 @@ saxes@^5.0.1:
dependencies:
xmlchars "^2.2.0"
scheduler@^0.21.0:
version "0.21.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.21.0.tgz#6fd2532ff5a6d877b6edb12f00d8ab7e8f308820"
integrity sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==
dependencies:
loose-envify "^1.1.0"
scheduler@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
@ -9566,11 +9352,6 @@ stackframe@^1.3.4:
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310"
integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==
stats.js@^0.17.0:
version "0.17.0"
resolved "https://registry.yarnpkg.com/stats.js/-/stats.js-0.17.0.tgz#b1c3dc46d94498b578b7fd3985b81ace7131cc7d"
integrity sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==
statuses@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
@ -9620,11 +9401,6 @@ string-width@^4.2.0:
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string.prototype.codepointat@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc"
integrity sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==
string.prototype.matchall@^4.0.6, string.prototype.matchall@^4.0.7:
version "4.0.7"
resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d"
@ -9801,11 +9577,6 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
suspend-react@^0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/suspend-react/-/suspend-react-0.0.8.tgz#b0740c1386b4eb652f17affe4339915ee268bd31"
integrity sha512-ZC3r8Hu1y0dIThzsGw0RLZplnX9yXwfItcvaIzJc2VQVi8TGyGDlu92syMB5ulybfvGLHAI5Ghzlk23UBPF8xg==
svg-parser@^2.0.2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
@ -9993,33 +9764,6 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
three-mesh-bvh@^0.5.15:
version "0.5.18"
resolved "https://registry.yarnpkg.com/three-mesh-bvh/-/three-mesh-bvh-0.5.18.tgz#e884bf7f23356b2e9de99be7a0dcd6f9d72b4860"
integrity sha512-lJQkt4A+pfHMf8Pbyqm5UiIBoVtp3cuy5rrTpuhIaJlbAobJW3/uQxJVZKiHaGi1Bs+5Svb+T8xIS17EqjG2ZA==
three-stdlib@^2.18.1:
version "2.19.0"
resolved "https://registry.yarnpkg.com/three-stdlib/-/three-stdlib-2.19.0.tgz#22b73fe07b2524548b486e551737db4b17586beb"
integrity sha512-ImKJXIlmx/iYH3U2CeLiYA2V60UqhpwLE2ErOME+FrD3Xodk7oQU5N9IZGKQzGjnl3hOfLmLZ5BPJrotmttBjg==
dependencies:
"@babel/runtime" "^7.16.7"
"@types/offscreencanvas" "^2019.6.4"
"@webgpu/glslang" "^0.0.15"
chevrotain "^10.1.2"
draco3d "^1.4.1"
fflate "^0.6.9"
ktx-parse "^0.4.5"
mmd-parser "^1.0.4"
opentype.js "^1.3.3"
potpack "^1.0.1"
zstddec "^0.0.2"
three@^0.146.0:
version "0.146.0"
resolved "https://registry.yarnpkg.com/three/-/three-0.146.0.tgz#fd80f0d128ab4bb821a02191ae241e4e6326f17a"
integrity sha512-1lvNfLezN6OJ9NaFAhfX4sm5e9YCzHtaRgZ1+B4C+Hv6TibRMsuBAM5/wVKzxjpYIlMymvgsHEFrrigEfXnb2A==
throat@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375"
@ -10030,11 +9774,6 @@ thunky@^1.0.2:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
tiny-inflate@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4"
integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==
tmpl@1.0.x:
version "1.0.4"
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
@ -10091,31 +9830,30 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
troika-three-text@^0.46.4:
version "0.46.4"
resolved "https://registry.yarnpkg.com/troika-three-text/-/troika-three-text-0.46.4.tgz#77627ac2ac4765d5248c857a8b42f82c25f2d034"
integrity sha512-Qsv0HhUKTZgSmAJs5wvO7YlBoJSP9TGPLmrg+K9pbQq4lseQdcevbno/WI38bwJBZ/qS56hvfqEzY0zUEFzDIw==
dependencies:
bidi-js "^1.0.2"
troika-three-utils "^0.46.0"
troika-worker-utils "^0.46.0"
webgl-sdf-generator "1.1.1"
troika-three-utils@^0.46.0:
version "0.46.0"
resolved "https://registry.yarnpkg.com/troika-three-utils/-/troika-three-utils-0.46.0.tgz#6d97a9bf08f2260285edf2bb0be6328dd3d50eec"
integrity sha512-llHyrXAcwzr0bpg80GxsIp73N7FuImm4WCrKDJkAqcAsWmE5pfP9+Qzw+oMWK1P/AdHQ79eOrOl9NjyW4aOw0w==
troika-worker-utils@^0.46.0:
version "0.46.0"
resolved "https://registry.yarnpkg.com/troika-worker-utils/-/troika-worker-utils-0.46.0.tgz#1b698090af78b51a27e03881c90237a2e648d6c4"
integrity sha512-bzOx5f2ZBxkFhXtIvDJlLn2AI3bzCkGVbCndl/2dL5QZrwHEKl45OEIilCxYQQWJG1rEbOD9O80tMjoYjw19OA==
tryer@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
ts-node@^10.9.1:
version "10.9.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
dependencies:
"@cspotcode/source-map-support" "^0.8.0"
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
"@tsconfig/node16" "^1.0.2"
acorn "^8.4.1"
acorn-walk "^8.1.1"
arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
tsconfig-paths@^3.14.1:
version "3.14.1"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a"
@ -10395,11 +10133,6 @@ utila@~0.4:
resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c"
integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw=
utility-types@^3.10.0:
version "3.10.0"
resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b"
integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg==
utils-merge@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
@ -10415,6 +10148,11 @@ uuid@^9.0.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
v8-to-istanbul@^8.1.0:
version "8.1.1"
resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed"
@ -10482,16 +10220,6 @@ web-vitals@^2.1.0:
resolved "https://registry.yarnpkg.com/web-vitals/-/web-vitals-2.1.4.tgz#76563175a475a5e835264d373704f9dde718290c"
integrity sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==
webgl-constants@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/webgl-constants/-/webgl-constants-1.1.1.tgz#f9633ee87fea56647a60b9ce735cbdfb891c6855"
integrity sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==
webgl-sdf-generator@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz#3e1b422b3d87cd3cc77f2602c9db63bc0f6accbd"
integrity sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
@ -11005,21 +10733,16 @@ yarn@^1.22.19:
resolved "https://registry.yarnpkg.com/yarn/-/yarn-1.22.19.tgz#4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
integrity sha512-/0V5q0WbslqnwP91tirOvldvYISzaqhClxzyUKXYxs07yUILIs5jx/k6CFe8bvKSkds5w+eiOqta39Wk3WxdcQ==
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
zstddec@^0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/zstddec/-/zstddec-0.0.2.tgz#57e2f28dd1ff56b750e07d158a43f0611ad9eeb4"
integrity sha512-DCo0oxvcvOTGP/f5FA6tz2Z6wF+FIcEApSTu0zV5sQgn9hoT5lZ9YRAKUraxt9oP7l4e8TnNdi8IZTCX6WCkwA==
zustand@^3.5.13, zustand@^3.7.1:
version "3.7.2"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-3.7.2.tgz#7b44c4f4a5bfd7a8296a3957b13e1c346f42514d"
integrity sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==
zustand@^4.1.4:
version "4.1.4"
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.1.4.tgz#b0286da4cc9edd35e91c96414fa54bfa4652a54d"