SketchOnFace UI (#1664)

* always enter edit mode

* initial blocking of extra code-mirror updates

* dry out code

* rejig selections

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* clean up

* stream clean up

* update export

* sketch mode can be entered and exited for extrude faces

But has bugs

* startSketchOn working in some cases, editsketch animation working but not orientation of instersection plane etc

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"

This reverts commit 406fca4c55.

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* remove comment

* add sketch on face e2e test

* tweenCamToNegYAxis should respect reduced motion

* initial sketch on face working with test

* remove temporary toolbar button and xState flow

* un-used vars

* snapshot test tweak

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* type tidy up

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"

This reverts commit c39b8ebf95.

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"

This reverts commit fecf6f490a.

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* rename

* sketch on sketch on sketch

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* typo

* startSketchOn Endcaps

end works, start is weird still

* clear selections for entity_ids that are not recognised

* fix sketch on end cap of second order extrustion

* tiny clean up

* fix sketch on close segment/face

* clean up 'lastCodeMirrorSelectionUpdatedFromScene'

* add code mode test for sketchOnExtrudedFace

* make end cap selection more robust

* update js artifacts for extrudes

* update kcl docs

* clean up

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Kurt Hutten
2024-03-22 10:23:04 +11:00
committed by GitHub
parent e773e932b0
commit 0e916cfd5b
63 changed files with 1860 additions and 720 deletions

View File

@ -23,9 +23,9 @@ import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
import { pathMapToSelections } from 'lang/util'
import { useStore } from 'useStore'
import {
Selections,
canExtrudeSelection,
handleSelectionBatch,
handleSelectionWithShift,
isSelectionLastLine,
isSketchPipe,
} from 'lib/selections'
@ -34,14 +34,20 @@ import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
import useStateMachineCommands from 'hooks/useStateMachineCommands'
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
import { sceneInfra } from 'clientSideScene/sceneInfra'
import { getSketchQuaternion } from 'clientSideScene/sceneEntities'
import { startSketchOnDefault } from 'lang/modifyAst'
import { Program } from 'lang/wasm'
import { isSingleCursorInPipe } from 'lang/queryAst'
import {
getSketchQuaternion,
getSketchOrientationDetails,
} from 'clientSideScene/sceneEntities'
import { sketchOnExtrudedFace, startSketchOnDefault } from 'lang/modifyAst'
import { Program, parse } from 'lang/wasm'
import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
import { TEST } from 'env'
import { exportFromEngine } from 'lib/exportFromEngine'
import { Models } from '@kittycad/lib/dist/types/src'
import toast from 'react-hot-toast'
import { EditorSelection } from '@uiw/react-codemirror'
import { Vector3 } from 'three'
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -69,9 +75,15 @@ export const ModelingMachineProvider = ({
const streamRef = useRef<HTMLDivElement>(null)
useSetupEngineManager(streamRef, token)
const { isShiftDown, editorView } = useStore((s) => ({
const {
isShiftDown,
editorView,
setLastCodeMirrorSelectionUpdatedFromScene,
} = useStore((s) => ({
isShiftDown: s.isShiftDown,
editorView: s.editorView,
setLastCodeMirrorSelectionUpdatedFromScene:
s.setLastCodeMirrorSelectionUpdatedFromScene,
}))
// Settings machine setup
@ -92,92 +104,98 @@ export const ModelingMachineProvider = ({
{
actions: {
'sketch exit execute': () => {
kclManager.executeAst()
try {
kclManager.executeAst(parse(kclManager.code))
} catch (e) {
kclManager.executeAst()
}
},
'Set selection': assign(({ selectionRanges }, event) => {
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
const setSelections = event.data
if (!editorView) return {}
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
return { selectionRanges: setSelections.selection }
else if (setSelections.selectionType === 'otherSelection') {
const {
codeMirrorSelection,
selectionRangeTypeMap,
otherSelections,
} = handleSelectionWithShift({
otherSelection: setSelections.selection,
currentSelections: selectionRanges,
isShiftDown,
})
setTimeout(() => {
editorView.dispatch({
selection: codeMirrorSelection,
})
})
return {
selectionRangeTypeMap,
selectionRanges: {
codeBasedSelections: selectionRanges.codeBasedSelections,
otherSelections,
},
}
} else if (setSelections.selectionType === 'singleCodeCursor') {
// This DOES NOT set the `selectionRanges` in xstate context
// instead it updates/dispatches to the editor, which in turn updates the xstate context
// I've found this the best way to deal with the editor without causing an infinite loop
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
// because we want to respect the user manually placing the cursor too.
// for more details on how selections see `src/lib/selections.ts`.
const {
codeMirrorSelection,
selectionRangeTypeMap,
otherSelections,
} = handleSelectionWithShift({
codeSelection: setSelections.selection,
currentSelections: selectionRanges,
isShiftDown,
})
if (codeMirrorSelection) {
setTimeout(() => {
editorView.dispatch({
selection: codeMirrorSelection,
})
})
}
if (!setSelections.selection) {
return {
selectionRangeTypeMap,
selectionRanges: {
codeBasedSelections: selectionRanges.codeBasedSelections,
otherSelections,
},
const dispatchSelection = (selection?: EditorSelection) => {
if (!selection) return // TODO less of hack for the below please
setLastCodeMirrorSelectionUpdatedFromScene(Date.now())
setTimeout(() => editorView.dispatch({ selection }))
}
let selections: Selections = {
codeBasedSelections: [],
otherSelections: [],
}
if (setSelections.selectionType === 'singleCodeCursor') {
if (!setSelections.selection && isShiftDown) {
} else if (!setSelections.selection && !isShiftDown) {
selections = {
codeBasedSelections: [],
otherSelections: [],
}
} else if (setSelections.selection && !isShiftDown) {
selections = {
codeBasedSelections: [setSelections.selection],
otherSelections: [],
}
} else if (setSelections.selection && isShiftDown) {
selections = {
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
setSelections.selection,
],
otherSelections: selectionRanges.otherSelections,
}
}
const {
engineEvents,
codeMirrorSelection,
updateSceneObjectColors,
} = handleSelectionBatch({
selections,
})
codeMirrorSelection && dispatchSelection(codeMirrorSelection)
engineEvents &&
engineEvents.forEach((event) =>
engineCommandManager.sendSceneCommand(event)
)
updateSceneObjectColors()
return {
selectionRangeTypeMap,
selectionRanges: {
codeBasedSelections: selectionRanges.codeBasedSelections,
otherSelections,
},
selectionRanges: selections,
}
}
// This DOES NOT set the `selectionRanges` in xstate context
// same as comment above
const { codeMirrorSelection, selectionRangeTypeMap } =
handleSelectionBatch({
selections: setSelections.selection,
})
if (codeMirrorSelection) {
setTimeout(() => {
editorView.dispatch({
selection: codeMirrorSelection,
})
})
if (setSelections.selectionType === 'mirrorCodeMirrorSelections') {
return {
selectionRanges: setSelections.selection,
}
}
return { selectionRangeTypeMap }
if (setSelections.selectionType === 'otherSelection') {
if (isShiftDown) {
selections = {
codeBasedSelections: selectionRanges.codeBasedSelections,
otherSelections: [setSelections.selection],
}
} else {
selections = {
codeBasedSelections: [],
otherSelections: [setSelections.selection],
}
}
const { engineEvents, updateSceneObjectColors } =
handleSelectionBatch({
selections,
})
engineEvents &&
engineEvents.forEach((event) =>
engineCommandManager.sendSceneCommand(event)
)
updateSceneObjectColors()
return {
selectionRanges: selections,
}
}
return {}
}),
'Engine export': (_, event) => {
if (event.type !== 'Export' || TEST) return
@ -255,10 +273,10 @@ export const ModelingMachineProvider = ({
kclManager.kclErrors.length === 0 && kclManager.ast.body.length > 0,
},
services: {
'AST-undo-startSketchOn': async ({ sketchPathToNode }) => {
if (!sketchPathToNode) return
'AST-undo-startSketchOn': async ({ sketchDetails }) => {
if (!sketchDetails) return
const newAst: Program = JSON.parse(JSON.stringify(kclManager.ast))
const varDecIndex = sketchPathToNode[1][0]
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
// remove body item at varDecIndex
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
await kclManager.executeAstMock(newAst, { updates: 'code' })
@ -267,28 +285,69 @@ export const ModelingMachineProvider = ({
onDrag: () => {},
})
},
'animate-to-face': async (_, { data: { plane, normal } }) => {
'animate-to-face': async (_, { data }) => {
if (data.type === 'extrudeFace') {
const { modifiedAst, pathToNode: pathToNewSketchNode } =
sketchOnExtrudedFace(
kclManager.ast,
data.extrudeSegmentPathToNode,
kclManager.programMemory,
data.cap
)
await kclManager.executeAstMock(modifiedAst, { updates: 'code' })
const forward = new Vector3(...data.zAxis)
const up = new Vector3(...data.yAxis)
let target = new Vector3(...data.position).multiplyScalar(
sceneInfra._baseUnitMultiplier
)
const quaternion = quaternionFromUpNForward(up, forward)
await sceneInfra.camControls.tweenCameraToQuaternion(
quaternion,
target
)
return {
sketchPathToNode: pathToNewSketchNode,
zAxis: data.zAxis,
yAxis: data.yAxis,
origin: data.position,
}
}
const { modifiedAst, pathToNode } = startSketchOnDefault(
kclManager.ast,
plane
data.plane
)
await kclManager.updateAst(modifiedAst, false)
const quaternion = getSketchQuaternion(pathToNode, normal)
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
const quat = await getSketchQuaternion(pathToNode, data.zAxis)
await sceneInfra.camControls.tweenCameraToQuaternion(quat)
return {
sketchPathToNode: pathToNode,
sketchNormalBackUp: normal,
zAxis: data.zAxis,
yAxis: data.yAxis,
origin: [0, 0, 0],
}
},
'animate-to-sketch': async ({
sketchPathToNode,
sketchNormalBackUp,
}) => {
const quaternion = getSketchQuaternion(
sketchPathToNode || [],
sketchNormalBackUp
'animate-to-sketch': async ({ selectionRanges }) => {
const sourceRange = selectionRanges.codeBasedSelections[0].range
const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast,
sourceRange
)
await sceneInfra.camControls.tweenCameraToQuaternion(quaternion)
const info = await getSketchOrientationDetails(sketchPathToNode || [])
await sceneInfra.camControls.tweenCameraToQuaternion(
info.quat,
new Vector3(...info.sketchDetails.origin)
)
return {
sketchPathToNode: sketchPathToNode || [],
zAxis: info.sketchDetails.zAxis || null,
yAxis: info.sketchDetails.yAxis || null,
origin: info.sketchDetails.origin.map(
(a) => a / sceneInfra._baseUnitMultiplier
) as [number, number, number],
}
},
'Get horizontal info': async ({
selectionRanges,