fix edit sketch from cursor
This commit is contained in:
@ -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
70
src/lang/artifact.test.ts
Normal 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
|
||||
})
|
||||
}
|
@ -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 = {
|
||||
|
Reference in New Issue
Block a user