massive overhall to how program memory works and how artifacts their metada are rendered

This commit is contained in:
Kurt Hutten IrevDev
2023-01-08 16:37:31 +11:00
parent 0515acf459
commit 9ad6b946c0
13 changed files with 1090 additions and 884 deletions

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"cSpell.words": [
"geos"
]
}

View File

@ -4,7 +4,7 @@ import { Allotment } from 'allotment'
import { OrbitControls, OrthographicCamera } from '@react-three/drei'
import { lexer } from './lang/tokeniser'
import { abstractSyntaxTree } from './lang/abstractSyntaxTree'
import { executor, processShownObjects, ViewerArtifact } from './lang/executor'
import { executor, ExtrudeGroup, SketchGroup } from './lang/executor'
import { recast } from './lang/recast'
import CodeMirror from '@uiw/react-codemirror'
import { javascript } from '@codemirror/lang-javascript'
@ -78,7 +78,7 @@ function App() {
if (isNoChange) return
setSelectionRange([range.from, range.to])
}
const [geoArray, setGeoArray] = useState<ViewerArtifact[]>([])
const [geoArray, setGeoArray] = useState<(ExtrudeGroup | SketchGroup)[]>([])
useEffect(() => {
try {
if (!code) {
@ -92,26 +92,44 @@ function App() {
setAst(_ast)
const programMemory = executor(_ast, {
root: {
log: (a: any) => {
log: {
type: 'userVal',
value: (a: any) => {
console.log('raw log', a)
let b = a
if (Array.isArray(a)) {
b = a.map(({ geo, ...rest }) => rest)
b = a.map(({ geo, __geoMeta, ...rest }) => rest)
b = JSON.stringify(b, null, 2)
} else if (typeof a === 'object') {
b = JSON.stringify(a, null, 2)
const { geo, __geoMeta, ...rest } = a
b = JSON.stringify(rest, null, 2)
}
addLog(b)
},
__meta: [
{
pathToNode: [],
sourceRange: [0, 0],
},
],
},
},
_sketch: [],
})
setProgramMemory(programMemory)
const geos: ViewerArtifact[] =
programMemory?.return?.flatMap(
({ name }: { name: string }) =>
processShownObjects(programMemory, programMemory?.root?.[name]) ||
[]
) || []
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)[]
setGeoArray(geos)
removeError()
console.log(programMemory)
@ -177,9 +195,7 @@ function App() {
/>
<ambientLight />
<pointLight position={[10, 10, 10]} />
{geoArray.map((artifact, index) => (
<RenderViewerArtifacts artifact={artifact} key={index} />
))}
<RenderViewerArtifacts artifacts={geoArray} />
<BasePlanes />
<SketchPlane />
<AxisIndicator />

View File

@ -34,7 +34,7 @@ export const Toolbar = () => {
mode: 'sketch',
sketchMode: 'sketchEdit',
pathToNode: guiMode.pathToNode,
quaternion: guiMode.quaternion,
rotation: guiMode.rotation,
position: guiMode.position,
})
}}

View File

@ -65,7 +65,7 @@ export const BasePlanes = () => {
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
quaternion,
rotation: quaternion.toArray() as [number, number, number, number],
position: [0, 0, 0],
pathToNode,
})

View File

@ -3,110 +3,22 @@ import {
getNodePathFromSourceRange,
getNodeFromPath,
CallExpression,
VariableDeclarator,
} from '../lang/abstractSyntaxTree'
import { changeArguments } from '../lang/modifyAst'
import { ViewerArtifact } from '../lang/executor'
import {
ExtrudeGroup,
ExtrudeSurface,
SketchGroup,
Path,
Rotation,
Position,
} from '../lang/executor'
import { BufferGeometry } from 'three'
import { useStore } from '../useStore'
import { isOverlapping } from '../lib/utils'
import { LineGeos } from '../lang/engine'
import { Vector3, DoubleSide, Quaternion, Vector2 } from 'three'
import { combineTransformsAlt } from '../lang/sketch'
import { Vector3, DoubleSide, Quaternion } from 'three'
import { useSetCursor } from '../hooks/useSetCursor'
function SketchLine({
geo,
sourceRange,
forceHighlight = false,
}: {
geo: LineGeos
sourceRange: [number, number]
forceHighlight?: boolean
}) {
const { setHighlightRange } = useStore(({ setHighlightRange }) => ({
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)
return (
<>
<mesh
ref={ref}
onPointerOver={(event) => {
setHover(true)
setHighlightRange(sourceRange)
}}
onPointerOut={(event) => {
setHover(false)
setHighlightRange([0, 0])
}}
onClick={onClick}
>
<primitive object={geo.line} />
<meshStandardMaterial
color={hovered ? 'hotpink' : forceHighlight ? 'skyblue' : 'orange'}
/>
</mesh>
<MovingSphere
geo={geo.tip}
sourceRange={sourceRange}
editorCursor={forceHighlight}
/>
</>
)
}
function ExtrudeWall({
geo,
sourceRange,
forceHighlight = false,
}: {
geo: BufferGeometry
sourceRange: [number, number]
forceHighlight?: boolean
}) {
const { setHighlightRange } = useStore(
({ setHighlightRange, selectionRange, guiMode, setGuiMode, ast }) => ({
setHighlightRange,
selectionRange,
guiMode,
setGuiMode,
ast,
})
)
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)
return (
<>
<mesh
ref={ref}
onPointerOver={(event) => {
setHover(true)
setHighlightRange(sourceRange)
}}
onPointerOut={(event) => {
setHover(false)
setHighlightRange([0, 0])
}}
onClick={onClick}
>
<primitive object={geo} />
<meshStandardMaterial
side={DoubleSide}
color={hovered ? 'hotpink' : forceHighlight ? 'skyblue' : 'orange'}
/>
</mesh>
</>
)
}
const roundOff = (num: number, places: number): number => {
const x = Math.pow(10, places)
return Math.round(num * x) / x
@ -116,15 +28,19 @@ function MovingSphere({
geo,
sourceRange,
editorCursor,
rotation,
position,
}: {
geo: BufferGeometry
sourceRange: [number, number]
editorCursor: boolean
rotation: Rotation
position: Position
}) {
const ref = useRef<BufferGeometry | undefined>() as any
const detectionPlaneRef = useRef<BufferGeometry | undefined>() as any
const lastPointerRef = useRef<Vector3>(new Vector3())
const point2DRef = useRef<Vector2>(new Vector2())
const point2DRef = useRef<Vector3>(new Vector3())
const [hovered, setHover] = useState(false)
const [isMouseDown, setIsMouseDown] = useState(false)
@ -154,14 +70,22 @@ function MovingSphere({
const handleMouseUp = () => {
if (isMouseDown && ast) {
const thePath = getNodePathFromSourceRange(ast, sourceRange)
let [x, y] = [
roundOff(point2DRef.current.x, 2),
roundOff(point2DRef.current.y, 2),
]
const yo = point2DRef.current.clone()
const inverseQuaternion = new Quaternion()
if (
guiMode.mode === 'canEditSketch' ||
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
) {
inverseQuaternion.set(...guiMode.rotation)
inverseQuaternion.invert()
}
yo.sub(new Vector3(...position).applyQuaternion(inverseQuaternion))
let [x, y] = [roundOff(yo.x, 2), roundOff(yo.y, 2)]
let theNewPoints: [number, number] = [x, y]
const { modifiedAst } = changeArguments(ast, thePath, theNewPoints)
updateAst(modifiedAst)
ref.current.position.set(0, 0, 0)
console.log('reset position')
ref.current.position.set(...position)
}
setIsMouseDown(false)
}
@ -169,31 +93,32 @@ function MovingSphere({
return () => {
window.removeEventListener('mouseup', handleMouseUp)
}
}, [isMouseDown, ast])
}, [isMouseDown])
let clickDetectPlaneQuaternion = new Quaternion()
let position = new Vector3(0, 0, 0)
if (
const inEditMode =
guiMode.mode === 'canEditSketch' ||
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
) {
clickDetectPlaneQuaternion = guiMode.quaternion.clone()
position = new Vector3(...guiMode.position)
let clickDetectPlaneQuaternion = new Quaternion()
if (inEditMode) {
clickDetectPlaneQuaternion = new Quaternion(...rotation)
}
return (
<>
<mesh
position={position}
quaternion={rotation}
ref={ref}
onPointerOver={(event) => {
setHover(true)
inEditMode && setHover(true)
setHighlightRange(sourceRange)
}}
onPointerOut={(event) => {
setHover(false)
setHighlightRange([0, 0])
}}
onPointerDown={() => setIsMouseDown(true)}
onPointerDown={() => inEditMode && setIsMouseDown(true)}
>
<primitive object={geo} scale={hovered ? 2 : 1} />
<meshStandardMaterial
@ -202,7 +127,6 @@ function MovingSphere({
</mesh>
{isMouseDown && (
<mesh
position={position}
quaternion={clickDetectPlaneQuaternion}
onPointerMove={(a) => {
const point = a.point
@ -213,13 +137,11 @@ function MovingSphere({
guiMode.mode === 'canEditSketch' ||
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
) {
inverseQuaternion.copy(guiMode.quaternion.clone().invert())
inverseQuaternion.set(...guiMode.rotation)
inverseQuaternion.invert()
}
transformedPoint.applyQuaternion(inverseQuaternion)
transformedPoint.sub(
position.clone().applyQuaternion(inverseQuaternion)
)
point2DRef.current.set(transformedPoint.x, transformedPoint.y)
point2DRef.current.copy(transformedPoint)
if (
lastPointerRef.current.x === 0 &&
@ -248,7 +170,7 @@ function MovingSphere({
ref.current.position.add(
diff.applyQuaternion(inverseQuaternion.invert())
)
lastPointerRef.current.set(point.x, point.y, point.z)
lastPointerRef.current.copy(point.clone())
}
}}
>
@ -266,86 +188,258 @@ function MovingSphere({
}
export function RenderViewerArtifacts({
artifact,
forceHighlight = false,
artifacts,
}: {
artifact: ViewerArtifact
forceHighlight?: boolean
artifacts: (ExtrudeGroup | SketchGroup)[]
}) {
const { selectionRange, guiMode, ast, setGuiMode, programMemory } = useStore(
({ selectionRange, guiMode, ast, setGuiMode, programMemory }) => ({
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,
programMemory,
})
)
const [editorCursor, setEditorCursor] = useState(false)
useEffect(() => {
const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange)
setEditorCursor(shouldHighlight && artifact.type !== 'sketch')
}, [selectionRange, artifact.sourceRange])
const shouldHighlight = isOverlapping(
artifact.__meta.slice(-1)[0].sourceRange,
selectionRange
)
setEditorCursor(shouldHighlight)
}, [selectionRange, artifact.__meta])
useEffect(() => {
const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange)
const shouldHighlight = artifact.__meta.some((aMeta) =>
isOverlapping(aMeta.sourceRange, selectionRange)
)
if (
shouldHighlight &&
(guiMode.mode === 'default' || guiMode.mode === 'canEditSketch') &&
artifact.type === 'sketch' &&
ast
ast &&
artifact.type === 'sketchGroup'
) {
const pathToNode = getNodePathFromSourceRange(ast, artifact.sourceRange)
const varDec: VariableDeclarator = getNodeFromPath(
const pathToNode = getNodePathFromSourceRange(
ast,
pathToNode,
'VariableDeclarator'
artifact.__meta[0].sourceRange
)
const varName = varDec?.id?.name
const { quaternion, position } = combineTransformsAlt(
programMemory.root[varName]
)
setGuiMode({ mode: 'canEditSketch', pathToNode, quaternion, position })
const { rotation, position } = artifact
setGuiMode({ mode: 'canEditSketch', pathToNode, rotation, position })
} else if (
!shouldHighlight &&
guiMode.mode === 'canEditSketch' &&
artifact.type === 'sketch'
artifact.type === 'sketchGroup'
) {
setGuiMode({ mode: 'default' })
}
}, [selectionRange, artifact.sourceRange, ast, guiMode.mode, setGuiMode])
if (artifact.type === 'sketchLine') {
const { geo, sourceRange } = artifact
return (
<SketchLine
geo={geo}
sourceRange={sourceRange}
forceHighlight={forceHighlight || editorCursor}
/>
)
}
if (artifact.type === 'sketchBase') {
console.log('BASE TODO')
return null
}
if (artifact.type === 'extrudeWall') {
return (
<ExtrudeWall
geo={artifact.geo}
sourceRange={artifact.sourceRange}
forceHighlight={forceHighlight || editorCursor}
/>
)
}
}, [selectionRange, artifact, ast, guiMode.mode, setGuiMode])
if (artifact.type === 'sketchGroup') {
return (
<>
{artifact.children.map((artifact, index) => (
<RenderViewerArtifacts
artifact={artifact}
key={index}
forceHighlight={forceHighlight || editorCursor}
{artifact.value.map((geoInfo, key) => (
<PathRender
geoInfo={geoInfo}
key={key}
forceHighlight={editorCursor}
rotation={artifact.rotation}
position={artifact.position}
/>
))}
</>
)
}
if (artifact.type === 'extrudeGroup') {
return (
<>
{artifact.value.map((geoInfo, key) => (
<WallRender
geoInfo={geoInfo}
key={key}
forceHighlight={editorCursor}
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, selectionRange } = useStore(
({ setHighlightRange, selectionRange }) => ({
setHighlightRange,
selectionRange,
})
)
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 = isOverlapping(
geoInfo.__geoMeta.sourceRange,
selectionRange
)
setEditorCursor(shouldHighlight)
}, [selectionRange, 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 { selectionRange } = useStore(({ selectionRange }) => ({
selectionRange,
}))
const [editorCursor, setEditorCursor] = useState(false)
useEffect(() => {
const shouldHighlight = isOverlapping(
geoInfo.__geoMeta.sourceRange,
selectionRange
)
setEditorCursor(shouldHighlight)
}, [selectionRange, geoInfo])
return (
<>
{geoInfo.__geoMeta.geos.map((meta, i) => {
if (meta.type === 'line') {
return (
<LineRender
key={i}
geo={meta.geo}
sourceRange={geoInfo.__geoMeta.sourceRange}
forceHighlight={forceHighlight || editorCursor}
rotation={rotation}
position={position}
/>
)
}
if (meta.type === 'lineEnd') {
return (
<MovingSphere
key={i}
geo={meta.geo}
sourceRange={geoInfo.__geoMeta.sourceRange}
editorCursor={forceHighlight || editorCursor}
rotation={rotation}
position={position}
/>
)
}
})}
</>
)
}
function LineRender({
geo,
sourceRange,
forceHighlight = false,
rotation,
position,
}: {
geo: BufferGeometry
sourceRange: [number, number]
forceHighlight?: boolean
rotation: Rotation
position: Position
}) {
const { setHighlightRange } = useStore(({ setHighlightRange }) => ({
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)
return (
<>
<mesh
quaternion={rotation}
position={position}
ref={ref}
onPointerOver={(event) => {
setHover(true)
setHighlightRange(sourceRange)
}}
onPointerOut={(event) => {
setHover(false)
setHighlightRange([0, 0])
}}
onClick={onClick}
>
<primitive object={geo} />
<meshStandardMaterial
color={hovered ? 'hotpink' : forceHighlight ? 'skyblue' : 'orange'}
/>
</mesh>
</>
)
}

View File

@ -20,7 +20,7 @@ export const SketchPlane = () => {
const sketchGridName = 'sketchGrid'
let clickDetectQuaternion = guiMode.quaternion.clone()
let clickDetectQuaternion = new Quaternion(...guiMode.rotation)
let temp = new Quaternion().setFromAxisAngle(
new Vector3(1, 0, 0),
@ -28,7 +28,7 @@ export const SketchPlane = () => {
)
let position = guiMode.position
const gridQuaternion = new Quaternion().multiplyQuaternions(
guiMode.quaternion,
new Quaternion(...guiMode.rotation),
temp
)
@ -47,8 +47,12 @@ export const SketchPlane = () => {
)
const inverseQuaternion = clickDetectQuaternion.clone().invert()
let transformedPoint = sketchGridIntersection?.point.clone()
if (transformedPoint)
if (transformedPoint) {
transformedPoint.applyQuaternion(inverseQuaternion)
transformedPoint?.sub(
new Vector3(...position).applyQuaternion(inverseQuaternion)
)
}
const point = roundy(transformedPoint)
let _ast: Program = ast

View File

@ -453,7 +453,7 @@ export interface Literal extends GeneralStatement {
raw: string
}
interface Identifier extends GeneralStatement {
export interface Identifier extends GeneralStatement {
type: 'Identifier'
name: string
}

View File

@ -1,9 +1,9 @@
import { abstractSyntaxTree } from './abstractSyntaxTree'
import { lexer } from './tokeniser'
import { executor, ViewerArtifact, processShownObjects } from './executor'
import { executor, SketchGroup, ExtrudeGroup } from './executor'
describe('findClosingBrace', () => {
test('finds the closing brace', () => {
describe('testing artifacts', () => {
test('sketch artifacts', () => {
const code = `
sketch mySketch001 {
lineTo(-1.59, -1.54)
@ -12,34 +12,86 @@ sketch mySketch001 {
|> rx(45, %)
show(mySketch001)`
const programMemory = executor(abstractSyntaxTree(lexer(code)))
const geos: ViewerArtifact[] =
programMemory?.return?.flatMap(
({ name }: { name: string }) =>
processShownObjects(programMemory, programMemory?.root?.[name]) || []
) || []
const artifactsWithouGeos = removeGeo(geos)
expect(artifactsWithouGeos).toEqual([
const geos = programMemory?.return?.map(
(a) => programMemory?.root?.[a.name]
)
const artifactsWithoutGeos = removeGeo(geos as any)
expect(artifactsWithoutGeos).toEqual([
{
type: 'parent',
sourceRange: [74, 83],
children: [
type: 'sketchGroup',
value: [
{
type: 'sketch',
sourceRange: [20, 68],
children: [
{
type: 'sketchBase',
sourceRange: [0, 0],
},
{
type: 'sketchLine',
type: 'toPoint',
to: [-1.59, -1.54],
from: [0, 0],
__geoMeta: {
sourceRange: [24, 44],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
},
{
type: 'sketchLine',
type: 'toPoint',
to: [0.46, -5.82],
from: [-1.59, -1.54],
__geoMeta: {
sourceRange: [47, 66],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
},
],
position: [0, 0, 0],
rotation: [0.3826834323650898, 0, 0, 0.9238795325112867],
__meta: [
{
sourceRange: [20, 68],
pathToNode: ['body', 0, 'declarations', 0, 'init', 0],
},
{
sourceRange: [74, 83],
pathToNode: [],
},
],
},
])
})
test('extrude artifacts', () => {
const code = `
sketch mySketch001 {
lineTo(-1.59, -1.54)
lineTo(0.46, -5.82)
}
|> rx(45, %)
|> extrude(2, %)
show(mySketch001)`
const programMemory = executor(abstractSyntaxTree(lexer(code)))
const geos = programMemory?.return?.map(
(a) => programMemory?.root?.[a.name]
)
const artifactsWithoutGeos = removeGeo(geos as any)
expect(artifactsWithoutGeos).toEqual([
{
type: 'extrudeGroup',
value: [
{
type: 'extrudePlane',
position: [0, 0, 0],
rotation: [0.3826834323650898, 0, 0, 0.9238795325112867],
__geoMeta: {
geo: 'PlaneGeometry',
sourceRange: [47, 66],
pathToNode: [],
},
},
],
height: 2,
position: [0, 0, 0],
rotation: [0.3826834323650898, 0, 0, 0.9238795325112867],
__meta: [
{
sourceRange: [89, 102],
pathToNode: [],
},
],
},
@ -47,24 +99,29 @@ show(mySketch001)`
})
})
function removeGeo(arts: ViewerArtifact[]): any {
function removeGeo(arts: (SketchGroup | ExtrudeGroup)[]): any {
return arts.map((art) => {
if (art.type === 'sketchLine' || art.type === 'sketchBase') {
const { geo, ...rest } = art
return rest
}
if (art.type === 'parent') {
if (art.type === 'extrudeGroup') {
return {
...art,
children: removeGeo(art.children),
value: art.value.map((v) => ({
...v,
__geoMeta: {
...v.__geoMeta,
geo: v.__geoMeta.geo.type,
},
})),
}
}
if (art.type === 'sketch') {
return {
...art,
children: removeGeo(art.children),
value: art.value.map((v) => ({
...v,
__geoMeta: {
...v.__geoMeta,
geos: v.__geoMeta.geos.map((g) => g.type),
},
})),
}
}
return art
})
}

View File

@ -44,19 +44,17 @@ function trigCalcs({
}
}
export interface LineGeos {
line: BufferGeometry
tip: BufferGeometry
centre: BufferGeometry
}
export function lineGeo({
from,
to,
}: {
from: [number, number, number]
to: [number, number, number]
}): LineGeos {
}): {
line: BufferGeometry
tip: BufferGeometry
centre: BufferGeometry
} {
const {
centre,
Hypotenuse: Hypotenuse3d,

View File

@ -2,21 +2,20 @@ import fs from 'node:fs'
import { abstractSyntaxTree } from './abstractSyntaxTree'
import { lexer } from './tokeniser'
import { executor, ProgramMemory } from './executor'
import { Transform, SketchGeo } from './sketch'
import { executor, ProgramMemory, Path, SketchGroup } from './executor'
describe('test', () => {
it('test assigning two variables, the second summing with the first', () => {
const code = `const myVar = 5
const newVar = myVar + 1`
const { root } = exe(code)
expect(root.myVar).toBe(5)
expect(root.newVar).toBe(6)
expect(root.myVar.value).toBe(5)
expect(root.newVar.value).toBe(6)
})
it('test assigning a var with a string', () => {
const code = `const myVar = "a str"`
const { root } = exe(code)
expect(root.myVar).toBe('a str')
expect(root.myVar.value).toBe('a str')
})
it('test assigning a var by cont concatenating two strings string execute', () => {
const code = fs.readFileSync(
@ -24,21 +23,30 @@ const newVar = myVar + 1`
'utf-8'
)
const { root } = exe(code)
expect(root.myVar).toBe('a str another str')
expect(root.myVar.value).toBe('a str another str')
})
it('test with function call', () => {
const code = `
const myVar = "hello"
log(5, myVar)`
const programMemoryOverride = {
log: jest.fn(),
const programMemoryOverride: ProgramMemory['root'] = {
log: {
type: 'userVal',
value: jest.fn(),
__meta: [
{
sourceRange: [0, 0],
pathToNode: [],
},
],
},
}
const { root } = executor(abstractSyntaxTree(lexer(code)), {
root: programMemoryOverride,
_sketch: [],
})
expect(root.myVar).toBe('hello')
expect(programMemoryOverride.log).toHaveBeenCalledWith(5, 'hello')
expect(root.myVar.value).toBe('hello')
expect(programMemoryOverride.log.value).toHaveBeenCalledWith(5, 'hello')
})
it('fn funcN = () => {} execute', () => {
const { root } = exe(
@ -50,39 +58,71 @@ log(5, myVar)`
'const magicNum = funcN(9, theVar)',
].join('\n')
)
expect(root.theVar).toBe(60)
expect(root.magicNum).toBe(69)
expect(root.theVar.value).toBe(60)
expect(root.magicNum.value).toBe(69)
})
it('sketch declaration', () => {
let code = `sketch mySketch {
path myPath = lineTo(0,1)
lineTo(1,1)
path rightPath = lineTo(1,0)
path myPath = lineTo(0,2)
lineTo(2,3)
path rightPath = lineTo(5,-1)
close()
}
show(mySketch)
`
const { root, return: _return } = exe(code)
expect(
root.mySketch.sketch.map(
({ previousPath, firstPath, geo, ...rest }: any) => rest
)
).toEqual([
{ type: 'base', from: [0, 0], sourceRange: [0, 0] },
{ type: 'toPoint', to: [0, 1], sourceRange: [25, 45], name: 'myPath' },
{ type: 'toPoint', to: [1, 1], sourceRange: [48, 59] },
{ type: 'toPoint', to: [1, 0], sourceRange: [67, 90], name: 'rightPath' },
// geo is three js buffer geometry and is very bloated to have in tests
const minusGeo = removeGeoFromPaths(root.mySketch.value)
expect(minusGeo).toEqual([
{
type: 'close',
sourceRange: [93, 100],
type: 'toPoint',
to: [0, 2],
from: [5, -1],
__geoMeta: {
sourceRange: [25, 45],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
name: 'myPath',
},
{
type: 'toPoint',
to: [2, 3],
from: [0, 2],
__geoMeta: {
sourceRange: [48, 59],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
},
{
type: 'toPoint',
to: [5, -1],
from: [2, 3],
__geoMeta: {
sourceRange: [67, 91],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
name: 'rightPath',
},
{
type: 'toPoint',
from: [5, -1],
to: [0, 2],
__geoMeta: {
sourceRange: [94, 101],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
},
])
// expect(root.mySketch.sketch[0]).toEqual(root.mySketch.sketch[4].firstPath)
expect(_return).toEqual([
{
type: 'Identifier',
start: 108,
end: 116,
start: 109,
end: 117,
name: 'mySketch',
},
])
@ -94,7 +134,7 @@ show(mySketch)
'const myVar = 5 + 1 |> myFn(%)',
].join('\n')
const { root } = exe(code)
expect(root.myVar).toBe(7)
expect(root.myVar.value).toBe(7)
})
it('rotated sketch', () => {
@ -108,8 +148,20 @@ show(mySketch)
// 'show(mySk1)',
].join('\n')
const { root } = exe(code)
expect(root.mySk1.sketch).toHaveLength(4)
expect(root?.rotated?.type).toBe('transform')
expect(root.mySk1.value).toHaveLength(3)
expect(root?.rotated?.type).toBe('sketchGroup')
if (
root?.mySk1?.type !== 'sketchGroup' ||
root?.rotated?.type !== 'sketchGroup'
)
throw new Error('not a sketch group')
expect(root.mySk1.rotation).toEqual([0, 0, 0, 1])
expect(root.rotated.rotation.map((a) => a.toFixed(4))).toEqual([
'0.7071',
'0.0000',
'0.0000',
'0.7071',
])
})
it('execute pipe sketch into call expression', () => {
@ -121,73 +173,87 @@ show(mySketch)
'} |> rx(90, %)',
].join('\n')
const { root } = exe(code)
const striptVersion = removeGeoFromSketch(root.mySk1)
const striptVersion = removeGeoFromSketch(root.mySk1 as SketchGroup)
expect(striptVersion).toEqual({
type: 'sketchGeo',
sketch: [
{
type: 'base',
from: [0, 0],
sourceRange: [0, 0],
},
type: 'sketchGroup',
value: [
{
type: 'toPoint',
to: [1, 1],
from: [0, 0],
__geoMeta: {
sourceRange: [17, 28],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
},
{
type: 'toPoint',
to: [0, 1],
from: [1, 1],
__geoMeta: {
sourceRange: [36, 57],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
name: 'myPath',
},
{
type: 'toPoint',
to: [1, 1],
from: [0, 1],
__geoMeta: {
sourceRange: [60, 71],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
},
],
position: [0, 0, 0],
rotation: [0.7071067811865475, 0, 0, 0.7071067811865476],
__meta: [
{
sourceRange: [13, 73],
pathToNode: ['body', 0, 'declarations', 0, 'init', 0],
},
{
sourceRange: [77, 86],
pathToNode: [],
},
],
})
// old expect
// expect(striptVersion).toEqual({
// type: 'transform',
// rotation: [1.5707963267948966, 0, 0],
// transform: [0, 0, 0],
// sketch: [
// {
// type: 'base',
// from: [0, 0],
// sourceRange: [0, 0],
// },
// {
// type: 'toPoint',
// to: [1, 1],
// sourceRange: [17, 28],
// },
// {
// type: 'toPoint',
// to: [0, 1],
// sourceRange: [36, 57],
// name: 'myPath',
// },
// {
// type: 'toPoint',
// to: [1, 1],
// sourceRange: [60, 71],
// },
// ],
// sourceRange: [77, 86],
// })
})
it('execute array expression', () => {
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
'\n'
)
const { root } = exe(code)
// TODO path to node is probably wrong here, zero indexes are not correct
expect(root).toEqual({
three: 3,
yo: [1, '2', 3, 9],
three: {
type: 'userVal',
value: 3,
__meta: [
{
pathToNode: ['body', 0, 'declarations', 0, 'init'],
sourceRange: [14, 15],
},
],
},
yo: {
type: 'userVal',
value: [1, '2', 3, 9],
__meta: [
{
pathToNode: ['body', 1, 'declarations', 0, 'init'],
sourceRange: [27, 49],
},
{
pathToNode: ['body', 0, 'declarations', 0, 'init'],
sourceRange: [14, 15],
},
],
},
})
})
it('execute object expression', () => {
@ -196,14 +262,20 @@ show(mySketch)
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const { root } = exe(code)
expect(root).toEqual({
three: 3,
yo: {
expect(root.yo).toEqual({
type: 'userVal',
value: {
aStr: 'str',
anum: 2,
identifier: 3,
binExp: 9,
},
__meta: [
{
pathToNode: ['body', 1, 'declarations', 0, 'init'],
sourceRange: [27, 83],
},
],
})
})
it('execute memberExpression', () => {
@ -211,11 +283,15 @@ show(mySketch)
'\n'
)
const { root } = exe(code)
expect(root).toEqual({
yo: {
a: { b: '123' },
expect(root.myVar).toEqual({
type: 'userVal',
value: '123',
__meta: [
{
pathToNode: ['body', 1, 'declarations', 0, 'init'],
sourceRange: [41, 50],
},
myVar: '123',
],
})
})
})
@ -231,15 +307,22 @@ function exe(
return executor(ast, programMemory)
}
function removeGeoFromSketch(sketch: Transform | SketchGeo): any {
if (sketch.type !== 'sketchGeo' && sketch.type === 'transform') {
return removeGeoFromSketch(sketch.sketch as any) // TODO fix type
}
if (sketch.type === 'sketchGeo') {
function removeGeoFromSketch(sketch: SketchGroup): any {
return {
...sketch,
sketch: sketch.sketch.map(({ geo, previousPath, ...rest }: any) => rest),
value: removeGeoFromPaths(sketch.value),
}
}
throw new Error('not a sketch')
function removeGeoFromPaths(paths: Path[]): any[] {
return paths.map((path: Path) => {
const newGeos = path?.__geoMeta?.geos.map((geo) => geo.type)
return {
...path,
__geoMeta: {
...path.__geoMeta,
geos: newGeos,
},
}
})
}

View File

@ -5,21 +5,108 @@ import {
PipeExpression,
ObjectExpression,
MemberExpression,
Identifier,
} from './abstractSyntaxTree'
import { Path, Transform, SketchGeo, sketchFns, ExtrudeGeo } from './sketch'
import { BufferGeometry, Quaternion, Vector3 } from 'three'
import { LineGeos } from './engine'
import { sketchFns } from './sketch'
import { BufferGeometry } from 'three'
export type SourceRange = [number, number]
export type PathToNode = (string | number)[]
export type Metadata = {
sourceRange: SourceRange
pathToNode: PathToNode
}
export type Position = [number, number, number]
export type Rotation = [number, number, number, number]
interface BasePath {
from: [number, number]
to: [number, number]
name?: string
__geoMeta: {
geos: {
geo: BufferGeometry
type: 'line' | 'lineEnd'
}[]
sourceRange: SourceRange
pathToNode: PathToNode
}
}
export interface ToPoint extends BasePath {
type: 'toPoint'
}
export interface HorizontalLineTo extends BasePath {
type: 'horizontalLineTo'
x: number
}
export interface AngledLineTo extends BasePath {
type: 'angledLineTo'
angle: number
x?: number
y?: number
}
interface GeoMeta {
__geoMeta: {
geo: BufferGeometry
sourceRange: SourceRange
pathToNode: PathToNode
}
}
export type Path = ToPoint | HorizontalLineTo | AngledLineTo
export interface SketchGroup {
type: 'sketchGroup'
value: Path[]
position: Position
rotation: Rotation
__meta: Metadata[]
}
interface ExtrudePlane {
type: 'extrudePlane'
position: Position
rotation: Rotation
}
export type ExtrudeSurface = GeoMeta &
ExtrudePlane /* | ExtrudeRadius | ExtrudeSpline */
export interface ExtrudeGroup {
type: 'extrudeGroup'
value: ExtrudeSurface[]
height: number
position: Position
rotation: Rotation
__meta: Metadata[]
}
/** UserVal not produced by one of our internal functions */
export interface UserVal {
type: 'userVal'
value: any
__meta: Metadata[]
}
interface Memory {
[key: string]: UserVal | SketchGroup | ExtrudeGroup // | Memory
}
export interface ProgramMemory {
root: { [key: string]: any }
return?: any
_sketch: Path[]
root: Memory
return?: Identifier[]
_sketch?: Path[]
}
export const executor = (
node: Program,
programMemory: ProgramMemory = { root: {}, _sketch: [] },
options: { bodyType: 'root' | 'sketch' | 'block' } = { bodyType: 'root' }
options: { bodyType: 'root' | 'sketch' | 'block' } = { bodyType: 'root' },
previousPathToNode: PathToNode = []
): ProgramMemory => {
const _programMemory: ProgramMemory = {
root: {
@ -29,33 +116,82 @@ export const executor = (
return: programMemory.return,
}
const { body } = node
body.forEach((statement) => {
body.forEach((statement, bodyIndex) => {
if (statement.type === 'VariableDeclaration') {
statement.declarations.forEach((declaration) => {
statement.declarations.forEach((declaration, index) => {
const variableName = declaration.id.name
const pathToNode = [
...previousPathToNode,
'body',
bodyIndex,
'declarations',
index,
'init',
]
const sourceRange: SourceRange = [
declaration.init.start,
declaration.init.end,
]
const __meta: Metadata[] = [
{
pathToNode,
sourceRange,
},
]
if (declaration.init.type === 'PipeExpression') {
_programMemory.root[variableName] = getPipeExpressionResult(
const value = getPipeExpressionResult(
declaration.init,
_programMemory
_programMemory,
pathToNode
)
if (value?.type === 'sketchGroup' || value?.type === 'extrudeGroup') {
_programMemory.root[variableName] = value
} else {
_programMemory.root[variableName] = {
type: 'userVal',
value,
__meta,
}
}
} else if (declaration.init.type === 'Literal') {
_programMemory.root[variableName] = declaration.init.value
_programMemory.root[variableName] = {
type: 'userVal',
value: declaration.init.value,
__meta,
}
} else if (declaration.init.type === 'BinaryExpression') {
_programMemory.root[variableName] = getBinaryExpressionResult(
declaration.init,
_programMemory
)
_programMemory.root[variableName] = {
type: 'userVal',
value: getBinaryExpressionResult(declaration.init, _programMemory),
__meta,
}
} else if (declaration.init.type === 'ArrayExpression') {
_programMemory.root[variableName] = declaration.init.elements.map(
(element) => {
const valueInfo: { value: any; __meta?: Metadata }[] =
declaration.init.elements.map(
(element): { value: any; __meta?: Metadata } => {
if (element.type === 'Literal') {
return element.value
return {
value: element.value,
}
} else if (element.type === 'BinaryExpression') {
return getBinaryExpressionResult(element, _programMemory)
return {
value: getBinaryExpressionResult(element, _programMemory),
}
} else if (element.type === 'PipeExpression') {
return getPipeExpressionResult(element, _programMemory)
return {
value: getPipeExpressionResult(
element,
_programMemory,
pathToNode
),
}
} else if (element.type === 'Identifier') {
return _programMemory.root[element.name]
const node = _programMemory.root[element.name]
return {
value: node.value,
__meta: node.__meta[node.__meta.length - 1],
}
} else {
throw new Error(
`Unexpected element type ${element.type} in array expression`
@ -63,9 +199,20 @@ export const executor = (
}
}
)
const meta = valueInfo
.filter(({ __meta }) => __meta)
.map(({ __meta }) => __meta) as Metadata[]
_programMemory.root[variableName] = {
type: 'userVal',
value: valueInfo.map(({ value }) => value),
__meta: [...__meta, ...meta],
}
} else if (declaration.init.type === 'ObjectExpression') {
const obj = executeObjectExpression(_programMemory, declaration.init)
_programMemory.root[variableName] = obj
_programMemory.root[variableName] = {
type: 'userVal',
value: executeObjectExpression(_programMemory, declaration.init),
__meta,
}
} else if (declaration.init.type === 'SketchExpression') {
const sketchInit = declaration.init
const fnMemory: ProgramMemory = {
@ -77,26 +224,20 @@ export const executor = (
let { _sketch } = executor(sketchInit.body, fnMemory, {
bodyType: 'sketch',
})
if (_sketch.length === 0) {
const { programMemory: newProgramMemory } = sketchFns.base(
fnMemory,
'',
[0, 0],
0,
0
)
_sketch = newProgramMemory._sketch
}
const newSketch: SketchGeo = {
type: 'sketchGeo',
sketch: _sketch,
sourceRange: [sketchInit.start, sketchInit.end],
const newSketch: SketchGroup = {
type: 'sketchGroup',
value: _sketch || [],
position: [0, 0, 0],
rotation: [0, 0, 0, 1], //x,y,z,w
__meta,
}
_programMemory.root[variableName] = newSketch
} else if (declaration.init.type === 'FunctionExpression') {
const fnInit = declaration.init
_programMemory.root[declaration.id.name] = (...args: any[]) => {
_programMemory.root[declaration.id.name] = {
type: 'userVal',
value: (...args: any[]) => {
const fnMemory: ProgramMemory = {
root: {
..._programMemory.root,
@ -113,28 +254,36 @@ export const executor = (
)
}
fnInit.params.forEach((param, index) => {
fnMemory.root[param.name] = args[index]
fnMemory.root[param.name] = {
type: 'userVal',
value: args[index],
__meta,
}
})
return executor(fnInit.body, fnMemory, { bodyType: 'block' }).return
return executor(fnInit.body, fnMemory, { bodyType: 'block' })
.return
},
__meta,
}
} else if (declaration.init.type === 'MemberExpression') {
_programMemory.root[variableName] = getMemberExpressionResult(
declaration.init,
_programMemory
)
_programMemory.root[variableName] = {
type: 'userVal',
value: getMemberExpressionResult(declaration.init, _programMemory),
__meta,
}
} else if (declaration.init.type === 'CallExpression') {
const functionName = declaration.init.callee.name
const fnArgs = declaration.init.arguments.map((arg) => {
if (arg.type === 'Literal') {
return arg.value
} else if (arg.type === 'Identifier') {
return _programMemory.root[arg.name]
return _programMemory.root[arg.name].value
}
})
if (
'lineTo' === functionName ||
'close' === functionName ||
'base' === functionName
'close' === functionName // ||
// 'base' === functionName
) {
if (options.bodyType !== 'sketch') {
throw new Error(
@ -148,7 +297,11 @@ export const executor = (
...fnArgs
)
_programMemory._sketch = result.programMemory._sketch
_programMemory.root[variableName] = result.currentPath
_programMemory.root[variableName] = {
type: 'userVal',
value: result.currentPath,
__meta,
}
} else if (
'rx' === functionName ||
'ry' === functionName ||
@ -162,9 +315,9 @@ export const executor = (
_programMemory,
[declaration.start, declaration.end],
fnArgs[0],
sketchVal
sketchVal as any // todo memory redo
)
_programMemory.root[variableName] = result
_programMemory.root[variableName] = result as any // todo memory redo
} else if (functionName === 'extrude') {
const sketch = declaration.init.arguments[1]
if (sketch.type !== 'Identifier')
@ -175,9 +328,9 @@ export const executor = (
'yo',
[declaration.start, declaration.end],
fnArgs[0],
sketchVal
sketchVal as any // todo memory redo
)
_programMemory.root[variableName] = result
_programMemory.root[variableName] = result as any // todo memory redo
} else if (functionName === 'translate') {
const sketch = declaration.init.arguments[1]
if (sketch.type !== 'Identifier')
@ -187,13 +340,15 @@ export const executor = (
_programMemory,
[declaration.start, declaration.end],
fnArgs[0],
sketchVal
sketchVal as any // todo memory redo
)
_programMemory.root[variableName] = result
_programMemory.root[variableName] = result as any // todo memory redo
} else {
_programMemory.root[variableName] = _programMemory.root[
functionName
](...fnArgs)
_programMemory.root[variableName] = {
type: 'userVal',
value: _programMemory.root[functionName].value(...fnArgs),
__meta,
}
}
} else {
throw new Error(
@ -209,13 +364,13 @@ export const executor = (
if (arg.type === 'Literal') {
return arg.value
} else if (arg.type === 'Identifier') {
return _programMemory.root[arg.name]
return _programMemory.root[arg.name].value
}
})
if (
'lineTo' === functionName ||
'close' === functionName ||
'base' === functionName
'close' === functionName
// || 'base' === functionName
) {
if (options.bodyType !== 'sketch') {
throw new Error(
@ -228,14 +383,14 @@ export const executor = (
[statement.start, statement.end],
...args
)
_programMemory._sketch = [...result.programMemory._sketch]
_programMemory._sketch = [...(result.programMemory._sketch || [])]
} else if ('show' === functionName) {
if (options.bodyType !== 'root') {
throw new Error(`Cannot call ${functionName} outside of a root`)
}
_programMemory.return = expression.arguments
_programMemory.return = expression.arguments as any // todo memory redo
} else {
_programMemory.root[functionName](...args)
_programMemory.root[functionName].value(...args)
}
}
} else if (statement.type === 'ReturnStatement') {
@ -262,7 +417,7 @@ function getMemberExpressionResult(
const object: any =
expression.object.type === 'MemberExpression'
? getMemberExpressionResult(expression.object, programMemory)
: programMemory.root[expression.object.name]
: programMemory.root[expression.object.name].value
return object[propertyName]
}
@ -274,7 +429,7 @@ function getBinaryExpressionResult(
if (part.type === 'Literal') {
return part.value
} else if (part.type === 'Identifier') {
return programMemory.root[part.name]
return programMemory.root[part.name].value
}
}
const left = getVal(expression.left)
@ -284,9 +439,14 @@ function getBinaryExpressionResult(
function getPipeExpressionResult(
expression: PipeExpression,
programMemory: ProgramMemory
programMemory: ProgramMemory,
previousPathToNode: PathToNode = []
) {
const executedBody = executePipeBody(expression.body, programMemory)
const executedBody = executePipeBody(
expression.body,
programMemory,
previousPathToNode
)
const result = executedBody[executedBody.length - 1]
return result
}
@ -294,6 +454,7 @@ function getPipeExpressionResult(
function executePipeBody(
body: PipeExpression['body'],
programMemory: ProgramMemory,
previousPathToNode: PathToNode = [],
expressionIndex = 0,
previousResults: any[] = []
): any[] {
@ -303,10 +464,13 @@ function executePipeBody(
const expression = body[expressionIndex]
if (expression.type === 'BinaryExpression') {
const result = getBinaryExpressionResult(expression, programMemory)
return executePipeBody(body, programMemory, expressionIndex + 1, [
...previousResults,
result,
])
return executePipeBody(
body,
programMemory,
previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
} else if (expression.type === 'CallExpression') {
const functionName = expression.callee.name
const fnArgs = expression.arguments.map((arg) => {
@ -341,10 +505,13 @@ function executePipeBody(
fnArgs[0],
fnArgs[1]
)
return executePipeBody(body, programMemory, expressionIndex + 1, [
...previousResults,
result,
])
return executePipeBody(
body,
programMemory,
previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
}
if (functionName === 'extrude') {
const result = sketchFns[functionName](
@ -354,10 +521,13 @@ function executePipeBody(
fnArgs[0],
fnArgs[1]
)
return executePipeBody(body, programMemory, expressionIndex + 1, [
...previousResults,
result,
])
return executePipeBody(
body,
programMemory,
previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
}
if (functionName === 'translate') {
const result = sketchFns[functionName](
@ -366,16 +536,22 @@ function executePipeBody(
fnArgs[0],
fnArgs[1]
)
return executePipeBody(body, programMemory, expressionIndex + 1, [
...previousResults,
result,
])
return executePipeBody(
body,
programMemory,
previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
}
const result = programMemory.root[functionName](...fnArgs)
return executePipeBody(body, programMemory, expressionIndex + 1, [
...previousResults,
result,
])
const result = programMemory.root[functionName].value(...fnArgs)
return executePipeBody(
body,
programMemory,
previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
} else if (expression.type === 'SketchExpression') {
const sketchBody = expression.body
const fnMemory: ProgramMemory = {
@ -387,26 +563,25 @@ function executePipeBody(
let { _sketch } = executor(sketchBody, fnMemory, {
bodyType: 'sketch',
})
if (_sketch.length === 0) {
const { programMemory: newProgramMemory } = sketchFns.base(
fnMemory,
'',
[0, 0],
0,
0
)
_sketch = newProgramMemory._sketch
}
// _programMemory.root[variableName] = _sketch
const newSketch: SketchGeo = {
type: 'sketchGeo',
sketch: _sketch,
const newSketch: SketchGroup = {
type: 'sketchGroup',
value: _sketch || [],
position: [0, 0, 0],
rotation: [0, 0, 0, 1], //x,y,z,w
__meta: [
{
sourceRange: [expression.start, expression.end],
pathToNode: [...previousPathToNode, expressionIndex],
},
],
}
return executePipeBody(body, programMemory, expressionIndex + 1, [
...previousResults,
newSketch,
])
return executePipeBody(
body,
programMemory,
previousPathToNode,
expressionIndex + 1,
[...previousResults, newSketch]
)
}
throw new Error('Invalid pipe expression')
@ -432,7 +607,7 @@ function executeObjectExpression(
_programMemory
)
} else if (property.value.type === 'Identifier') {
obj[property.key.name] = _programMemory.root[property.value.name]
obj[property.key.name] = _programMemory.root[property.value.name].value
} else if (property.value.type === 'ObjectExpression') {
obj[property.key.name] = executeObjectExpression(
_programMemory,
@ -451,125 +626,3 @@ function executeObjectExpression(
})
return obj
}
type SourceRange = [number, number]
export type ViewerArtifact =
| {
type: 'sketchLine'
sourceRange: SourceRange
geo: LineGeos
}
| {
type: 'sketchBase'
sourceRange: SourceRange
geo: BufferGeometry
}
| {
type: 'extrudeWall'
sourceRange: SourceRange
geo: BufferGeometry
}
| {
type: 'parent'
sourceRange: SourceRange
children: ViewerArtifact[]
}
| {
type: 'sketch'
sourceRange: SourceRange
children: ViewerArtifact[]
}
type PreviousTransforms = {
rotation: Quaternion
transform: [number, number, number]
}[]
export const processShownObjects = (
programMemory: ProgramMemory,
geoMeta: SketchGeo | ExtrudeGeo | Transform,
previousTransforms: PreviousTransforms = []
): ViewerArtifact[] => {
if (geoMeta?.type === 'sketchGeo') {
return [
{
type: 'sketch',
sourceRange: geoMeta.sourceRange,
children: geoMeta.sketch.map(({ geo, sourceRange, type }) => {
if (type === 'toPoint') {
const newGeo: LineGeos = {
line: geo.line.clone(),
tip: geo.tip.clone(),
centre: geo.centre.clone(),
}
let rotationQuaternion = new Quaternion()
let position = new Vector3(0, 0, 0)
previousTransforms.forEach(({ rotation, transform }) => {
const newQuant = rotation.clone()
newQuant.multiply(rotationQuaternion)
rotationQuaternion.copy(newQuant)
position.applyQuaternion(rotation)
position.add(new Vector3(...transform))
})
Object.values(newGeo).forEach((geoItem: BufferGeometry) => {
geoItem.applyQuaternion(rotationQuaternion.clone())
const position_ = position.clone()
geoItem.translate(position_.x, position_.y, position_.z)
})
return {
type: 'sketchLine',
geo: newGeo,
sourceRange,
}
} else if (type === 'base') {
const newGeo: BufferGeometry = geo.clone()
const rotationQuaternion = new Quaternion()
let position = new Vector3(0, 0, 0)
// todo don't think this is right
previousTransforms.forEach(({ rotation, transform }) => {
newGeo.applyQuaternion(rotationQuaternion)
newGeo.translate(position.x, position.y, position.z)
})
newGeo.applyQuaternion(rotationQuaternion)
newGeo.translate(position.x, position.y, position.z)
return {
type: 'sketchBase',
geo: newGeo,
sourceRange,
}
}
throw new Error('Unknown geo type')
}),
},
]
} else if (geoMeta.type === 'transform') {
const referencedVar = geoMeta.sketch
const parentArtifact: ViewerArtifact = {
type: 'parent',
sourceRange: geoMeta.sourceRange,
children: processShownObjects(programMemory, referencedVar, [
{
rotation: geoMeta.rotation,
transform: geoMeta.transform,
},
...previousTransforms,
]),
}
return [parentArtifact]
} else if (geoMeta.type === 'extrudeGeo') {
const result: ViewerArtifact[] = geoMeta.surfaces.map((a) => {
const geo: BufferGeometry = a.geo.clone()
geo.translate(a.translate[0], a.translate[1], a.translate[2])
geo.applyQuaternion(a.quaternion)
return {
type: 'extrudeWall',
sourceRange: a.sourceRanges[0],
geo,
}
})
return result
}
throw new Error('Unknown geoMeta type')
}

View File

@ -1,100 +1,15 @@
import { ProgramMemory } from './executor'
import { lineGeo, baseGeo, LineGeos, extrudeGeo } from './engine'
import { BufferGeometry } from 'three'
import {
ProgramMemory,
Path,
SketchGroup,
ExtrudeGroup,
SourceRange,
ExtrudeSurface,
} from './executor'
import { lineGeo, extrudeGeo } from './engine'
import { Quaternion, Vector3 } from 'three'
type Coords2d = [number, number]
type SourceRange = [number, number]
type Rotation3 = Quaternion
type Translate3 = [number, number, number]
export type Path =
| {
type: 'points'
name?: string
from: Coords2d
to: Coords2d
geo: BufferGeometry
sourceRange: SourceRange
}
| {
type: 'horizontalLineTo'
name?: string
x: number
geo: BufferGeometry
sourceRange: SourceRange
}
| {
type: 'verticalLineTo'
name?: string
y: number
geo: BufferGeometry
sourceRange: SourceRange
}
| {
type: 'toPoint'
name?: string
to: Coords2d
geo: LineGeos
sourceRange: SourceRange
}
| {
type: 'close'
name?: string
geo: LineGeos
sourceRange: SourceRange
}
| {
type: 'base'
from: Coords2d
geo: BufferGeometry
sourceRange: SourceRange
}
export interface Transform {
type: 'transform'
rotation: Rotation3
transform: Translate3
sketch: SketchGeo | ExtrudeGeo | Transform
sourceRange: SourceRange
}
export interface SketchGeo {
type: 'sketchGeo'
sketch: Path[]
sourceRange: SourceRange
}
export interface ExtrudeFace {
type: 'extrudeFace'
quaternion: Quaternion
translate: [number, number, number]
geo: BufferGeometry
sourceRanges: SourceRange[]
}
export interface ExtrudeGeo {
type: 'extrudeGeo'
surfaces: ExtrudeFace[]
sourceRange: SourceRange
}
function addBasePath(programMemory: ProgramMemory) {
const geo = baseGeo({ from: [0, 0, 0] })
const base: Path = {
type: 'base',
from: [0, 0],
sourceRange: [0, 0],
geo,
}
if (programMemory._sketch?.length === 0) {
return {
...programMemory,
_sketch: [base],
}
}
return programMemory
}
interface PathReturn {
programMemory: ProgramMemory
@ -106,70 +21,74 @@ function getCoordsFromPaths(paths: Path[], index = 0): Coords2d {
if (!currentPath) {
return [0, 0]
}
if (currentPath.type === 'points' || currentPath.type === 'toPoint') {
return currentPath.to
} else if (currentPath.type === 'base') {
return currentPath.from
} else if (currentPath.type === 'horizontalLineTo') {
if (currentPath.type === 'horizontalLineTo') {
const pathBefore = getCoordsFromPaths(paths, index - 1)
return [currentPath.x, pathBefore[1]]
} else if (currentPath.type === 'verticalLineTo') {
const pathBefore = getCoordsFromPaths(paths, index - 1)
return [pathBefore[0], currentPath.y]
} else if (currentPath.type === 'toPoint') {
return [currentPath.to[0], currentPath.to[1]]
}
return [0, 0]
}
export const sketchFns = {
base: (
programMemory: ProgramMemory,
name: string = '',
sourceRange: SourceRange,
...args: any[]
): PathReturn => {
if (programMemory._sketch?.length > 0) {
throw new Error('Base can only be called once')
}
const [x, y] = args as [number, number]
let from: [number, number] = [x, y]
const geo = baseGeo({ from: [x, y, 0] })
const newPath: Path = {
type: 'base',
from,
sourceRange,
geo,
}
return {
programMemory: {
...programMemory,
_sketch: [...(programMemory?._sketch || []), newPath],
},
currentPath: newPath,
}
},
// base: (
// programMemory: ProgramMemory,
// name: string = '',
// sourceRange: SourceRange,
// ...args: any[]
// ): PathReturn => {
// if ((programMemory?._sketch?.length || 0) > 0) {
// throw new Error('Base can only be called once')
// }
// const [x, y] = args as [number, number]
// let from: [number, number] = [x, y]
// const geo = baseGeo({ from: [x, y, 0] })
// const newPath: Path = {
// type: 'base',
// from,
// sourceRange,
// geo,
// }
// return {
// programMemory: {
// ...programMemory,
// _sketch: [...(programMemory?._sketch || []), newPath],
// },
// currentPath: newPath,
// }
// },
close: (
programMemory: ProgramMemory,
name: string = '',
sourceRange: SourceRange
): PathReturn => {
const lastPath = programMemory?._sketch?.[
programMemory?._sketch.length - 1
] as Path
const firstPath = programMemory?._sketch?.[0] as Path
let from = getCoordsFromPaths(
programMemory?._sketch,
programMemory?._sketch.length - 1
programMemory?._sketch || [],
(programMemory?._sketch?.length || 1) - 1
)
const firstPath = programMemory?._sketch?.[0] as Path
if (lastPath?.type === 'base') {
throw new Error('Cannot close a base path')
}
let to = getCoordsFromPaths(programMemory?._sketch, 0)
let to = getCoordsFromPaths(programMemory?._sketch || [], 0)
const geo = lineGeo({ from: [...from, 0], to: [...to, 0] })
const newPath: Path = {
type: 'close',
geo: lineGeo({ from: [...from, 0], to: [...to, 0] }),
type: 'toPoint',
from,
to,
__geoMeta: {
sourceRange,
pathToNode: [], // TODO
geos: [
{
type: 'line',
geo: geo.line,
},
{
type: 'lineEnd',
geo: geo.tip,
},
],
},
}
if (name) {
newPath.name = name
@ -177,7 +96,14 @@ export const sketchFns = {
return {
programMemory: {
...programMemory,
_sketch: [...(programMemory?._sketch || []), newPath],
_sketch: [
{
...firstPath,
from,
},
...(programMemory?._sketch || []).slice(1),
newPath,
],
},
currentPath: newPath,
}
@ -188,28 +114,41 @@ export const sketchFns = {
sourceRange: SourceRange,
...args: any[]
): PathReturn => {
const _programMemory = addBasePath(programMemory)
const [x, y] = args
if (!_programMemory._sketch) {
if (!programMemory._sketch) {
throw new Error('No sketch to draw on')
}
let from = getCoordsFromPaths(
programMemory?._sketch,
programMemory?._sketch.length - 1
programMemory?._sketch || [],
(programMemory?._sketch?.length || 1) - 1
)
const geo = lineGeo({ from: [...from, 0], to: [x, y, 0] })
const currentPath: Path = {
type: 'toPoint',
to: [x, y],
geo: lineGeo({ from: [...from, 0], to: [x, y, 0] }),
from,
__geoMeta: {
sourceRange,
pathToNode: [], // TODO
geos: [
{
type: 'line',
geo: geo.line,
},
{
type: 'lineEnd',
geo: geo.tip,
},
],
},
}
if (name) {
currentPath.name = name
}
return {
programMemory: {
..._programMemory,
_sketch: [...(_programMemory._sketch || []), currentPath],
...programMemory,
_sketch: [...(programMemory._sketch || []), currentPath],
},
currentPath,
}
@ -222,31 +161,22 @@ export const sketchFns = {
name: string = '',
sourceRange: SourceRange,
length: number,
sketchVal: SketchGeo | Transform
): ExtrudeGeo | Transform => {
const getSketchGeo = (sketchVal: SketchGeo | Transform): SketchGeo => {
if (
sketchVal.type === 'transform' &&
sketchVal.sketch.type === 'extrudeGeo'
)
throw new Error('Cannot extrude a extrude')
return sketchVal.type === 'transform'
? getSketchGeo(sketchVal.sketch as any) // TODO fix types
: (sketchVal as SketchGeo) // TODO fix types
sketchVal: SketchGroup
): ExtrudeGroup => {
const getSketchGeo = (sketchVal: SketchGroup): SketchGroup => {
return sketchVal
}
const sketch = getSketchGeo(sketchVal)
const { position, quaternion } = combineTransforms(sketchVal)
const { position, rotation } = sketchVal
const extrudeFaces: ExtrudeFace[] = []
sketch.sketch.map((line, index) => {
const extrudeSurfaces: ExtrudeSurface[] = []
sketch.value.map((line, index) => {
if (line.type === 'toPoint' && index !== 0) {
const lastPoint = sketch.sketch[index - 1]
const lastPoint = sketch.value[index - 1]
let from: [number, number] = [0, 0]
if (lastPoint.type === 'toPoint') {
from = lastPoint.to
} else if (lastPoint.type === 'base') {
from = lastPoint.from
}
const to = line.to
const geo = extrudeGeo({
@ -254,117 +184,87 @@ export const sketchFns = {
to: [to[0], to[1], 0],
length,
})
extrudeFaces.push({
type: 'extrudeFace',
quaternion,
translate: position,
extrudeSurfaces.push({
type: 'extrudePlane',
position, // todo should come from extrudeGeo
rotation, // todo should come from extrudeGeo
__geoMeta: {
geo,
sourceRanges: [line.sourceRange, sourceRange],
sourceRange: line.__geoMeta.sourceRange,
pathToNode: line.__geoMeta.pathToNode,
},
})
}
})
return {
type: 'extrudeGeo',
type: 'extrudeGroup',
value: extrudeSurfaces,
height: length,
position,
rotation,
__meta: [
{
sourceRange,
surfaces: extrudeFaces,
pathToNode: [], // TODO
},
],
}
},
translate,
}
function rotateOnAxis(axisMultiplier: [number, number, number]) {
function rotateOnAxis<T extends SketchGroup | ExtrudeGroup>(
axisMultiplier: [number, number, number]
) {
return (
programMemory: ProgramMemory,
sourceRange: SourceRange,
rotationD: number,
sketch: SketchGeo | Transform
): Transform => {
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 existingQuat = new Quaternion(...sketch.rotation)
const rotation = quaternion.multiply(existingQuat).toArray()
return {
type: 'transform',
rotation: quaternion.setFromAxisAngle(rotateVec, rotationR),
transform: [0, 0, 0],
sketch,
...sketch,
rotation,
position,
__meta: [
...sketch.__meta,
{
sourceRange,
pathToNode: [], // TODO
},
],
}
}
}
function translate(
function translate<T extends SketchGroup | ExtrudeGroup>(
programMemory: ProgramMemory,
sourceRange: SourceRange,
vec3: [number, number, number],
sketch: SketchGeo | Transform
): Transform {
sketch: T
): T {
const oldPosition = new Vector3(...sketch.position)
const newPosition = oldPosition.add(new Vector3(...vec3))
return {
type: 'transform',
rotation: new Quaternion(),
transform: vec3,
sketch,
sourceRange,
}
}
type PreviousTransforms = {
rotation: Quaternion
transform: [number, number, number]
}[]
function collectTransforms(
sketchVal: SketchGeo | ExtrudeGeo | Transform,
previousTransforms: PreviousTransforms = []
): PreviousTransforms {
if (sketchVal.type !== 'transform') return previousTransforms
const newTransforms = [
...previousTransforms,
...sketch,
position: [newPosition.x, newPosition.y, newPosition.z],
__meta: [
...sketch.__meta,
{
rotation: sketchVal.rotation,
transform: sketchVal.transform,
sourceRange,
pathToNode: [], // TODO
},
]
return collectTransforms(sketchVal.sketch, newTransforms)
}
export function combineTransforms(
sketchVal: SketchGeo | ExtrudeGeo | Transform
): {
quaternion: Quaternion
position: [number, number, number]
} {
const previousTransforms = collectTransforms(sketchVal)
const position = new Vector3(0, 0, 0)
const quaternion = new Quaternion()
previousTransforms.forEach(({ rotation, transform }) => {
quaternion.multiply(rotation)
position.applyQuaternion(rotation.clone().invert())
position.add(new Vector3(...transform))
})
return {
quaternion,
position: [position.x, position.y, position.z],
}
}
export function combineTransformsAlt(
sketchVal: SketchGeo | ExtrudeGeo | Transform
): {
quaternion: Quaternion
position: [number, number, number]
} {
const previousTransforms = collectTransforms(sketchVal)
let rotationQuaternion = new Quaternion()
let position = new Vector3(0, 0, 0)
previousTransforms.reverse().forEach(({ rotation, transform }) => {
const newQuant = rotation.clone()
newQuant.multiply(rotationQuaternion)
rotationQuaternion.copy(newQuant)
position.applyQuaternion(rotation)
position.add(new Vector3(...transform))
})
return {
quaternion: rotationQuaternion,
position: [position.x, position.y, position.z],
],
}
}

View File

@ -1,16 +1,12 @@
import create from 'zustand'
import { addLineHighlight, EditorView } from './editor/highlightextension'
import { Program, abstractSyntaxTree } from './lang/abstractSyntaxTree'
import { ProgramMemory } from './lang/executor'
import { ProgramMemory, Position, PathToNode, Rotation } from './lang/executor'
import { recast } from './lang/recast'
import { lexer } from './lang/tokeniser'
import { Quaternion } from 'three'
export type Range = [number, number]
type PathToNode = (string | number)[]
type Position = [number, number, number]
type GuiModes =
| {
mode: 'default'
@ -18,7 +14,7 @@ type GuiModes =
| {
mode: 'sketch'
sketchMode: 'points'
quaternion: Quaternion
rotation: Rotation
position: Position
id?: string
pathToNode: PathToNode
@ -26,7 +22,7 @@ type GuiModes =
| {
mode: 'sketch'
sketchMode: 'sketchEdit'
quaternion: Quaternion
rotation: Rotation
position: Position
pathToNode: PathToNode
}
@ -37,7 +33,7 @@ type GuiModes =
| {
mode: 'canEditSketch'
pathToNode: PathToNode
quaternion: Quaternion
rotation: Rotation
position: Position
}