diff --git a/src/App.tsx b/src/App.tsx index d4c15e6a2..dc244fe92 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,7 +3,11 @@ import { Canvas } from '@react-three/fiber' import { Allotment } from 'allotment' import { OrbitControls, OrthographicCamera } from '@react-three/drei' import { lexer } from './lang/tokeniser' -import { abstractSyntaxTree } from './lang/abstractSyntaxTree' +import { + abstractSyntaxTree, + getNodePathFromSourceRange, + getNodeFromPath +} from './lang/abstractSyntaxTree' import { executor, processShownObjects, ViewerArtifact } from './lang/executor' import { recast } from './lang/recast' import { BufferGeometry } from 'three' @@ -32,7 +36,6 @@ function App() { setSelectionRange, selectionRange, guiMode, - setGuiMode, lastGuiMode, removeError, addLog, @@ -41,6 +44,8 @@ function App() { setAst, formatCode, ast, + setError, + errorState, } = useStore((s) => ({ editorView: s.editorView, setEditorView: s.setEditorView, @@ -56,6 +61,8 @@ function App() { setAst: s.setAst, lastGuiMode: s.lastGuiMode, formatCode: s.formatCode, + setError: s.setError, + errorState: s.errorState, })) // const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => { const onChange = (value: string, viewUpdate: ViewUpdate) => { @@ -110,8 +117,9 @@ function App() { setGeoArray(geos) removeError() console.log(programMemory) + setError() } catch (e: any) { - setGuiMode({ mode: 'codeError' }) + setError('problem') console.log(e) addLog(e) } @@ -173,7 +181,7 @@ function App() { - {guiMode.mode === 'codeError' && ( + {errorState.isError && (
                   {'last first: \n\n' +
@@ -201,19 +209,39 @@ function Line({
   sourceRange: [number, number]
   forceHighlight?: boolean
 }) {
-  const { setHighlightRange, selectionRange } = useStore(
-    ({ setHighlightRange, selectionRange }) => ({
-      setHighlightRange,
-      selectionRange,
-    })
-  )
+  const { setHighlightRange, selectionRange, guiMode, setGuiMode, ast } =
+    useStore(
+      ({ setHighlightRange, selectionRange, guiMode, setGuiMode, ast }) => ({
+        setHighlightRange,
+        selectionRange,
+        guiMode,
+        setGuiMode,
+        ast,
+      })
+    )
   // This reference will give us direct access to the mesh
   const ref = useRef() as any
   const [hovered, setHover] = useState(false)
   const [editorCursor, setEditorCursor] = useState(false)
+  const [didSetCanEdit, setDidSetCanEdit] = useState(false)
   useEffect(() => {
     const shouldHighlight = isOverlapping(sourceRange, selectionRange)
     setEditorCursor(shouldHighlight)
+    if (shouldHighlight && guiMode.mode === 'default' && ast) {
+      const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
+      const piper = getNodeFromPath(ast, pathToNode, 'PipeExpression')
+      const axis = piper.type !== 'PipeExpression' ? 'xy'
+        : piper?.body?.[1]?.callee?.name === 'rx' ? 'xz' : 'yz'
+      setGuiMode({ mode: 'canEditSketch', pathToNode, axis })
+      setDidSetCanEdit(true)
+    } else if (
+      !shouldHighlight &&
+      didSetCanEdit &&
+      guiMode.mode === 'canEditSketch'
+    ) {
+      setGuiMode({ mode: 'default' })
+      setDidSetCanEdit(false)
+    }
   }, [selectionRange, sourceRange])
 
   return (
@@ -257,7 +285,7 @@ function RenderViewerArtifacts({
     const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange)
     setEditorCursor(shouldHighlight)
   }, [selectionRange, artifact.sourceRange])
-  if (artifact.type === 'geo') {
+  if (artifact.type === 'sketchLine') {
     const { geo, sourceRange } = artifact
     return (
        {
               sketchMode: 'selectFace',
             })
           }}
+          className="border m-1 px-1 rounded"
         >
           Start sketch
         
       )}
-      {guiMode.mode === 'sketch' && guiMode.sketchMode === 'points' && (
-        
+      {guiMode.mode === 'canEditSketch' && (
+        
       )}
+
       {guiMode.mode !== 'default' && (
-        
+        
       )}
+      {guiMode.mode === 'sketch' &&
+        (guiMode.sketchMode === 'points' ||
+          guiMode.sketchMode === 'sketchEdit') && (
+          
+        )}
     
) } diff --git a/src/components/BasePlanes.tsx b/src/components/BasePlanes.tsx index 30a7d4182..93f16056d 100644 --- a/src/components/BasePlanes.tsx +++ b/src/components/BasePlanes.tsx @@ -56,9 +56,8 @@ export const BasePlanes = () => { setGuiMode({ mode: 'sketch', - sketchMode: 'points', + sketchMode: 'sketchEdit', axis, - id, pathToNode, }) diff --git a/src/components/SketchPlane.tsx b/src/components/SketchPlane.tsx index e7810d45f..b1a1bdb24 100644 --- a/src/components/SketchPlane.tsx +++ b/src/components/SketchPlane.tsx @@ -14,7 +14,7 @@ export const SketchPlane = () => { if (guiMode.mode !== 'sketch') { return null } - if (guiMode.sketchMode !== 'points') { + if (guiMode.sketchMode !== 'points' && guiMode.sketchMode !== 'sketchEdit' ) { return null } @@ -38,6 +38,9 @@ export const SketchPlane = () => { rotation={clickDetectPlaneRotation} name={sketchGridName} onClick={(e) => { + if (guiMode.sketchMode !== 'points') { + return + } const sketchGridIntersection = e.intersections.find( ({ object }) => object.name === sketchGridName ) diff --git a/src/lang/abstractSyntaxTree.ts b/src/lang/abstractSyntaxTree.ts index 0efc97f6b..66442e381 100644 --- a/src/lang/abstractSyntaxTree.ts +++ b/src/lang/abstractSyntaxTree.ts @@ -1176,7 +1176,8 @@ export function addLine( const dumbyStartend = { start: 0, end: 0 } const sketchExpression = getNodeFromPath( _node, - pathToNode + pathToNode, + 'SketchExpression' ) as SketchExpression const line: ExpressionStatement = { type: 'ExpressionStatement', @@ -1263,8 +1264,13 @@ function debuggerr(tokens: Token[], indexes: number[], msg = ''): string { return debugResult } -export function getNodeFromPath(node: Program, path: (string | number)[]) { +export function getNodeFromPath( + node: Program, + path: (string | number)[], + stopAt: string = '' +) { let currentNode = node as any + let stopAtNode = null let successfulPaths: (string | number)[] = [] for (const pathItem of path) { try { @@ -1272,6 +1278,11 @@ export function getNodeFromPath(node: Program, path: (string | number)[]) { throw new Error('not an object') currentNode = currentNode[pathItem] successfulPaths.push(pathItem) + if (currentNode.type === stopAt) { + // it will match the deepest node of the type + // instead of returning at the first match + stopAtNode = currentNode + } } catch (e) { throw new Error( `Could not find path ${pathItem} in node ${JSON.stringify( @@ -1282,7 +1293,7 @@ export function getNodeFromPath(node: Program, path: (string | number)[]) { ) } } - return currentNode + return stopAtNode || currentNode } type Path = (string | number)[] diff --git a/src/lang/executor.test.ts b/src/lang/executor.test.ts index 118767255..86a3a9fc9 100644 --- a/src/lang/executor.test.ts +++ b/src/lang/executor.test.ts @@ -3,7 +3,7 @@ import fs from 'node:fs' import { abstractSyntaxTree } from './abstractSyntaxTree' import { lexer } from './tokeniser' import { executor, ProgramMemory } from './executor' -import { Transform } from './sketch' +import { Transform, SketchGeo } from './sketch' describe('test', () => { it('test assigning two variables, the second summing with the first', () => { @@ -64,7 +64,7 @@ show(mySketch) ` const { root, return: _return } = exe(code) expect( - root.mySketch.map( + root.mySketch.sketch.map( ({ previousPath, firstPath, geo, ...rest }: any) => rest ) ).toEqual([ @@ -77,7 +77,7 @@ show(mySketch) sourceRange: [93, 100], }, ]) - expect(root.mySketch[0]).toEqual(root.mySketch[4].firstPath) + expect(root.mySketch.sketch[0]).toEqual(root.mySketch.sketch[4].firstPath) // hmm not sure what handle the "show" function expect(_return).toEqual([ { @@ -109,7 +109,7 @@ show(mySketch) // 'show(mySk1)', ].join('\n') const { root } = exe(code) - expect(root.mySk1).toHaveLength(4) + expect(root.mySk1.sketch).toHaveLength(4) expect(root?.rotated?.type).toBe('transform') }) @@ -124,9 +124,7 @@ show(mySketch) const { root } = exe(code) const striptVersion = removeGeoFromSketch(root.mySk1) expect(striptVersion).toEqual({ - type: 'transform', - rotation: [1.5707963267948966, 0, 0], - transform: [0, 0, 0], + type: 'sketchGeo', sketch: [ { type: 'base', @@ -150,8 +148,38 @@ show(mySketch) sourceRange: [60, 71], }, ], - sourceRange: [77, 86], + 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], + // }) }) }) @@ -166,8 +194,8 @@ function exe( return executor(ast, programMemory) } -function removeGeoFromSketch(sketch: Transform): any { - if (!Array.isArray(sketch.sketch)) { +function removeGeoFromSketch(sketch: Transform | SketchGeo): any { + if (sketch.type !== 'sketchGeo') { return removeGeoFromSketch(sketch.sketch) } return { diff --git a/src/lang/executor.ts b/src/lang/executor.ts index 696ecb630..6a76e87ef 100644 --- a/src/lang/executor.ts +++ b/src/lang/executor.ts @@ -4,7 +4,7 @@ import { BinaryExpression, PipeExpression, } from './abstractSyntaxTree' -import { Path, Transform, sketchFns } from './sketch' +import { Path, Transform, SketchGeo, sketchFns } from './sketch' import { BufferGeometry } from 'three' export interface ProgramMemory { @@ -63,7 +63,12 @@ export const executor = ( ) _sketch = newProgramMemory._sketch } - _programMemory.root[variableName] = _sketch + const newSketch: SketchGeo = { + type: 'sketchGeo', + sketch: _sketch, + sourceRange: [sketchInit.start, sketchInit.end], + } + _programMemory.root[variableName] = newSketch } else if (declaration.init.type === 'FunctionExpression') { const fnInit = declaration.init @@ -271,9 +276,14 @@ function executePipeBody( _sketch = newProgramMemory._sketch } // _programMemory.root[variableName] = _sketch + const newSketch: SketchGeo = { + type: 'sketchGeo', + sketch: _sketch, + sourceRange: [expression.start, expression.end], + } return executePipeBody(body, programMemory, expressionIndex + 1, [ ...previousResults, - _sketch, + newSketch, ]) } @@ -284,7 +294,7 @@ type SourceRange = [number, number] export type ViewerArtifact = | { - type: 'geo' + type: 'sketchLine' sourceRange: SourceRange geo: BufferGeometry } @@ -301,11 +311,11 @@ type PreviousTransforms = { export const processShownObjects = ( programMemory: ProgramMemory, - geoMeta: Path[] | Transform, + geoMeta: SketchGeo | Transform, previousTransforms: PreviousTransforms = [] ): ViewerArtifact[] => { - if (Array.isArray(geoMeta)) { - return geoMeta.map(({ geo, sourceRange }) => { + if (geoMeta?.type === 'sketchGeo') { + return geoMeta.sketch.map(({ geo, sourceRange }) => { const newGeo = geo.clone() previousTransforms.forEach(({ rotation, transform }) => { newGeo.rotateX(rotation[0]) @@ -315,7 +325,7 @@ export const processShownObjects = ( }) return { - type: 'geo', + type: 'sketchLine', geo: newGeo, sourceRange, } diff --git a/src/lang/sketch.ts b/src/lang/sketch.ts index 5f7fd30c5..d6f1e5e72 100644 --- a/src/lang/sketch.ts +++ b/src/lang/sketch.ts @@ -59,7 +59,13 @@ export interface Transform { type: 'transform' rotation: Rotation3 transform: Translate3 - sketch: Path[] | Transform + sketch: SketchGeo | Transform + sourceRange: SourceRange +} + +export interface SketchGeo { + type: 'sketchGeo' + sketch: Path[] sourceRange: SourceRange } @@ -213,7 +219,7 @@ function RotateOnAxis(axisMultiplier: [number, number, number]) { programMemory: ProgramMemory, sourceRange: SourceRange, rotationD: number, - sketch: Path[] | Transform + sketch: SketchGeo | Transform ): Transform => { const rotationR = rotationD * (Math.PI / 180) return { diff --git a/src/useStore.ts b/src/useStore.ts index 4525ba50c..f61a0e064 100644 --- a/src/useStore.ts +++ b/src/useStore.ts @@ -6,6 +6,9 @@ import { lexer } from './lang/tokeniser' export type Range = [number, number] +type Plane = 'xy' | 'xz' | 'yz' +type PathToNode = (string | number)[] + type GuiModes = | { mode: 'default' @@ -13,17 +16,25 @@ type GuiModes = | { mode: 'sketch' sketchMode: 'points' - axis: 'xy' | 'xz' | 'yz' + axis: Plane id?: string - pathToNode: (string | number)[] + pathToNode: PathToNode } + | { + mode: 'sketch' + sketchMode: 'sketchEdit' + axis: Plane + pathToNode: PathToNode + } | { mode: 'sketch' sketchMode: 'selectFace' } | { - mode: 'codeError' - } + mode: 'canEditSketch' + pathToNode: PathToNode + axis: Plane + } interface StoreState { editorView: EditorView | null @@ -45,6 +56,11 @@ interface StoreState { code: string setCode: (code: string) => void formatCode: () => void + errorState: { + isError: boolean + error: string + } + setError: (error?: string) => void } export const useStore = create()((set, get) => ({ @@ -69,19 +85,10 @@ export const useStore = create()((set, get) => ({ setGuiMode: (guiMode) => { const lastGuiMode = get().guiMode set({ guiMode }) - if (guiMode.mode !== 'codeError') { - // don't set lastGuiMode to and error state - // as the point fo lastGuiMode is to restore the last healthy state - // todo maybe rename to lastHealthyGuiMode and remove this comment - set({ lastGuiMode }) - } }, removeError: () => { const lastGuiMode = get().lastGuiMode const currentGuiMode = get().guiMode - if (currentGuiMode.mode === 'codeError') { - set({ guiMode: lastGuiMode }) - } }, logs: [], addLog: (log) => { @@ -108,4 +115,11 @@ export const useStore = create()((set, get) => ({ const newCode = recast(ast) set({ code: newCode, ast }) }, + errorState: { + isError: false, + error: '', + }, + setError: (error = '') => { + set({ errorState: { isError: !!error, error } }) + } }))