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 { LineGeos } from '../lang/engine'
|
||||||
import { Vector3, DoubleSide, Quaternion, Vector2 } from 'three'
|
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({
|
function SketchLine({
|
||||||
geo,
|
geo,
|
||||||
sourceRange,
|
sourceRange,
|
||||||
@ -66,7 +21,6 @@ function SketchLine({
|
|||||||
sourceRange: [number, number]
|
sourceRange: [number, number]
|
||||||
forceHighlight?: boolean
|
forceHighlight?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { editorCursor } = useHeightlight(sourceRange)
|
|
||||||
const { setHighlightRange } = useStore(
|
const { setHighlightRange } = useStore(
|
||||||
({ setHighlightRange, selectionRange, guiMode, setGuiMode, ast }) => ({
|
({ setHighlightRange, selectionRange, guiMode, setGuiMode, ast }) => ({
|
||||||
setHighlightRange,
|
setHighlightRange,
|
||||||
@ -95,19 +49,13 @@ function SketchLine({
|
|||||||
>
|
>
|
||||||
<primitive object={geo.line} />
|
<primitive object={geo.line} />
|
||||||
<meshStandardMaterial
|
<meshStandardMaterial
|
||||||
color={
|
color={hovered ? 'hotpink' : forceHighlight ? 'skyblue' : 'orange'}
|
||||||
hovered
|
|
||||||
? 'hotpink'
|
|
||||||
: editorCursor || forceHighlight
|
|
||||||
? 'skyblue'
|
|
||||||
: 'orange'
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
<MovingSphere
|
<MovingSphere
|
||||||
geo={geo.tip}
|
geo={geo.tip}
|
||||||
sourceRange={sourceRange}
|
sourceRange={sourceRange}
|
||||||
editorCursor={editorCursor || forceHighlight}
|
editorCursor={forceHighlight}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -272,14 +220,51 @@ export function RenderViewerArtifacts({
|
|||||||
artifact: ViewerArtifact
|
artifact: ViewerArtifact
|
||||||
forceHighlight?: boolean
|
forceHighlight?: boolean
|
||||||
}) {
|
}) {
|
||||||
const { selectionRange } = useStore(({ selectionRange }) => ({
|
const { selectionRange, guiMode, ast, setGuiMode } = useStore(
|
||||||
selectionRange,
|
({ selectionRange, guiMode, ast, setGuiMode }) => ({
|
||||||
}))
|
selectionRange,
|
||||||
|
guiMode,
|
||||||
|
ast,
|
||||||
|
setGuiMode,
|
||||||
|
})
|
||||||
|
)
|
||||||
const [editorCursor, setEditorCursor] = useState(false)
|
const [editorCursor, setEditorCursor] = useState(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange)
|
const shouldHighlight = isOverlapping(artifact.sourceRange, selectionRange)
|
||||||
setEditorCursor(shouldHighlight)
|
setEditorCursor(shouldHighlight && artifact.type !== 'sketch')
|
||||||
}, [selectionRange, artifact.sourceRange])
|
}, [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') {
|
if (artifact.type === 'sketchLine') {
|
||||||
const { geo, sourceRange } = artifact
|
const { geo, sourceRange } = artifact
|
||||||
return (
|
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
|
sourceRange: SourceRange
|
||||||
children: ViewerArtifact[]
|
children: ViewerArtifact[]
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'sketch'
|
||||||
|
sourceRange: SourceRange
|
||||||
|
children: ViewerArtifact[]
|
||||||
|
}
|
||||||
|
|
||||||
type PreviousTransforms = {
|
type PreviousTransforms = {
|
||||||
rotation: [number, number, number]
|
rotation: [number, number, number]
|
||||||
@ -321,43 +326,48 @@ export const processShownObjects = (
|
|||||||
previousTransforms: PreviousTransforms = []
|
previousTransforms: PreviousTransforms = []
|
||||||
): ViewerArtifact[] => {
|
): ViewerArtifact[] => {
|
||||||
if (geoMeta?.type === 'sketchGeo') {
|
if (geoMeta?.type === 'sketchGeo') {
|
||||||
return geoMeta.sketch.map(({ geo, sourceRange, type }) => {
|
return [
|
||||||
if (type === 'toPoint') {
|
{
|
||||||
// const newGeo = geo.clone()
|
type: 'sketch',
|
||||||
const newGeo: LineGeos = {
|
sourceRange: geoMeta.sourceRange,
|
||||||
line: geo.line.clone(),
|
children: geoMeta.sketch.map(({ geo, sourceRange, type }) => {
|
||||||
tip: geo.tip.clone(),
|
if (type === 'toPoint') {
|
||||||
centre: geo.centre.clone(),
|
const newGeo: LineGeos = {
|
||||||
}
|
line: geo.line.clone(),
|
||||||
previousTransforms.forEach(({ rotation, transform }) => {
|
tip: geo.tip.clone(),
|
||||||
Object.values(newGeo).forEach((geoItem) => {
|
centre: geo.centre.clone(),
|
||||||
geoItem.rotateX(rotation[0])
|
}
|
||||||
geoItem.rotateY(rotation[1])
|
previousTransforms.forEach(({ rotation, transform }) => {
|
||||||
geoItem.rotateZ(rotation[2])
|
Object.values(newGeo).forEach((geoItem) => {
|
||||||
geoItem.translate(transform[0], transform[1], transform[2])
|
geoItem.rotateX(rotation[0])
|
||||||
})
|
geoItem.rotateY(rotation[1])
|
||||||
})
|
geoItem.rotateZ(rotation[2])
|
||||||
return {
|
geoItem.translate(transform[0], transform[1], transform[2])
|
||||||
type: 'sketchLine',
|
})
|
||||||
geo: newGeo,
|
})
|
||||||
sourceRange,
|
return {
|
||||||
}
|
type: 'sketchLine',
|
||||||
} else if (type === 'base') {
|
geo: newGeo,
|
||||||
const newGeo = geo.clone()
|
sourceRange,
|
||||||
previousTransforms.forEach(({ rotation, transform }) => {
|
}
|
||||||
newGeo.rotateX(rotation[0])
|
} else if (type === 'base') {
|
||||||
newGeo.rotateY(rotation[1])
|
const newGeo = geo.clone()
|
||||||
newGeo.rotateZ(rotation[2])
|
previousTransforms.forEach(({ rotation, transform }) => {
|
||||||
newGeo.translate(transform[0], transform[1], transform[2])
|
newGeo.rotateX(rotation[0])
|
||||||
})
|
newGeo.rotateY(rotation[1])
|
||||||
return {
|
newGeo.rotateZ(rotation[2])
|
||||||
type: 'sketchBase',
|
newGeo.translate(transform[0], transform[1], transform[2])
|
||||||
geo: newGeo,
|
})
|
||||||
sourceRange,
|
return {
|
||||||
}
|
type: 'sketchBase',
|
||||||
}
|
geo: newGeo,
|
||||||
throw new Error('Unknown geo type')
|
sourceRange,
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
throw new Error('Unknown geo type')
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
} else if (geoMeta.type === 'transform') {
|
} else if (geoMeta.type === 'transform') {
|
||||||
const referencedVar = geoMeta.sketch
|
const referencedVar = geoMeta.sketch
|
||||||
const parentArtifact: ViewerArtifact = {
|
const parentArtifact: ViewerArtifact = {
|
||||||
|
Reference in New Issue
Block a user