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

View File

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

View File

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

View File

@ -3,110 +3,22 @@ import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
getNodeFromPath, getNodeFromPath,
CallExpression, CallExpression,
VariableDeclarator,
} from '../lang/abstractSyntaxTree' } from '../lang/abstractSyntaxTree'
import { changeArguments } from '../lang/modifyAst' 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 { BufferGeometry } from 'three'
import { useStore } from '../useStore' import { useStore } from '../useStore'
import { isOverlapping } from '../lib/utils' import { isOverlapping } from '../lib/utils'
import { LineGeos } from '../lang/engine' import { Vector3, DoubleSide, Quaternion } from 'three'
import { Vector3, DoubleSide, Quaternion, Vector2 } from 'three'
import { combineTransformsAlt } from '../lang/sketch'
import { useSetCursor } from '../hooks/useSetCursor' 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 roundOff = (num: number, places: number): number => {
const x = Math.pow(10, places) const x = Math.pow(10, places)
return Math.round(num * x) / x return Math.round(num * x) / x
@ -116,15 +28,19 @@ function MovingSphere({
geo, geo,
sourceRange, sourceRange,
editorCursor, editorCursor,
rotation,
position,
}: { }: {
geo: BufferGeometry geo: BufferGeometry
sourceRange: [number, number] sourceRange: [number, number]
editorCursor: boolean editorCursor: boolean
rotation: Rotation
position: Position
}) { }) {
const ref = useRef<BufferGeometry | undefined>() as any const ref = useRef<BufferGeometry | undefined>() as any
const detectionPlaneRef = useRef<BufferGeometry | undefined>() as any const detectionPlaneRef = useRef<BufferGeometry | undefined>() as any
const lastPointerRef = useRef<Vector3>(new Vector3()) const lastPointerRef = useRef<Vector3>(new Vector3())
const point2DRef = useRef<Vector2>(new Vector2()) const point2DRef = useRef<Vector3>(new Vector3())
const [hovered, setHover] = useState(false) const [hovered, setHover] = useState(false)
const [isMouseDown, setIsMouseDown] = useState(false) const [isMouseDown, setIsMouseDown] = useState(false)
@ -154,14 +70,22 @@ function MovingSphere({
const handleMouseUp = () => { const handleMouseUp = () => {
if (isMouseDown && ast) { if (isMouseDown && ast) {
const thePath = getNodePathFromSourceRange(ast, sourceRange) const thePath = getNodePathFromSourceRange(ast, sourceRange)
let [x, y] = [ const yo = point2DRef.current.clone()
roundOff(point2DRef.current.x, 2), const inverseQuaternion = new Quaternion()
roundOff(point2DRef.current.y, 2), 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] let theNewPoints: [number, number] = [x, y]
const { modifiedAst } = changeArguments(ast, thePath, theNewPoints) const { modifiedAst } = changeArguments(ast, thePath, theNewPoints)
updateAst(modifiedAst) updateAst(modifiedAst)
ref.current.position.set(0, 0, 0) console.log('reset position')
ref.current.position.set(...position)
} }
setIsMouseDown(false) setIsMouseDown(false)
} }
@ -169,31 +93,32 @@ function MovingSphere({
return () => { return () => {
window.removeEventListener('mouseup', handleMouseUp) window.removeEventListener('mouseup', handleMouseUp)
} }
}, [isMouseDown, ast]) }, [isMouseDown])
let clickDetectPlaneQuaternion = new Quaternion() const inEditMode =
let position = new Vector3(0, 0, 0)
if (
guiMode.mode === 'canEditSketch' || guiMode.mode === 'canEditSketch' ||
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit') (guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
) {
clickDetectPlaneQuaternion = guiMode.quaternion.clone() let clickDetectPlaneQuaternion = new Quaternion()
position = new Vector3(...guiMode.position) if (inEditMode) {
clickDetectPlaneQuaternion = new Quaternion(...rotation)
} }
return ( return (
<> <>
<mesh <mesh
position={position}
quaternion={rotation}
ref={ref} ref={ref}
onPointerOver={(event) => { onPointerOver={(event) => {
setHover(true) inEditMode && setHover(true)
setHighlightRange(sourceRange) setHighlightRange(sourceRange)
}} }}
onPointerOut={(event) => { onPointerOut={(event) => {
setHover(false) setHover(false)
setHighlightRange([0, 0]) setHighlightRange([0, 0])
}} }}
onPointerDown={() => setIsMouseDown(true)} onPointerDown={() => inEditMode && setIsMouseDown(true)}
> >
<primitive object={geo} scale={hovered ? 2 : 1} /> <primitive object={geo} scale={hovered ? 2 : 1} />
<meshStandardMaterial <meshStandardMaterial
@ -202,7 +127,6 @@ function MovingSphere({
</mesh> </mesh>
{isMouseDown && ( {isMouseDown && (
<mesh <mesh
position={position}
quaternion={clickDetectPlaneQuaternion} quaternion={clickDetectPlaneQuaternion}
onPointerMove={(a) => { onPointerMove={(a) => {
const point = a.point const point = a.point
@ -213,13 +137,11 @@ function MovingSphere({
guiMode.mode === 'canEditSketch' || guiMode.mode === 'canEditSketch' ||
(guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit') (guiMode.mode === 'sketch' && guiMode.sketchMode === 'sketchEdit')
) { ) {
inverseQuaternion.copy(guiMode.quaternion.clone().invert()) inverseQuaternion.set(...guiMode.rotation)
inverseQuaternion.invert()
} }
transformedPoint.applyQuaternion(inverseQuaternion) transformedPoint.applyQuaternion(inverseQuaternion)
transformedPoint.sub( point2DRef.current.copy(transformedPoint)
position.clone().applyQuaternion(inverseQuaternion)
)
point2DRef.current.set(transformedPoint.x, transformedPoint.y)
if ( if (
lastPointerRef.current.x === 0 && lastPointerRef.current.x === 0 &&
@ -248,7 +170,7 @@ function MovingSphere({
ref.current.position.add( ref.current.position.add(
diff.applyQuaternion(inverseQuaternion.invert()) 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({ export function RenderViewerArtifacts({
artifact, artifacts,
forceHighlight = false,
}: { }: {
artifact: ViewerArtifact artifacts: (ExtrudeGroup | SketchGroup)[]
forceHighlight?: boolean
}) { }) {
const { selectionRange, guiMode, ast, setGuiMode, programMemory } = useStore(
({ selectionRange, guiMode, ast, setGuiMode, programMemory }) => ({
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])
useEffect(() => {
const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange)
if (
shouldHighlight &&
(guiMode.mode === 'default' || guiMode.mode === 'canEditSketch') &&
artifact.type === 'sketch' &&
ast
) {
const pathToNode = getNodePathFromSourceRange(ast, artifact.sourceRange)
const varDec: VariableDeclarator = getNodeFromPath(
ast,
pathToNode,
'VariableDeclarator'
)
const varName = varDec?.id?.name
const { quaternion, position } = combineTransformsAlt(
programMemory.root[varName]
)
setGuiMode({ mode: 'canEditSketch', pathToNode, quaternion, position })
} else if (
!shouldHighlight &&
guiMode.mode === 'canEditSketch' &&
artifact.type === 'sketch'
) {
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}
/>
)
}
return ( return (
<> <>
{artifact.children.map((artifact, index) => ( {artifacts.map((artifact, i) => (
<RenderViewerArtifacts <RenderViewerArtifact key={i} artifact={artifact} />
artifact={artifact}
key={index}
forceHighlight={forceHighlight || editorCursor}
/>
))} ))}
</> </>
) )
} }
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])
useEffect(() => {
const shouldHighlight = artifact.__meta.some((aMeta) =>
isOverlapping(aMeta.sourceRange, selectionRange)
)
if (
shouldHighlight &&
(guiMode.mode === 'default' || guiMode.mode === 'canEditSketch') &&
ast &&
artifact.type === 'sketchGroup'
) {
const pathToNode = getNodePathFromSourceRange(
ast,
artifact.__meta[0].sourceRange
)
const { rotation, position } = artifact
setGuiMode({ mode: 'canEditSketch', pathToNode, rotation, position })
} else if (
!shouldHighlight &&
guiMode.mode === 'canEditSketch' &&
artifact.type === 'sketchGroup'
) {
setGuiMode({ mode: 'default' })
}
}, [selectionRange, artifact, ast, guiMode.mode, setGuiMode])
if (artifact.type === 'sketchGroup') {
return (
<>
{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' const sketchGridName = 'sketchGrid'
let clickDetectQuaternion = guiMode.quaternion.clone() let clickDetectQuaternion = new Quaternion(...guiMode.rotation)
let temp = new Quaternion().setFromAxisAngle( let temp = new Quaternion().setFromAxisAngle(
new Vector3(1, 0, 0), new Vector3(1, 0, 0),
@ -28,7 +28,7 @@ export const SketchPlane = () => {
) )
let position = guiMode.position let position = guiMode.position
const gridQuaternion = new Quaternion().multiplyQuaternions( const gridQuaternion = new Quaternion().multiplyQuaternions(
guiMode.quaternion, new Quaternion(...guiMode.rotation),
temp temp
) )
@ -47,8 +47,12 @@ export const SketchPlane = () => {
) )
const inverseQuaternion = clickDetectQuaternion.clone().invert() const inverseQuaternion = clickDetectQuaternion.clone().invert()
let transformedPoint = sketchGridIntersection?.point.clone() let transformedPoint = sketchGridIntersection?.point.clone()
if (transformedPoint) if (transformedPoint) {
transformedPoint.applyQuaternion(inverseQuaternion) transformedPoint.applyQuaternion(inverseQuaternion)
transformedPoint?.sub(
new Vector3(...position).applyQuaternion(inverseQuaternion)
)
}
const point = roundy(transformedPoint) const point = roundy(transformedPoint)
let _ast: Program = ast let _ast: Program = ast

View File

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

View File

@ -1,9 +1,9 @@
import { abstractSyntaxTree } from './abstractSyntaxTree' import { abstractSyntaxTree } from './abstractSyntaxTree'
import { lexer } from './tokeniser' import { lexer } from './tokeniser'
import { executor, ViewerArtifact, processShownObjects } from './executor' import { executor, SketchGroup, ExtrudeGroup } from './executor'
describe('findClosingBrace', () => { describe('testing artifacts', () => {
test('finds the closing brace', () => { test('sketch artifacts', () => {
const code = ` const code = `
sketch mySketch001 { sketch mySketch001 {
lineTo(-1.59, -1.54) lineTo(-1.59, -1.54)
@ -12,34 +12,86 @@ sketch mySketch001 {
|> rx(45, %) |> rx(45, %)
show(mySketch001)` show(mySketch001)`
const programMemory = executor(abstractSyntaxTree(lexer(code))) const programMemory = executor(abstractSyntaxTree(lexer(code)))
const geos: ViewerArtifact[] = const geos = programMemory?.return?.map(
programMemory?.return?.flatMap( (a) => programMemory?.root?.[a.name]
({ name }: { name: string }) => )
processShownObjects(programMemory, programMemory?.root?.[name]) || [] const artifactsWithoutGeos = removeGeo(geos as any)
) || [] expect(artifactsWithoutGeos).toEqual([
const artifactsWithouGeos = removeGeo(geos)
expect(artifactsWithouGeos).toEqual([
{ {
type: 'parent', type: 'sketchGroup',
sourceRange: [74, 83], value: [
children: [ {
type: 'toPoint',
to: [-1.59, -1.54],
from: [0, 0],
__geoMeta: {
sourceRange: [24, 44],
pathToNode: [],
geos: ['line', 'lineEnd'],
},
},
{
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: [
{ {
type: 'sketch',
sourceRange: [20, 68], sourceRange: [20, 68],
children: [ pathToNode: ['body', 0, 'declarations', 0, 'init', 0],
{ },
type: 'sketchBase', {
sourceRange: [0, 0], sourceRange: [74, 83],
}, pathToNode: [],
{ },
type: 'sketchLine', ],
sourceRange: [24, 44], },
}, ])
{ })
type: 'sketchLine', test('extrude artifacts', () => {
sourceRange: [47, 66], 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) => { return arts.map((art) => {
if (art.type === 'sketchLine' || art.type === 'sketchBase') { if (art.type === 'extrudeGroup') {
const { geo, ...rest } = art
return rest
}
if (art.type === 'parent') {
return { return {
...art, ...art,
children: removeGeo(art.children), value: art.value.map((v) => ({
...v,
__geoMeta: {
...v.__geoMeta,
geo: v.__geoMeta.geo.type,
},
})),
} }
} }
if (art.type === 'sketch') { return {
return { ...art,
...art, value: art.value.map((v) => ({
children: removeGeo(art.children), ...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({ export function lineGeo({
from, from,
to, to,
}: { }: {
from: [number, number, number] from: [number, number, number]
to: [number, number, number] to: [number, number, number]
}): LineGeos { }): {
line: BufferGeometry
tip: BufferGeometry
centre: BufferGeometry
} {
const { const {
centre, centre,
Hypotenuse: Hypotenuse3d, Hypotenuse: Hypotenuse3d,

View File

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

View File

@ -5,21 +5,108 @@ import {
PipeExpression, PipeExpression,
ObjectExpression, ObjectExpression,
MemberExpression, MemberExpression,
Identifier,
} from './abstractSyntaxTree' } from './abstractSyntaxTree'
import { Path, Transform, SketchGeo, sketchFns, ExtrudeGeo } from './sketch' import { sketchFns } from './sketch'
import { BufferGeometry, Quaternion, Vector3 } from 'three' import { BufferGeometry } from 'three'
import { LineGeos } from './engine'
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 { export interface ProgramMemory {
root: { [key: string]: any } root: Memory
return?: any return?: Identifier[]
_sketch: Path[] _sketch?: Path[]
} }
export const executor = ( export const executor = (
node: Program, node: Program,
programMemory: ProgramMemory = { root: {}, _sketch: [] }, programMemory: ProgramMemory = { root: {}, _sketch: [] },
options: { bodyType: 'root' | 'sketch' | 'block' } = { bodyType: 'root' } options: { bodyType: 'root' | 'sketch' | 'block' } = { bodyType: 'root' },
previousPathToNode: PathToNode = []
): ProgramMemory => { ): ProgramMemory => {
const _programMemory: ProgramMemory = { const _programMemory: ProgramMemory = {
root: { root: {
@ -29,43 +116,103 @@ export const executor = (
return: programMemory.return, return: programMemory.return,
} }
const { body } = node const { body } = node
body.forEach((statement) => { body.forEach((statement, bodyIndex) => {
if (statement.type === 'VariableDeclaration') { if (statement.type === 'VariableDeclaration') {
statement.declarations.forEach((declaration) => { statement.declarations.forEach((declaration, index) => {
const variableName = declaration.id.name 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') { if (declaration.init.type === 'PipeExpression') {
_programMemory.root[variableName] = getPipeExpressionResult( const value = getPipeExpressionResult(
declaration.init, declaration.init,
_programMemory _programMemory,
pathToNode
) )
} else if (declaration.init.type === 'Literal') { if (value?.type === 'sketchGroup' || value?.type === 'extrudeGroup') {
_programMemory.root[variableName] = declaration.init.value _programMemory.root[variableName] = value
} else if (declaration.init.type === 'BinaryExpression') { } else {
_programMemory.root[variableName] = getBinaryExpressionResult( _programMemory.root[variableName] = {
declaration.init, type: 'userVal',
_programMemory value,
) __meta,
} else if (declaration.init.type === 'ArrayExpression') {
_programMemory.root[variableName] = declaration.init.elements.map(
(element) => {
if (element.type === 'Literal') {
return element.value
} else if (element.type === 'BinaryExpression') {
return getBinaryExpressionResult(element, _programMemory)
} else if (element.type === 'PipeExpression') {
return getPipeExpressionResult(element, _programMemory)
} else if (element.type === 'Identifier') {
return _programMemory.root[element.name]
} else {
throw new Error(
`Unexpected element type ${element.type} in array expression`
)
}
} }
) }
} else if (declaration.init.type === 'Literal') {
_programMemory.root[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,
}
} else if (declaration.init.type === 'ArrayExpression') {
const valueInfo: { value: any; __meta?: Metadata }[] =
declaration.init.elements.map(
(element): { value: any; __meta?: Metadata } => {
if (element.type === 'Literal') {
return {
value: element.value,
}
} else if (element.type === 'BinaryExpression') {
return {
value: getBinaryExpressionResult(element, _programMemory),
}
} else if (element.type === 'PipeExpression') {
return {
value: getPipeExpressionResult(
element,
_programMemory,
pathToNode
),
}
} else if (element.type === 'Identifier') {
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`
)
}
}
)
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') { } else if (declaration.init.type === 'ObjectExpression') {
const obj = executeObjectExpression(_programMemory, declaration.init) _programMemory.root[variableName] = {
_programMemory.root[variableName] = obj type: 'userVal',
value: executeObjectExpression(_programMemory, declaration.init),
__meta,
}
} else if (declaration.init.type === 'SketchExpression') { } else if (declaration.init.type === 'SketchExpression') {
const sketchInit = declaration.init const sketchInit = declaration.init
const fnMemory: ProgramMemory = { const fnMemory: ProgramMemory = {
@ -77,64 +224,66 @@ export const executor = (
let { _sketch } = executor(sketchInit.body, fnMemory, { let { _sketch } = executor(sketchInit.body, fnMemory, {
bodyType: 'sketch', bodyType: 'sketch',
}) })
if (_sketch.length === 0) { const newSketch: SketchGroup = {
const { programMemory: newProgramMemory } = sketchFns.base( type: 'sketchGroup',
fnMemory, value: _sketch || [],
'', position: [0, 0, 0],
[0, 0], rotation: [0, 0, 0, 1], //x,y,z,w
0, __meta,
0
)
_sketch = newProgramMemory._sketch
}
const newSketch: SketchGeo = {
type: 'sketchGeo',
sketch: _sketch,
sourceRange: [sketchInit.start, sketchInit.end],
} }
_programMemory.root[variableName] = newSketch _programMemory.root[variableName] = newSketch
} else if (declaration.init.type === 'FunctionExpression') { } else if (declaration.init.type === 'FunctionExpression') {
const fnInit = declaration.init const fnInit = declaration.init
_programMemory.root[declaration.id.name] = (...args: any[]) => { _programMemory.root[declaration.id.name] = {
const fnMemory: ProgramMemory = { type: 'userVal',
root: { value: (...args: any[]) => {
..._programMemory.root, const fnMemory: ProgramMemory = {
}, root: {
_sketch: [], ..._programMemory.root,
} },
if (args.length > fnInit.params.length) { _sketch: [],
throw new Error( }
`Too many arguments passed to function ${declaration.id.name}` if (args.length > fnInit.params.length) {
) throw new Error(
} else if (args.length < fnInit.params.length) { `Too many arguments passed to function ${declaration.id.name}`
throw new Error( )
`Too few 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] = args[index] }
}) fnInit.params.forEach((param, index) => {
return executor(fnInit.body, fnMemory, { bodyType: 'block' }).return fnMemory.root[param.name] = {
type: 'userVal',
value: args[index],
__meta,
}
})
return executor(fnInit.body, fnMemory, { bodyType: 'block' })
.return
},
__meta,
} }
} else if (declaration.init.type === 'MemberExpression') { } else if (declaration.init.type === 'MemberExpression') {
_programMemory.root[variableName] = getMemberExpressionResult( _programMemory.root[variableName] = {
declaration.init, type: 'userVal',
_programMemory value: getMemberExpressionResult(declaration.init, _programMemory),
) __meta,
}
} else if (declaration.init.type === 'CallExpression') { } else if (declaration.init.type === 'CallExpression') {
const functionName = declaration.init.callee.name const functionName = declaration.init.callee.name
const fnArgs = declaration.init.arguments.map((arg) => { const fnArgs = declaration.init.arguments.map((arg) => {
if (arg.type === 'Literal') { if (arg.type === 'Literal') {
return arg.value return arg.value
} else if (arg.type === 'Identifier') { } else if (arg.type === 'Identifier') {
return _programMemory.root[arg.name] return _programMemory.root[arg.name].value
} }
}) })
if ( if (
'lineTo' === functionName || 'lineTo' === functionName ||
'close' === functionName || 'close' === functionName // ||
'base' === functionName // 'base' === functionName
) { ) {
if (options.bodyType !== 'sketch') { if (options.bodyType !== 'sketch') {
throw new Error( throw new Error(
@ -148,7 +297,11 @@ export const executor = (
...fnArgs ...fnArgs
) )
_programMemory._sketch = result.programMemory._sketch _programMemory._sketch = result.programMemory._sketch
_programMemory.root[variableName] = result.currentPath _programMemory.root[variableName] = {
type: 'userVal',
value: result.currentPath,
__meta,
}
} else if ( } else if (
'rx' === functionName || 'rx' === functionName ||
'ry' === functionName || 'ry' === functionName ||
@ -162,9 +315,9 @@ export const executor = (
_programMemory, _programMemory,
[declaration.start, declaration.end], [declaration.start, declaration.end],
fnArgs[0], 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') { } else if (functionName === 'extrude') {
const sketch = declaration.init.arguments[1] const sketch = declaration.init.arguments[1]
if (sketch.type !== 'Identifier') if (sketch.type !== 'Identifier')
@ -175,9 +328,9 @@ export const executor = (
'yo', 'yo',
[declaration.start, declaration.end], [declaration.start, declaration.end],
fnArgs[0], 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') { } else if (functionName === 'translate') {
const sketch = declaration.init.arguments[1] const sketch = declaration.init.arguments[1]
if (sketch.type !== 'Identifier') if (sketch.type !== 'Identifier')
@ -187,13 +340,15 @@ export const executor = (
_programMemory, _programMemory,
[declaration.start, declaration.end], [declaration.start, declaration.end],
fnArgs[0], fnArgs[0],
sketchVal sketchVal as any // todo memory redo
) )
_programMemory.root[variableName] = result _programMemory.root[variableName] = result as any // todo memory redo
} else { } else {
_programMemory.root[variableName] = _programMemory.root[ _programMemory.root[variableName] = {
functionName type: 'userVal',
](...fnArgs) value: _programMemory.root[functionName].value(...fnArgs),
__meta,
}
} }
} else { } else {
throw new Error( throw new Error(
@ -209,13 +364,13 @@ export const executor = (
if (arg.type === 'Literal') { if (arg.type === 'Literal') {
return arg.value return arg.value
} else if (arg.type === 'Identifier') { } else if (arg.type === 'Identifier') {
return _programMemory.root[arg.name] return _programMemory.root[arg.name].value
} }
}) })
if ( if (
'lineTo' === functionName || 'lineTo' === functionName ||
'close' === functionName || 'close' === functionName
'base' === functionName // || 'base' === functionName
) { ) {
if (options.bodyType !== 'sketch') { if (options.bodyType !== 'sketch') {
throw new Error( throw new Error(
@ -228,14 +383,14 @@ export const executor = (
[statement.start, statement.end], [statement.start, statement.end],
...args ...args
) )
_programMemory._sketch = [...result.programMemory._sketch] _programMemory._sketch = [...(result.programMemory._sketch || [])]
} else if ('show' === functionName) { } else if ('show' === functionName) {
if (options.bodyType !== 'root') { if (options.bodyType !== 'root') {
throw new Error(`Cannot call ${functionName} outside of a 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 { } else {
_programMemory.root[functionName](...args) _programMemory.root[functionName].value(...args)
} }
} }
} else if (statement.type === 'ReturnStatement') { } else if (statement.type === 'ReturnStatement') {
@ -262,7 +417,7 @@ function getMemberExpressionResult(
const object: any = const object: any =
expression.object.type === 'MemberExpression' expression.object.type === 'MemberExpression'
? getMemberExpressionResult(expression.object, programMemory) ? getMemberExpressionResult(expression.object, programMemory)
: programMemory.root[expression.object.name] : programMemory.root[expression.object.name].value
return object[propertyName] return object[propertyName]
} }
@ -274,7 +429,7 @@ function getBinaryExpressionResult(
if (part.type === 'Literal') { if (part.type === 'Literal') {
return part.value return part.value
} else if (part.type === 'Identifier') { } else if (part.type === 'Identifier') {
return programMemory.root[part.name] return programMemory.root[part.name].value
} }
} }
const left = getVal(expression.left) const left = getVal(expression.left)
@ -284,9 +439,14 @@ function getBinaryExpressionResult(
function getPipeExpressionResult( function getPipeExpressionResult(
expression: PipeExpression, 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] const result = executedBody[executedBody.length - 1]
return result return result
} }
@ -294,6 +454,7 @@ function getPipeExpressionResult(
function executePipeBody( function executePipeBody(
body: PipeExpression['body'], body: PipeExpression['body'],
programMemory: ProgramMemory, programMemory: ProgramMemory,
previousPathToNode: PathToNode = [],
expressionIndex = 0, expressionIndex = 0,
previousResults: any[] = [] previousResults: any[] = []
): any[] { ): any[] {
@ -303,10 +464,13 @@ function executePipeBody(
const expression = body[expressionIndex] const expression = body[expressionIndex]
if (expression.type === 'BinaryExpression') { if (expression.type === 'BinaryExpression') {
const result = getBinaryExpressionResult(expression, programMemory) const result = getBinaryExpressionResult(expression, programMemory)
return executePipeBody(body, programMemory, expressionIndex + 1, [ return executePipeBody(
...previousResults, body,
result, programMemory,
]) previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
} else if (expression.type === 'CallExpression') { } else if (expression.type === 'CallExpression') {
const functionName = expression.callee.name const functionName = expression.callee.name
const fnArgs = expression.arguments.map((arg) => { const fnArgs = expression.arguments.map((arg) => {
@ -341,10 +505,13 @@ function executePipeBody(
fnArgs[0], fnArgs[0],
fnArgs[1] fnArgs[1]
) )
return executePipeBody(body, programMemory, expressionIndex + 1, [ return executePipeBody(
...previousResults, body,
result, programMemory,
]) previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
} }
if (functionName === 'extrude') { if (functionName === 'extrude') {
const result = sketchFns[functionName]( const result = sketchFns[functionName](
@ -354,10 +521,13 @@ function executePipeBody(
fnArgs[0], fnArgs[0],
fnArgs[1] fnArgs[1]
) )
return executePipeBody(body, programMemory, expressionIndex + 1, [ return executePipeBody(
...previousResults, body,
result, programMemory,
]) previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
} }
if (functionName === 'translate') { if (functionName === 'translate') {
const result = sketchFns[functionName]( const result = sketchFns[functionName](
@ -366,16 +536,22 @@ function executePipeBody(
fnArgs[0], fnArgs[0],
fnArgs[1] fnArgs[1]
) )
return executePipeBody(body, programMemory, expressionIndex + 1, [ return executePipeBody(
...previousResults, body,
result, programMemory,
]) previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
} }
const result = programMemory.root[functionName](...fnArgs) const result = programMemory.root[functionName].value(...fnArgs)
return executePipeBody(body, programMemory, expressionIndex + 1, [ return executePipeBody(
...previousResults, body,
result, programMemory,
]) previousPathToNode,
expressionIndex + 1,
[...previousResults, result]
)
} else if (expression.type === 'SketchExpression') { } else if (expression.type === 'SketchExpression') {
const sketchBody = expression.body const sketchBody = expression.body
const fnMemory: ProgramMemory = { const fnMemory: ProgramMemory = {
@ -387,26 +563,25 @@ function executePipeBody(
let { _sketch } = executor(sketchBody, fnMemory, { let { _sketch } = executor(sketchBody, fnMemory, {
bodyType: 'sketch', bodyType: 'sketch',
}) })
if (_sketch.length === 0) { const newSketch: SketchGroup = {
const { programMemory: newProgramMemory } = sketchFns.base( type: 'sketchGroup',
fnMemory, value: _sketch || [],
'', position: [0, 0, 0],
[0, 0], rotation: [0, 0, 0, 1], //x,y,z,w
0, __meta: [
0 {
) sourceRange: [expression.start, expression.end],
_sketch = newProgramMemory._sketch pathToNode: [...previousPathToNode, expressionIndex],
},
],
} }
// _programMemory.root[variableName] = _sketch return executePipeBody(
const newSketch: SketchGeo = { body,
type: 'sketchGeo', programMemory,
sketch: _sketch, previousPathToNode,
sourceRange: [expression.start, expression.end], expressionIndex + 1,
} [...previousResults, newSketch]
return executePipeBody(body, programMemory, expressionIndex + 1, [ )
...previousResults,
newSketch,
])
} }
throw new Error('Invalid pipe expression') throw new Error('Invalid pipe expression')
@ -432,7 +607,7 @@ function executeObjectExpression(
_programMemory _programMemory
) )
} else if (property.value.type === 'Identifier') { } 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') { } else if (property.value.type === 'ObjectExpression') {
obj[property.key.name] = executeObjectExpression( obj[property.key.name] = executeObjectExpression(
_programMemory, _programMemory,
@ -451,125 +626,3 @@ function executeObjectExpression(
}) })
return obj 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 {
import { lineGeo, baseGeo, LineGeos, extrudeGeo } from './engine' ProgramMemory,
import { BufferGeometry } from 'three' Path,
SketchGroup,
ExtrudeGroup,
SourceRange,
ExtrudeSurface,
} from './executor'
import { lineGeo, extrudeGeo } from './engine'
import { Quaternion, Vector3 } from 'three' import { Quaternion, Vector3 } from 'three'
type Coords2d = [number, number] 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 { interface PathReturn {
programMemory: ProgramMemory programMemory: ProgramMemory
@ -106,70 +21,74 @@ function getCoordsFromPaths(paths: Path[], index = 0): Coords2d {
if (!currentPath) { if (!currentPath) {
return [0, 0] return [0, 0]
} }
if (currentPath.type === 'points' || currentPath.type === 'toPoint') { if (currentPath.type === 'horizontalLineTo') {
return currentPath.to
} else if (currentPath.type === 'base') {
return currentPath.from
} else if (currentPath.type === 'horizontalLineTo') {
const pathBefore = getCoordsFromPaths(paths, index - 1) const pathBefore = getCoordsFromPaths(paths, index - 1)
return [currentPath.x, pathBefore[1]] return [currentPath.x, pathBefore[1]]
} else if (currentPath.type === 'verticalLineTo') { } else if (currentPath.type === 'toPoint') {
const pathBefore = getCoordsFromPaths(paths, index - 1) return [currentPath.to[0], currentPath.to[1]]
return [pathBefore[0], currentPath.y]
} }
return [0, 0] return [0, 0]
} }
export const sketchFns = { export const sketchFns = {
base: ( // base: (
programMemory: ProgramMemory, // programMemory: ProgramMemory,
name: string = '', // name: string = '',
sourceRange: SourceRange, // sourceRange: SourceRange,
...args: any[] // ...args: any[]
): PathReturn => { // ): PathReturn => {
if (programMemory._sketch?.length > 0) { // if ((programMemory?._sketch?.length || 0) > 0) {
throw new Error('Base can only be called once') // throw new Error('Base can only be called once')
} // }
const [x, y] = args as [number, number] // const [x, y] = args as [number, number]
let from: [number, number] = [x, y] // let from: [number, number] = [x, y]
const geo = baseGeo({ from: [x, y, 0] }) // const geo = baseGeo({ from: [x, y, 0] })
const newPath: Path = { // const newPath: Path = {
type: 'base', // type: 'base',
from, // from,
sourceRange, // sourceRange,
geo, // geo,
} // }
return { // return {
programMemory: { // programMemory: {
...programMemory, // ...programMemory,
_sketch: [...(programMemory?._sketch || []), newPath], // _sketch: [...(programMemory?._sketch || []), newPath],
}, // },
currentPath: newPath, // currentPath: newPath,
} // }
}, // },
close: ( close: (
programMemory: ProgramMemory, programMemory: ProgramMemory,
name: string = '', name: string = '',
sourceRange: SourceRange sourceRange: SourceRange
): PathReturn => { ): PathReturn => {
const lastPath = programMemory?._sketch?.[ const firstPath = programMemory?._sketch?.[0] as Path
programMemory?._sketch.length - 1
] as Path
let from = getCoordsFromPaths( let from = getCoordsFromPaths(
programMemory?._sketch, programMemory?._sketch || [],
programMemory?._sketch.length - 1 (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 = { const newPath: Path = {
type: 'close', type: 'toPoint',
geo: lineGeo({ from: [...from, 0], to: [...to, 0] }), from,
sourceRange, to,
__geoMeta: {
sourceRange,
pathToNode: [], // TODO
geos: [
{
type: 'line',
geo: geo.line,
},
{
type: 'lineEnd',
geo: geo.tip,
},
],
},
} }
if (name) { if (name) {
newPath.name = name newPath.name = name
@ -177,7 +96,14 @@ export const sketchFns = {
return { return {
programMemory: { programMemory: {
...programMemory, ...programMemory,
_sketch: [...(programMemory?._sketch || []), newPath], _sketch: [
{
...firstPath,
from,
},
...(programMemory?._sketch || []).slice(1),
newPath,
],
}, },
currentPath: newPath, currentPath: newPath,
} }
@ -188,28 +114,41 @@ export const sketchFns = {
sourceRange: SourceRange, sourceRange: SourceRange,
...args: any[] ...args: any[]
): PathReturn => { ): PathReturn => {
const _programMemory = addBasePath(programMemory)
const [x, y] = args const [x, y] = args
if (!_programMemory._sketch) { if (!programMemory._sketch) {
throw new Error('No sketch to draw on') throw new Error('No sketch to draw on')
} }
let from = getCoordsFromPaths( let from = getCoordsFromPaths(
programMemory?._sketch, programMemory?._sketch || [],
programMemory?._sketch.length - 1 (programMemory?._sketch?.length || 1) - 1
) )
const geo = lineGeo({ from: [...from, 0], to: [x, y, 0] })
const currentPath: Path = { const currentPath: Path = {
type: 'toPoint', type: 'toPoint',
to: [x, y], to: [x, y],
geo: lineGeo({ from: [...from, 0], to: [x, y, 0] }), from,
sourceRange, __geoMeta: {
sourceRange,
pathToNode: [], // TODO
geos: [
{
type: 'line',
geo: geo.line,
},
{
type: 'lineEnd',
geo: geo.tip,
},
],
},
} }
if (name) { if (name) {
currentPath.name = name currentPath.name = name
} }
return { return {
programMemory: { programMemory: {
..._programMemory, ...programMemory,
_sketch: [...(_programMemory._sketch || []), currentPath], _sketch: [...(programMemory._sketch || []), currentPath],
}, },
currentPath, currentPath,
} }
@ -222,31 +161,22 @@ export const sketchFns = {
name: string = '', name: string = '',
sourceRange: SourceRange, sourceRange: SourceRange,
length: number, length: number,
sketchVal: SketchGeo | Transform sketchVal: SketchGroup
): ExtrudeGeo | Transform => { ): ExtrudeGroup => {
const getSketchGeo = (sketchVal: SketchGeo | Transform): SketchGeo => { const getSketchGeo = (sketchVal: SketchGroup): SketchGroup => {
if ( return sketchVal
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
} }
const sketch = getSketchGeo(sketchVal) const sketch = getSketchGeo(sketchVal)
const { position, quaternion } = combineTransforms(sketchVal) const { position, rotation } = sketchVal
const extrudeFaces: ExtrudeFace[] = [] const extrudeSurfaces: ExtrudeSurface[] = []
sketch.sketch.map((line, index) => { sketch.value.map((line, index) => {
if (line.type === 'toPoint' && index !== 0) { if (line.type === 'toPoint' && index !== 0) {
const lastPoint = sketch.sketch[index - 1] const lastPoint = sketch.value[index - 1]
let from: [number, number] = [0, 0] let from: [number, number] = [0, 0]
if (lastPoint.type === 'toPoint') { if (lastPoint.type === 'toPoint') {
from = lastPoint.to from = lastPoint.to
} else if (lastPoint.type === 'base') {
from = lastPoint.from
} }
const to = line.to const to = line.to
const geo = extrudeGeo({ const geo = extrudeGeo({
@ -254,117 +184,87 @@ export const sketchFns = {
to: [to[0], to[1], 0], to: [to[0], to[1], 0],
length, length,
}) })
extrudeFaces.push({ extrudeSurfaces.push({
type: 'extrudeFace', type: 'extrudePlane',
quaternion, position, // todo should come from extrudeGeo
translate: position, rotation, // todo should come from extrudeGeo
geo, __geoMeta: {
sourceRanges: [line.sourceRange, sourceRange], geo,
sourceRange: line.__geoMeta.sourceRange,
pathToNode: line.__geoMeta.pathToNode,
},
}) })
} }
}) })
return { return {
type: 'extrudeGeo', type: 'extrudeGroup',
sourceRange, value: extrudeSurfaces,
surfaces: extrudeFaces, height: length,
position,
rotation,
__meta: [
{
sourceRange,
pathToNode: [], // TODO
},
],
} }
}, },
translate, translate,
} }
function rotateOnAxis(axisMultiplier: [number, number, number]) { function rotateOnAxis<T extends SketchGroup | ExtrudeGroup>(
axisMultiplier: [number, number, number]
) {
return ( return (
programMemory: ProgramMemory, programMemory: ProgramMemory,
sourceRange: SourceRange, sourceRange: SourceRange,
rotationD: number, rotationD: number,
sketch: SketchGeo | Transform sketch: T
): Transform => { ): T => {
const rotationR = rotationD * (Math.PI / 180) const rotationR = rotationD * (Math.PI / 180)
const rotateVec = new Vector3(...axisMultiplier) const rotateVec = new Vector3(...axisMultiplier)
const quaternion = new Quaternion() 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 { return {
type: 'transform', ...sketch,
rotation: quaternion.setFromAxisAngle(rotateVec, rotationR), rotation,
transform: [0, 0, 0], position,
sketch, __meta: [
sourceRange, ...sketch.__meta,
{
sourceRange,
pathToNode: [], // TODO
},
],
} }
} }
} }
function translate( function translate<T extends SketchGroup | ExtrudeGroup>(
programMemory: ProgramMemory, programMemory: ProgramMemory,
sourceRange: SourceRange, sourceRange: SourceRange,
vec3: [number, number, number], vec3: [number, number, number],
sketch: SketchGeo | Transform sketch: T
): Transform { ): T {
const oldPosition = new Vector3(...sketch.position)
const newPosition = oldPosition.add(new Vector3(...vec3))
return { return {
type: 'transform', ...sketch,
rotation: new Quaternion(), position: [newPosition.x, newPosition.y, newPosition.z],
transform: vec3, __meta: [
sketch, ...sketch.__meta,
sourceRange, {
} sourceRange,
} pathToNode: [], // TODO
},
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,
{
rotation: sketchVal.rotation,
transform: sketchVal.transform,
},
]
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 create from 'zustand'
import { addLineHighlight, EditorView } from './editor/highlightextension' import { addLineHighlight, EditorView } from './editor/highlightextension'
import { Program, abstractSyntaxTree } from './lang/abstractSyntaxTree' 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 { recast } from './lang/recast'
import { lexer } from './lang/tokeniser' import { lexer } from './lang/tokeniser'
import { Quaternion } from 'three'
export type Range = [number, number] export type Range = [number, number]
type PathToNode = (string | number)[]
type Position = [number, number, number]
type GuiModes = type GuiModes =
| { | {
mode: 'default' mode: 'default'
@ -18,7 +14,7 @@ type GuiModes =
| { | {
mode: 'sketch' mode: 'sketch'
sketchMode: 'points' sketchMode: 'points'
quaternion: Quaternion rotation: Rotation
position: Position position: Position
id?: string id?: string
pathToNode: PathToNode pathToNode: PathToNode
@ -26,7 +22,7 @@ type GuiModes =
| { | {
mode: 'sketch' mode: 'sketch'
sketchMode: 'sketchEdit' sketchMode: 'sketchEdit'
quaternion: Quaternion rotation: Rotation
position: Position position: Position
pathToNode: PathToNode pathToNode: PathToNode
} }
@ -37,7 +33,7 @@ type GuiModes =
| { | {
mode: 'canEditSketch' mode: 'canEditSketch'
pathToNode: PathToNode pathToNode: PathToNode
quaternion: Quaternion rotation: Rotation
position: Position position: Position
} }