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 } })
+ }
}))