fix edit sketch from cursor

This commit is contained in:
Kurt Hutten IrevDev
2022-12-25 21:14:43 +11:00
parent 1c1ceae4d3
commit b09aae84fc
3 changed files with 160 additions and 95 deletions

View File

@ -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({
>
<primitive object={geo.line} />
<meshStandardMaterial
color={
hovered
? 'hotpink'
: editorCursor || forceHighlight
? 'skyblue'
: 'orange'
}
color={hovered ? 'hotpink' : forceHighlight ? 'skyblue' : 'orange'}
/>
</mesh>
<MovingSphere
geo={geo.tip}
sourceRange={sourceRange}
editorCursor={editorCursor || forceHighlight}
editorCursor={forceHighlight}
/>
</>
)
@ -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 (

70
src/lang/artifact.test.ts Normal file
View File

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

View File

@ -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 = {