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 { angleLengthInfo } 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' import { absDistanceInfo, applyConstraintAxisAlign, } from 'components/Toolbar/SetAbsDistance' 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 ABS X' } | { type: 'Constrain ABS Y' } | { type: 'Constrain vertical distance' } | { type: 'Constrain angle' } | { type: 'Constrain perpendicular distance' } | { type: 'Constrain horizontally align' } | { type: 'Constrain vertically align' } | { type: 'Constrain snap to X' } | { type: 'Constrain snap to Y' } | { 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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-TiqeAnts4giIfR1IwXlApVCf7kI-hJMLAk6EhQlty3pib+74gfdm-YDtl75lscIBUPPBKw9zD1GsMidK2QPq5EKIUaEdcrCG+a37iXlUnuqHl+xsP6QMgNDUsYdSzd7Dx+t5Pa7IZdgWih-TDxvWKHBAxbQyBmAGFMM0ZVZsqACAQG4GATwuBc6oF3OIGA7BH3t6xZgR9eB++7e4pkKWiSvLThFs5McfYwYaHyBOhxCaO7N9b1Q4BmK6Fd++b3pfA-NwbmAeIdLJB2D943EjKfgRZ+X470JRfXAZfC3d7VfJoeFLMSLTUR3BAKsU4C8KQOiBEPWU4JVM-DgZRI+bAE+G-HvFhPvAfIfWjUfcfSfafR9LlbAgA+-FfeQSodUXYCwcNDQB8ZaeYaWMwcoRQSmNAlvDAygnA7vDRfA+-AgR-Z-V-d-YBL-cggQ6goA1AWgmEJkMuOkNSARVg5af4W+bhS8c0D+SdaHdA1zHxfxAgwfYfEgifEfcgkgAAI1gEfT4EAOAPzx1kQj2DBSzGq0KHKGiXUiBTqFFQPQMOPxW3OmMM8VML8XMPEI3Bfw+SkM-2-xn3sMcOcJoJAJSA8IyDmz1Fw2J37SUiqH1iuzxjajMAGkoyML4JMNyXMKIJHzwFIJsJ-zSMfR0BcMUKyNGVsk+mGVknyAUmPSUjog+kjDhRK2qXqTCJqPP2yS9ViI3Cf3iMkI-xkLaIcI6K6NoPgWDWBTG3MHkAfGRHmHVEQjhByFsArF4PPw6R8AaMsOaOsJSMfXuKfUyLcLdByNhBMWXmFVogfBmyV2+kMCnGsFuIwPeKWJWISLf3WNePeJ2J6NgJ5H1AOFWERHjnohgIsDBEyAODBBWjUlCOqKb1qKy0imzX3EeOIOeLIJ-yzXWWRK+IzA8JCWRBiSZRtQfAbgvDHHWF2CaDOEhOCCZJpNELiLhKSI2Jn3FJZKxwV1RP1WOELEZTsVtl5K0LEGXnD1T0sFFKGCeUileXeU+SEJ+VNwUIsLpLHxePIMeQ3GeVNI+Q3EfVvxYQVNezyxxx5EkDG2BHWDNBOEhUyn2B5EOELAOQ2BmLJO-kiMdOdOwLNK+TwMoBhIkMSIRIdONJeWTNdPdLTI+IUN21kn9N6T+2DNxKkA+mjkjL7DJ1p0b2TiCFwE9RIltCIgwFbHeXug+Vo1zXUkTwrDemvB5GBGORKGrk+ihHCS0kQiOLbhIFqI6B3lxCmCZzXPtFZ1ZN0VDmpH0THAjJOFkGpWjWpD3x9H80vDtWW3EAPHCG3FXL7kSB2xROhHzRyEDDEAOAQhGVKFyGzA0nNFKTEyhwfPICfPwC3P7m4CD13KxkjCDFhVsAPwjKKP0DSGgi4LSGylNA9AQicH5DbIwHgFiEo0VILyHEkBkDkAPTUBYJ1EfUMGzCpwT0RGMzVDbixEop1mBF3WByqEOBiVBSlXHATzsGsFvnd28jjMAl4tekOGzGWnzDWihHUBZEfU4QVGsD2l-UUE9yTkdRnR8AUu5i+wNVVN2HkF8P-ORHRIUhsqsiOMMq-mowkzhzMogmkhMC+2BWqDBXUiBLQjyKNAKHhVJLvPplh0cx6GHy8vmmMDNEkEwR80swZGLFND030SA2BVsVkqiu-hiuk2RySw6wgASuyOp2yvhVyhspOH-LJzJkp1vhBHWAsDuzh1axc0U2U0qqEF-IPOBQjGOCHEyqDAVGBVaoUnOE6sc0e3YH6tgNI0kD8qGMCpgIVX2HHVBVWCHEh0MOio8sc0RyCFKvXSWoxNJlOHKC+znjhHGuat52pwyljMKoVh908u9ND3kDxl2WjSYnhT8K2isD5hnEjAOQOEiq92-k+uN2zwDyWpUByH+oarqTqGkDQROANGBGtlkBpSPzkqcThukx916w4CATfSCAXSYxYzACRuqBUnLzt0q0rEnKd3rlxpBDnnFVvJho+uzy6rJoprTWY1l3pu+qVOMF5lWlvi+1OBsHj3UD0wOiFmBEDQKv5uJsFpOs9VQwZsETpCmqrWhBJwfjek9HCTHAOjm0NLn2vwtLvwUMuuBzX12Bu3kCnBO34RLX2HSDJyaFtubIVkiLkNwOEPTPv0uoSWpHMTOFyCNn-L1FkAvCsS5EqPUDAsOvjIpIWJiKjsloLz2wmpUHnKQPKX4UXj01sChHFmJUMENIWJyQIOjuhEyDvikHMBK0Vv4TogkF2B40MDYK0jeq1tPwpOhILpDyVL237up0ZGxl+gfBG3hVQsnHkDMENPFJbsLvcP5iEW4LHnyA2CBPVDFkrAcgiTBAOtmPJPP0TJNPzPNI9Mjudt3u+OMAyGMSUHsByBUGBEhXgJhQ9ESS9oNODrOlbPbLbAlunoLyOAyAiss1jwXjNvlEWGyrRMnG0gTkMOXNb2fJZ0xm1jdFyiEVsHnmRCQiqHSkYi7SWAKHyhDFcutAgqgqgBgt9RIe5n1xSusw2h5FqAwoAroiVC9ppAszoiIocCAA */ 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 ABS X': { target: 'Await ABS X info', cond: 'Can constrain ABS X', }, 'Constrain ABS Y': { target: 'Await ABS Y info', cond: 'Can constrain ABS Y', }, '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 snap to X': { cond: 'Can constrain snap to X', target: 'SketchIdle', internal: true, actions: ['Constrain snap to X'], }, 'Constrain snap to Y': { cond: 'Can constrain snap to Y', target: 'SketchIdle', internal: true, actions: ['Constrain snap to Y'], }, '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 ABS X info': { invoke: { src: 'Get ABS X info', id: 'get-abs-x-info', onDone: { target: 'SketchIdle', actions: 'Set selection', }, onError: 'SketchIdle', }, }, 'Await ABS Y info': { invoke: { src: 'Get ABS Y info', id: 'get-abs-y-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 ABS X': ({ selectionRanges }) => absDistanceInfo({ selectionRanges, constraint: 'xAbs' }).enabled, 'Can constrain ABS Y': ({ selectionRanges }) => absDistanceInfo({ selectionRanges, constraint: 'yAbs' }).enabled, 'Can constrain angle': ({ selectionRanges }) => angleBetweenInfo({ selectionRanges }).enabled || angleLengthInfo({ selectionRanges, angleOrLength: 'setAngle' }).enabled, 'Can constrain length': ({ selectionRanges }) => angleLengthInfo({ 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 snap to X': ({ selectionRanges }) => absDistanceInfo({ selectionRanges, constraint: 'snapToXAxis' }).enabled, 'Can constrain snap to Y': ({ selectionRanges }) => absDistanceInfo({ selectionRanges, constraint: 'snapToYAxis' }).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 snap to X': ({ selectionRanges }) => { const { modifiedAst } = applyConstraintAxisAlign({ selectionRanges, constraint: 'snapToXAxis', }) kclManager.updateAst(modifiedAst, true) }, 'Constrain snap to Y': ({ selectionRanges }) => { const { modifiedAst } = applyConstraintAxisAlign({ selectionRanges, constraint: 'snapToYAxis', }) 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( 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, } }