Files
modeling-app/src/machines/modelingMachine.ts
Adam Chalmers fdbfd0c4b6 Fix typos (#972)
* Update twenty-twenty

Due to engine PR #1566

* Fix typos
2023-11-01 22:34:54 +00:00

1016 lines
35 KiB
TypeScript

import { PathToNode } from 'lang/wasm'
import { engineCommandManager } from 'lang/std/engineConnection'
import { isReducedMotion } from 'lang/util'
import {
Axis,
Selection,
SelectionRangeTypeMap,
Selections,
} from 'lib/selections'
import { assign, createMachine } from 'xstate'
import { v4 as uuidv4 } from 'uuid'
import { isCursorInSketchCommandRange } from 'lang/util'
import {
doesPipeHaveCallExp,
getNodePathFromSourceRange,
hasExtrudeSketchGroup,
} from 'lang/queryAst'
import { kclManager } from 'lang/KclSinglton'
import {
horzVertInfo,
applyConstraintHorzVert,
} from 'components/Toolbar/HorzVert'
import {
applyConstraintHorzVertAlign,
horzVertDistanceInfo,
} from 'components/Toolbar/SetHorzVertDistance'
import { angleBetweenInfo } from 'components/Toolbar/SetAngleBetween'
import { setAngleLengthInfo } from 'components/Toolbar/setAngleLength'
import {
applyConstraintEqualLength,
setEqualLengthInfo,
} from 'components/Toolbar/EqualLength'
import { extrudeSketch } from 'lang/modifyAst'
import { getNodeFromPath } from '../lang/queryAst'
import { CallExpression, PipeExpression } from '../lang/wasm'
import { getConstraintLevelFromSourceRange } from 'lang/std/sketchcombos'
import {
applyConstraintEqualAngle,
equalAngleInfo,
} from 'components/Toolbar/EqualAngle'
import {
applyRemoveConstrainingValues,
removeConstrainingValuesInfo,
} from 'components/Toolbar/RemoveConstrainingValues'
import { intersectInfo } from 'components/Toolbar/Intersect'
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
export type SetSelections =
| {
selectionType: 'singleCodeCursor'
selection?: Selection
}
| {
selectionType: 'otherSelection'
selection: Axis
}
| {
selectionType: 'completeSelection'
selection: Selections
}
| {
selectionType: 'mirrorCodeMirrorSelections'
selection: Selections
}
export type ModelingMachineEvent =
| { type: 'Deselect all' }
| { type: 'Deselect edge'; data: Selection & { type: 'edge' } }
| { type: 'Deselect axis'; data: Axis }
| {
type: 'Deselect segment'
data: Selection & { type: 'line' | 'arc' }
}
| { type: 'Deselect face'; data: Selection & { type: 'face' } }
| {
type: 'Deselect point'
data: Selection & { type: 'point' | 'line-end' | 'line-mid' }
}
| { type: 'Enter sketch' }
| { type: 'Select all'; data: Selection & { type: 'all ' } }
| { type: 'Select edge'; data: Selection & { type: 'edge' } }
| { type: 'Select axis'; data: Axis }
| { type: 'Select segment'; data: Selection & { type: 'line' | 'arc' } }
| { type: 'Select face'; data: Selection & { type: 'face' } }
| { type: 'Select default plane'; data: { planeId: string } }
| { type: 'Set selection'; data: SetSelections }
| {
type: 'Select point'
data: Selection & { type: 'point' | 'line-end' | 'line-mid' }
}
| { type: 'Sketch no face' }
| { type: 'Toggle gui mode' }
| { type: 'Cancel' }
| { type: 'CancelSketch' }
| {
type: 'Add point'
data: {
coords: { x: number; y: number }[]
axis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz' | null
segmentId?: string
}
}
| { type: 'Equip tool' }
| { type: 'Equip move tool' }
| { type: 'Set radius' }
| { type: 'Complete line' }
| { type: 'Set distance' }
| { type: 'Equip new tool' }
| { type: 'update_code'; data: string }
| { type: 'Make segment horizontal' }
| { type: 'Make segment vertical' }
| { type: 'Constrain horizontal distance' }
| { type: 'Constrain vertical distance' }
| { type: 'Constrain angle' }
| { type: 'Constrain perpendicular distance' }
| { type: 'Constrain horizontally align' }
| { type: 'Constrain vertically align' }
| { type: 'Constrain length' }
| { type: 'Constrain equal length' }
| { type: 'Constrain parallel' }
| { type: 'Constrain remove constraints' }
| { type: 'extrude intent' }
| { type: 'Re-execute' }
export const modelingMachine = createMachine(
{
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogDMAVgDsAOmEiAjLMGCAHIIAsAJlHKANCACeiaQDZBEsctGrVATmmiaNQQF8H2tBhz5x2CJjAEAymDsAASwWGDknNy0DEggLGyRPLECCMrCCuL20qrCRpYagvnaegjZBtLiBqLploIGCtZVyk4u6Fh4UJ7evgAicGERQSx47NG88RxcSaApdcKSNKIKi8ppwsrLwsX60oriqgaWBgbKImpWqi0gru0eXj4EfaE+g5AwY7ETibyzx5lLu1sygM61ERV0+ho0mU4gURlOwihlis2SuN3cnXuvX6L2CJD42FgH2YrEm3B+QnM4mqdhoyPUILqBm2CAaFTS5houQUCMczmubQxXQeAVxQ1QI2JcVJ32SiGUXPEhjBtWklgaokMzIhqVUNHEG0WgjVqkEUN2aMFHWFvlF4WCbzAUq+UwpqRoGQ2pwacPW4JKJxhgYU0kWu3Wymklrc1qx-gGeIJRPo4xlrrlqUEqkqdMKOSRNEOLPWGTVhkswlU0O5fNaMbu3XjYoAZiRKM60+SMwqMgY7Go6mbalCWaIjLCjtJ0n36tUFNHbpjGwBRXDsMAAJxCAGtAuQABYdhLpmY7SPiSzAyOXwGFUQs01BtXVEEV9VSZr89Gxldrzc7vdD2kGISWPLtTwQepBGpEN8lDGhVBDLYdTUfVZzvYFQ3MS4vytBsHieBMglbdsU0+Ttpn4Sk0KzBR1EKdING1EozQqYxajyNQ6MOBchTjO1BhITBMCPMlKJSSNoIsEEllQrlhGQko9RhZEDFUL1vWEUMcLrRcbUeHF7SCISRLI0CxLdRR2ROJQwQQ2RmMQENJD1OxjR5aE+1rAV6yXB4wD4dgNwAVwwIIRjANdRNlCDlGRJU6kfapK0vdYWSnBQJEsfJMtODYrM-XS+MbAKgtCsBwr-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-u6Y54Be496K5-77in7F6D6Wb2YXiwQ5AhIVpT4HDzC0hWDHCqQBrN6-6RQEr7jt47577S6y555gG4GZKQFB4SCWy3zZQ2BViwrIE14lKXj1DLBVBaQDS7qr4-5DDtKRRdI9J9Ip6DK9ZF4d4e7AHd5H7uptIbgdKCG9IbjupC4sKUHQEpDqA8gLBLBnaahqCP4KjzBmiTIlJmirrYF8HyECEO5CH9LO6UAEEbji677Z4kEyFyEKG2FKEqEOH95F4xbaGrShrjwGEPgiASAmGrBmE1oOKQxSJnRBC4AhYkS2hEQYCtg9L3S9Jtr5o1KnYVhvTXiTJGAfpTifRQh1DzxzzmCf56QkA26cD4A7y4hTCFYtH2glbn5DyhzUi1aL7HD8z3KqbUh9gbDqjPqXgSLxEeAHjhDbgdAdH9zcByYaH6D4b7CqYcgHBXJWpqRkqrDmiBKRhtxzHkALHNF9yJBybracybalC06VDGiVwaCgxFg0ppDQTlA1Y2YegIQ3aSYZpuqep8Deq+prEFo8jQQ2DyAGIA7Nyjiiz9Rwh2CVgWAHD6aAkEDAlerdLgndEBwKhkxlq6HQrFjWYeggiUzTYnFXDJEYDwCxC7q3r3FDiSAyByCKAqDqCOYIDuqGDZgGLWBCx9i2CRJtxYgskvapD7FSAGJVCHCajwjhw0qDhRwGLWy3zQjyAspSkX7ljZjLT5hrRQjqAsjuqcIKjxbQq1BcR1FfxrJKzdB6lDw2b7BMiVEBJHAM7IjX4KQBJWS1GiZJz7pAYBYulujSQmA2b1pSDom8mmhoSCZfR0hHDGCYkBbiA9AY4RkZjGBmiSCYIPoboMjFimizrynFLIjqj2kJEdz+YdphYQZUYQC5lD4GLQR3iKAFDyAnAM5pZkxcgTEgjrAWAZlkYnr7yQZtnzRZiRyJZ1zAhGB0hllBhGHDkKTnDjlSYybsAzmaHGjzAlKmixkjbqTFiiwWDnZ5T640yg6r4NlHrBahY9rsCMkbbSkHAzpNYmjPG3xTwDknBDlZaHmZQAnhnmZ3pCDGBZQ5B9nAiW7SC1xWB8xeTAhobIh4KOJg4dwraZl3a7n7lbY5AXhwWCyYLlCTb1zoXWCyD5HebYWr54XLadZBD0YcBAI+pBBqoglglgBEUFrVAqTqShzYLVCmhUUGjoUgg1GFjTHcH0zMVHorbsX-64k+r8WQWskvg6G0GPjGgM6yDqCzoHRCzAghjMr3mKWsWZnPk5oCXGBiCZBvgNBmjlBqDHZFpNbPoHSCaWHE4C4iHC5F4CXGJxYGJaSQpTi7BTzPjZja4vJoGYSWEF5O6p6OEi6hWzZix1yVioYiDV5xaUqimCJfmWF96u5ZWkr6JiBaRmDqDmJT7iKjEegqC1DzzZSWHgGVVaWfn8wXhSB0gvghhIXIGGDUiITelQjLRZiWGeE2HdJKFBVp4hW9UX6fYZAkzLRQhSC2AM6mgm6-pKB6xVD6EspJEpFtiaUfkX5HAZDoarpsgViFgpYlAKiRGgx0iUr2RtwNFQ6LFXFzTaxui5QXhVBvzIhIRVBWqZRCpLAFD5TlqnH7jzEA3FaYzA3czomFlxT-qyS1ChI0rpDZiyBaQ0hAV0ROBOBAA */
id: 'Modeling',
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
predictableActionArguments: true,
preserveActionOrder: true,
context: {
guiMode: 'default',
selection: [] as string[],
selectionRanges: {
otherSelections: [],
codeBasedSelections: [],
} as Selections,
selectionRangeTypeMap: {} as SelectionRangeTypeMap,
sketchPathToNode: null as PathToNode | null, // maybe too specific, and we should have a generic pathToNode, but being specific seems less risky when I'm not sure
sketchEnginePathId: '' as string,
sketchPlaneId: '' as string,
},
schema: {
events: {} as ModelingMachineEvent,
},
states: {
idle: {
on: {
'Set selection': {
target: 'idle',
internal: true,
actions: 'Set selection',
},
'Deselect point': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains point',
},
'Deselect edge': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains edge',
},
'Deselect axis': {
target: 'idle',
internal: true,
actions: [
'Remove from other selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains axis',
},
'Select point': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Select edge': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Select axis': {
target: 'idle',
internal: true,
actions: [
'Add to other selection',
// 'Engine: add highlight',
],
},
'Select face': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Enter sketch': [
{
target: 'Sketch',
cond: 'Selection is one face',
actions: [
'set sketch metadata',
'sketch mode enabled',
'edit mode enter',
],
},
'Sketch no face',
],
'Deselect face': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains face',
},
'Select all': {
target: 'idle',
internal: true,
actions: 'Add to code-based selection',
},
'Deselect all': {
target: 'idle',
internal: true,
actions: [
'Clear selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection is not empty',
},
'extrude intent': [
{
target: 'awaiting selection',
cond: 'has no selection',
},
{
target: 'idle',
cond: 'has valid extrude selection',
internal: true,
actions: 'AST extrude',
},
],
},
},
Sketch: {
states: {
SketchIdle: {
on: {
'Select point': {
target: 'SketchIdle',
internal: true,
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Select segment': {
target: 'SketchIdle',
internal: true,
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Deselect point': {
target: 'SketchIdle',
internal: true,
cond: 'Selection contains point',
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Deselect segment': {
target: 'SketchIdle',
internal: true,
cond: 'Selection contains line',
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Equip tool': {
target: 'Line Tool',
actions: 'set tool line',
},
'Equip move tool': 'Move Tool',
'Set selection': {
target: 'SketchIdle',
internal: true,
actions: 'Set selection',
},
'Make segment vertical': {
cond: 'Can make selection vertical',
target: 'SketchIdle',
internal: true,
actions: ['Make selection vertical'],
},
'Make segment horizontal': {
target: 'SketchIdle',
internal: true,
cond: 'Can make selection horizontal',
actions: ['Make selection horizontal'],
},
'Constrain horizontal distance': {
target: 'Await horizontal distance info',
cond: 'Can constrain horizontal distance',
},
'Constrain vertical distance': {
target: 'Await vertical distance info',
cond: 'Can constrain vertical distance',
},
'Constrain angle': {
target: 'Await angle info',
cond: 'Can constrain angle',
},
'Constrain length': {
target: 'Await length info',
cond: 'Can constrain length',
},
'Constrain perpendicular distance': {
target: 'Await perpendicular distance info',
cond: 'Can constrain perpendicular distance',
},
'Constrain horizontally align': {
cond: 'Can constrain horizontally align',
target: 'SketchIdle',
internal: true,
actions: ['Constrain horizontally align'],
},
'Constrain vertically align': {
cond: 'Can constrain vertically align',
target: 'SketchIdle',
internal: true,
actions: ['Constrain vertically align'],
},
'Constrain equal length': {
cond: 'Can constrain equal length',
target: 'SketchIdle',
internal: true,
actions: ['Constrain equal length'],
},
'Constrain parallel': {
target: 'SketchIdle',
internal: true,
cond: 'Can canstrain parallel',
actions: ['Constrain parallel'],
},
'Constrain remove constraints': {
target: 'SketchIdle',
internal: true,
cond: 'Can constrain remove constraints',
actions: ['Constrain remove constraints'],
},
'Re-execute': {
target: 'SketchIdle',
internal: true,
actions: [
'set sketchMetadata from pathToNode',
'sketch mode enabled',
'edit mode enter',
],
},
},
entry: 'equip select',
},
'Line Tool': {
states: {
Done: {
type: 'final',
},
'Point Added': {
on: {
'Add point': {
target: 'Segment Added',
actions: ['AST start new sketch'],
},
},
},
'Segment Added': {
on: {
'Add point': {
target: 'Segment Added',
internal: true,
actions: ['AST add line segment'],
},
'Complete line': {
target: 'Done',
actions: ['Modify AST', 'Update code selection cursors'],
},
'Equip new tool': {
target: 'Segment Added',
internal: true,
actions: 'set tool',
},
},
},
Init: {
always: [
{
target: 'Segment Added',
cond: 'is editing existing sketch',
},
'No Points',
],
},
'No Points': {
on: {
'Add point': 'Point Added',
},
},
},
// invoke: [
// {
// src: 'createLine',
// id: 'Create line',
// onDone: 'SketchIdle',
// },
// ],
initial: 'Init',
on: {
'Equip move tool': 'Move Tool',
'Re-execute': {
target: 'Line Tool',
internal: true,
actions: [
'set sketchMetadata from pathToNode',
'sketch mode enabled',
'edit mode enter',
],
},
},
},
'Move Tool': {
entry: 'set tool move',
on: {
'Set selection': {
target: 'Move Tool',
internal: true,
actions: 'Set selection',
},
'Re-execute': {
target: 'Move Tool',
internal: true,
actions: [
'set sketchMetadata from pathToNode',
'sketch mode enabled',
'edit mode enter',
],
},
},
states: {
'Move init': {
always: [
{
target: 'Move without re-execute',
cond: 'can move',
},
{
target: 'Move with execute',
cond: 'can move with execute',
},
'No move',
],
},
'Move without re-execute': {},
'Move with execute': {},
'No move': {},
},
initial: 'Move init',
},
'Await horizontal distance info': {
invoke: {
src: 'Get horizontal info',
id: 'get-horizontal-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
'Await vertical distance info': {
invoke: {
src: 'Get vertical info',
id: 'get-vertical-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
'Await angle info': {
invoke: {
src: 'Get angle info',
id: 'get-angle-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
'Await length info': {
invoke: {
src: 'Get length info',
id: 'get-length-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
'Await perpendicular distance info': {
invoke: {
src: 'Get perpendicular distance info',
id: 'get-perpendicular-distance-info',
onDone: {
target: 'SketchIdle',
actions: 'Set selection',
},
onError: 'SketchIdle',
},
},
},
initial: 'SketchIdle',
on: {
CancelSketch: '.SketchIdle',
},
exit: 'sketch exit execute',
},
'Sketch no face': {
entry: 'show default planes',
exit: 'hide default planes',
on: {
'Select default plane': {
target: 'Sketch',
actions: [
'reset sketch metadata',
'set default plane id',
'sketch mode enabled',
'create path',
],
},
},
},
'awaiting selection': {
on: {
'Set selection': {
target: 'checking selection',
actions: 'Set selection',
},
},
},
'checking selection': {
always: [
{
target: 'idle',
cond: 'has valid extrude selection',
actions: 'AST extrude',
},
{
target: 'idle',
actions: 'toast extrude failed',
},
],
},
},
initial: 'idle',
on: {
Cancel: {
target: 'idle',
// TODO what if we're existing extrude equipped, should these actions still be fired?
// maybe cancel needs to have a guard for if else logic?
actions: [
'edit_mode_exit',
'default_camera_disable_sketch_mode',
'reset sketch metadata',
],
},
},
},
{
guards: {
'is editing existing sketch': ({ sketchPathToNode }) =>
!!sketchPathToNode,
'Can make selection horizontal': ({ selectionRanges }) =>
horzVertInfo(selectionRanges, 'horizontal').enabled,
'Can make selection vertical': ({ selectionRanges }) =>
horzVertInfo(selectionRanges, 'vertical').enabled,
'Can constrain horizontal distance': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
.enabled,
'Can constrain vertical distance': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setVertDistance' })
.enabled,
'Can constrain angle': ({ selectionRanges }) =>
angleBetweenInfo({ selectionRanges }).enabled,
'Can constrain length': ({ selectionRanges }) =>
setAngleLengthInfo({ selectionRanges }).enabled,
'Can constrain perpendicular distance': ({ selectionRanges }) =>
intersectInfo({ selectionRanges }).enabled,
'Can constrain horizontally align': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
.enabled,
'Can constrain vertically align': ({ selectionRanges }) =>
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
.enabled,
'Can constrain equal length': ({ selectionRanges }) =>
setEqualLengthInfo({ selectionRanges }).enabled,
'Can canstrain parallel': ({ selectionRanges }) =>
equalAngleInfo({ selectionRanges }).enabled,
'Can constrain remove constraints': ({ selectionRanges }) =>
removeConstrainingValuesInfo({ selectionRanges }).enabled,
'has no selection': ({ selectionRanges }) => {
if (selectionRanges?.codeBasedSelections?.length < 1) return true
const selection = selectionRanges?.codeBasedSelections?.[0] || {}
return (
selectionRanges.codeBasedSelections.length === 1 &&
!hasExtrudeSketchGroup({
ast: kclManager.ast,
programMemory: kclManager.programMemory,
selection,
})
)
},
'has valid extrude selection': ({ selectionRanges }) => {
if (selectionRanges.codeBasedSelections.length !== 1) return false
const isSketchPipe = isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
const common = {
selection: selectionRanges.codeBasedSelections[0],
ast: kclManager.ast,
}
const hasClose = doesPipeHaveCallExp({ calleeName: 'close', ...common })
const hasExtrude = doesPipeHaveCallExp({
calleeName: 'extrude',
...common,
})
return !!isSketchPipe && hasClose && !hasExtrude
},
'can move': ({ selectionRanges }) =>
// todo check all cursors are also in the right sketch
selectionRanges.codeBasedSelections.every(
(selection) =>
getConstraintLevelFromSourceRange(
selection.range,
kclManager.ast
) === 'free'
),
'can move with execute': ({ selectionRanges }) =>
// todo check all cursors are also in the right sketch
selectionRanges.codeBasedSelections.every((selection) =>
['partial', 'free'].includes(
getConstraintLevelFromSourceRange(selection.range, kclManager.ast)
)
),
},
actions: {
'Add to code-based selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
event.data,
],
}),
}),
'Add to other selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
otherSelections: [...selectionRanges.otherSelections, event.data],
}),
}),
'Remove from code-based selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
event.data,
],
}),
}),
'Remove from other selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
otherSelections: [...selectionRanges.otherSelections, event.data],
}),
}),
'Clear selection': assign({
selectionRanges: () => ({
otherSelections: [],
codeBasedSelections: [],
}),
}),
'sketch mode enabled': ({ sketchPlaneId }) => {
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: sketchPlaneId,
ortho: true,
animated: !isReducedMotion(),
},
})
},
'set sketchMetadata from pathToNode': assign(({ sketchPathToNode }) => {
if (!sketchPathToNode) return {}
return getSketchMetadataFromPathToNode(sketchPathToNode)
}),
'edit mode enter': ({ selectionRanges }) => {
const pathId = isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
pathId &&
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: pathId,
},
})
},
'hide default planes': () => {
kclManager.hidePlanes()
},
edit_mode_exit: () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
}),
default_camera_disable_sketch_mode: () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
}),
'reset sketch metadata': assign({
sketchPathToNode: null,
sketchEnginePathId: '',
sketchPlaneId: '',
}),
'set sketch metadata': assign(({ selectionRanges }) => {
const sourceRange = selectionRanges.codeBasedSelections[0].range
const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast,
sourceRange
)
return getSketchMetadataFromPathToNode(
sketchPathToNode,
selectionRanges
)
}),
'set tool line': () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'sketch_line',
},
}),
'equip select': () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'select',
},
}),
'set tool move': () =>
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'move',
},
}),
// TODO implement source ranges for all of these constraints
// need to make the async like the modal constraints
'Make selection horizontal': ({ selectionRanges }) => {
const { modifiedAst } = applyConstraintHorzVert(
selectionRanges,
'horizontal',
kclManager.ast,
kclManager.programMemory
)
kclManager.updateAst(modifiedAst, true)
},
'Make selection vertical': ({ selectionRanges }) => {
const { modifiedAst } = applyConstraintHorzVert(
selectionRanges,
'vertical',
kclManager.ast,
kclManager.programMemory
)
kclManager.updateAst(modifiedAst, true)
},
'Constrain horizontally align': ({ selectionRanges }) => {
const { modifiedAst } = applyConstraintHorzVertAlign({
selectionRanges,
constraint: 'setVertDistance',
})
kclManager.updateAst(modifiedAst, true)
},
'Constrain vertically align': ({ selectionRanges }) => {
const { modifiedAst } = applyConstraintHorzVertAlign({
selectionRanges,
constraint: 'setHorzDistance',
})
kclManager.updateAst(modifiedAst, true)
},
'Constrain equal length': ({ selectionRanges }) => {
const { modifiedAst } = applyConstraintEqualLength({
selectionRanges,
})
kclManager.updateAst(modifiedAst, true)
},
'Constrain parallel': ({ selectionRanges }) => {
const { modifiedAst } = applyConstraintEqualAngle({
selectionRanges,
})
kclManager.updateAst(modifiedAst, true)
},
'Constrain remove constraints': ({ selectionRanges }) => {
const { modifiedAst } = applyRemoveConstrainingValues({
selectionRanges,
})
kclManager.updateAst(modifiedAst, true)
},
'AST extrude': ({ selectionRanges }) => {
const pathToNode = getNodePathFromSourceRange(
kclManager.ast,
selectionRanges.codeBasedSelections[0].range
)
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
kclManager.ast,
pathToNode
)
// TODO not handling focusPath correctly I think
kclManager.updateAst(modifiedAst, true, {
focusPath: pathToExtrudeArg,
})
},
'set default plane id': assign({
sketchPlaneId: (_, { data }) => data.planeId,
}),
},
}
)
function getSketchMetadataFromPathToNode(
pathToNode: PathToNode,
selectionRanges?: Selections
) {
const pipeExpression = getNodeFromPath<PipeExpression>(
kclManager.ast,
pathToNode,
'PipeExpression'
).node
if (pipeExpression.type !== 'PipeExpression') return {}
const sketchCallExpression = pipeExpression.body.find(
(e) => e.type === 'CallExpression' && e.callee.name === 'startSketchOn'
) as CallExpression
if (!sketchCallExpression) return {}
const firstArg = sketchCallExpression.arguments[0]
let planeId = ''
if (firstArg.type === 'Literal' && firstArg.value) {
const planeStrCleaned = firstArg.value
.toString()
.toLowerCase()
.replace('-', '')
if (
planeStrCleaned === 'xy' ||
planeStrCleaned === 'xz' ||
planeStrCleaned === 'yz'
) {
planeId = kclManager.getPlaneId(planeStrCleaned)
}
}
let sketchEnginePathId: string
if (selectionRanges) {
sketchEnginePathId =
isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
) || ''
} else {
const _selectionRanges: Selections = {
otherSelections: [],
codeBasedSelections: [
{ range: [pipeExpression.start, pipeExpression.end], type: 'default' },
],
}
sketchEnginePathId =
isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
_selectionRanges
) || ''
}
console.log('returning:', {
sketchPathToNode: pathToNode,
sketchEnginePathId,
sketchPlaneId: planeId,
})
return {
sketchPathToNode: pathToNode,
sketchEnginePathId,
sketchPlaneId: planeId,
}
}