diff --git a/src/components/SketchLine.tsx b/src/components/SketchLine.tsx index f66172ef8..2d97028cc 100644 --- a/src/components/SketchLine.tsx +++ b/src/components/SketchLine.tsx @@ -12,51 +12,6 @@ import { isOverlapping } from '../lib/utils' import { LineGeos } from '../lang/engine' import { Vector3, DoubleSide, Quaternion, Vector2 } from 'three' -function useHeightlight(sourceRange: [number, number]) { - const { selectionRange, guiMode, setGuiMode, ast } = useStore((s) => ({ - setHighlightRange: s.setHighlightRange, - selectionRange: s.selectionRange, - guiMode: s.guiMode, - setGuiMode: s.setGuiMode, - ast: s.ast, - })) - // This reference will give us direct access to the mesh - 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 quaternion = new Quaternion() - if (piper.type === 'PipeExpression') { - const rotateName = piper?.body?.[1]?.callee?.name - const rotateValue = piper?.body?.[1]?.arguments[0].value - let rotateAxis = new Vector3(1, 0, 0) - if (rotateName === 'ry') { - rotateAxis = new Vector3(0, 1, 0) - } else if (rotateName === 'rz') { - rotateAxis = new Vector3(0, 0, 1) - } - quaternion.setFromAxisAngle(rotateAxis, (Math.PI * rotateValue) / 180) - } - setGuiMode({ mode: 'canEditSketch', pathToNode, quaternion }) - setDidSetCanEdit(true) - } else if ( - !shouldHighlight && - didSetCanEdit && - guiMode.mode === 'canEditSketch' - ) { - setGuiMode({ mode: 'default' }) - setDidSetCanEdit(false) - } - }, [selectionRange, sourceRange]) - return { - editorCursor, - } -} - function SketchLine({ geo, sourceRange, @@ -66,7 +21,6 @@ function SketchLine({ sourceRange: [number, number] forceHighlight?: boolean }) { - const { editorCursor } = useHeightlight(sourceRange) const { setHighlightRange } = useStore( ({ setHighlightRange, selectionRange, guiMode, setGuiMode, ast }) => ({ setHighlightRange, @@ -95,19 +49,13 @@ function SketchLine({ > ) @@ -272,14 +220,51 @@ export function RenderViewerArtifacts({ artifact: ViewerArtifact forceHighlight?: boolean }) { - const { selectionRange } = useStore(({ selectionRange }) => ({ - selectionRange, - })) + const { selectionRange, guiMode, ast, setGuiMode } = useStore( + ({ selectionRange, guiMode, ast, setGuiMode }) => ({ + selectionRange, + guiMode, + ast, + setGuiMode, + }) + ) const [editorCursor, setEditorCursor] = useState(false) useEffect(() => { const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange) - setEditorCursor(shouldHighlight) + 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 piper = getNodeFromPath(ast, pathToNode, 'PipeExpression') + const quaternion = new Quaternion() + if (piper.type === 'PipeExpression') { + const rotateName = piper?.body?.[1]?.callee?.name + const rotateValue = piper?.body?.[1]?.arguments[0].value + let rotateAxis = new Vector3(1, 0, 0) + if (rotateName === 'ry') { + rotateAxis = new Vector3(0, 1, 0) + } else if (rotateName === 'rz') { + rotateAxis = new Vector3(0, 0, 1) + } + quaternion.setFromAxisAngle(rotateAxis, (Math.PI * rotateValue) / 180) + } + setGuiMode({ mode: 'canEditSketch', pathToNode, quaternion }) + } 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 ( diff --git a/src/lang/artifact.test.ts b/src/lang/artifact.test.ts new file mode 100644 index 000000000..a2cc37c0e --- /dev/null +++ b/src/lang/artifact.test.ts @@ -0,0 +1,70 @@ +import { abstractSyntaxTree } from './abstractSyntaxTree' +import { lexer } from './tokeniser' +import { executor, ViewerArtifact, processShownObjects } from './executor' + +describe('findClosingBrace', () => { + test('finds the closing brace', () => { + const code = ` +sketch mySketch001 { + lineTo(-1.59, -1.54) + lineTo(0.46, -5.82) +} + |> rx(45, %) +show(mySketch001)` + const programMemory = executor(abstractSyntaxTree(lexer(code))) + const geos: ViewerArtifact[] = + programMemory?.return?.flatMap( + ({ name }: { name: string }) => + processShownObjects(programMemory, programMemory?.root?.[name]) || [] + ) || [] + const artifactsWithouGeos = removeGeo(geos) + expect(artifactsWithouGeos).toEqual([ + { + type: 'parent', + sourceRange: [74, 83], + children: [ + { + type: 'sketch', + sourceRange: [20, 68], + children: [ + { + type: 'sketchBase', + sourceRange: [0, 0], + }, + { + type: 'sketchLine', + sourceRange: [24, 44], + }, + { + type: 'sketchLine', + sourceRange: [47, 66], + }, + ], + }, + ], + }, + ]) + }) +}) + +function removeGeo(arts: ViewerArtifact[]): any { + return arts.map((art) => { + if (art.type === 'sketchLine' || art.type === 'sketchBase') { + const { geo, ...rest } = art + return rest + } + if (art.type === 'parent') { + return { + ...art, + children: removeGeo(art.children), + } + } + if (art.type === 'sketch') { + return { + ...art, + children: removeGeo(art.children), + } + } + return art + }) +} diff --git a/src/lang/executor.ts b/src/lang/executor.ts index c585b0934..465f7f759 100644 --- a/src/lang/executor.ts +++ b/src/lang/executor.ts @@ -309,6 +309,11 @@ export type ViewerArtifact = sourceRange: SourceRange children: ViewerArtifact[] } + | { + type: 'sketch' + sourceRange: SourceRange + children: ViewerArtifact[] + } type PreviousTransforms = { rotation: [number, number, number] @@ -321,43 +326,48 @@ export const processShownObjects = ( previousTransforms: PreviousTransforms = [] ): ViewerArtifact[] => { if (geoMeta?.type === 'sketchGeo') { - return geoMeta.sketch.map(({ geo, sourceRange, type }) => { - if (type === 'toPoint') { - // const newGeo = geo.clone() - const newGeo: LineGeos = { - line: geo.line.clone(), - tip: geo.tip.clone(), - centre: geo.centre.clone(), - } - previousTransforms.forEach(({ rotation, transform }) => { - Object.values(newGeo).forEach((geoItem) => { - geoItem.rotateX(rotation[0]) - geoItem.rotateY(rotation[1]) - geoItem.rotateZ(rotation[2]) - geoItem.translate(transform[0], transform[1], transform[2]) - }) - }) - return { - type: 'sketchLine', - geo: newGeo, - sourceRange, - } - } else if (type === 'base') { - const newGeo = geo.clone() - previousTransforms.forEach(({ rotation, transform }) => { - newGeo.rotateX(rotation[0]) - newGeo.rotateY(rotation[1]) - newGeo.rotateZ(rotation[2]) - newGeo.translate(transform[0], transform[1], transform[2]) - }) - return { - type: 'sketchBase', - geo: newGeo, - sourceRange, - } - } - throw new Error('Unknown geo type') - }) + 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(), + } + previousTransforms.forEach(({ rotation, transform }) => { + Object.values(newGeo).forEach((geoItem) => { + geoItem.rotateX(rotation[0]) + geoItem.rotateY(rotation[1]) + geoItem.rotateZ(rotation[2]) + geoItem.translate(transform[0], transform[1], transform[2]) + }) + }) + return { + type: 'sketchLine', + geo: newGeo, + sourceRange, + } + } else if (type === 'base') { + const newGeo = geo.clone() + previousTransforms.forEach(({ rotation, transform }) => { + newGeo.rotateX(rotation[0]) + newGeo.rotateY(rotation[1]) + newGeo.rotateZ(rotation[2]) + newGeo.translate(transform[0], transform[1], transform[2]) + }) + return { + type: 'sketchBase', + geo: newGeo, + sourceRange, + } + } + throw new Error('Unknown geo type') + }), + }, + ] } else if (geoMeta.type === 'transform') { const referencedVar = geoMeta.sketch const parentArtifact: ViewerArtifact = {