* Rename useCalc * Move CommandBar so it has access to settings and kcl * Create codemirror variable mention extension * Make project path a dep of TextEditor useMemo * Add incomplete KCL input for CommandBar to replace current number arg type * Add previous variables autocompletion to kcl input * Fix missed typos from merge * Working AST mods, not working variable additions * Add ability to create a new variable * Add icon and tooltip to command arg tag if a variable is added * Polish variable naming logic, preserve when going back * Allow stepping back from KCL input * Don't prevent keydown of enter, it's used by autocomplete * Round the variable value in cmd bar header * Add Playwright test * Formatting, polish TS types * More type wrangling * Needed to fmt after above type wrangling * Update snapshot tests to account for new variable name autogeneration * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * Merge branch 'main' into cmd-bar-make-variable * Update all test instances of var name with number index after merge with main * Partial revert of "Polish variable naming logic, preserve when going back" This reverts commitdddcb13c36
. * Revert "Update all test instances of var name with number index after merge with main" This reverts commit8c4b63b523
. * Revert "Update snapshot tests to account for new variable name autogeneration" This reverts commit11bfce3832
. * Retry a refactoring of findUniqueName * minor feedback from @jgomez720 - better highlighting of kcl input - consistent hotkeys - disallow invalid var names * Polish stepping back state logic * Fix tests now that keyboard shortcut changed * Remove unused imports * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * Fix tests * Trigger CI * Update src/components/ProjectSidebarMenu.test.tsx * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) * re-trigger CI --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
893 lines
32 KiB
TypeScript
893 lines
32 KiB
TypeScript
import { PathToNode, VariableDeclarator } from 'lang/wasm'
|
|
import { engineCommandManager } from 'lang/std/engineConnection'
|
|
import {
|
|
Axis,
|
|
Selection,
|
|
SelectionRangeTypeMap,
|
|
Selections,
|
|
} from 'lib/selections'
|
|
import { assign, createMachine } from 'xstate'
|
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
|
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
|
import { kclManager } from 'lang/KclSingleton'
|
|
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 { addStartProfileAt, extrudeSketch } from 'lang/modifyAst'
|
|
import { getNodeFromPath } from '../lang/queryAst'
|
|
import { CallExpression, PipeExpression } from '../lang/wasm'
|
|
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'
|
|
import { Models } from '@kittycad/lib/dist/types/src'
|
|
import { ModelingCommandSchema } from 'lib/commandBarConfigs/modelingCommandConfig'
|
|
import {
|
|
DefaultPlaneStr,
|
|
sceneEntitiesManager,
|
|
quaternionFromSketchGroup,
|
|
sketchGroupFromPathToNode,
|
|
} from 'clientSideScene/sceneEntities'
|
|
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
|
|
|
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: 'Enter sketch'
|
|
data?: {
|
|
forceNewSketch?: boolean
|
|
}
|
|
}
|
|
| {
|
|
type: 'Select default plane'
|
|
data: { plane: DefaultPlaneStr; normal: [number, number, number] }
|
|
}
|
|
| { type: 'Set selection'; data: SetSelections }
|
|
| { type: 'Sketch no face' }
|
|
| { type: 'Toggle gui mode' }
|
|
| { type: 'Cancel' }
|
|
| { type: 'CancelSketch' }
|
|
| { type: 'Add start point' }
|
|
| { 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: 'Re-execute' }
|
|
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
|
|
| { type: 'Equip Line tool' }
|
|
| { type: 'Equip tangential arc to' }
|
|
| {
|
|
type: 'done.invoke.animate-to-face'
|
|
data: {
|
|
sketchPathToNode: PathToNode
|
|
sketchNormalBackUp: [number, number, number] | null
|
|
}
|
|
}
|
|
|
|
export type MoveDesc = { line: number; snippet: string }
|
|
|
|
export const modelingMachine = createMachine(
|
|
{
|
|
/** @xstate-layout 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-McBNlhGQjynZHZSmibF5HsGoKFmKgKi6KcCAA */
|
|
id: 'Modeling',
|
|
|
|
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
|
predictableActionArguments: true,
|
|
preserveActionOrder: true,
|
|
|
|
context: {
|
|
guiMode: 'default',
|
|
tool: null as Models['SceneToolType_type'] | null,
|
|
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,
|
|
sketchNormalBackUp: null as null | [number, number, number],
|
|
moveDescs: [] as MoveDesc[],
|
|
},
|
|
|
|
schema: {
|
|
events: {} as ModelingMachineEvent,
|
|
},
|
|
|
|
states: {
|
|
idle: {
|
|
on: {
|
|
'Set selection': {
|
|
target: 'idle',
|
|
internal: true,
|
|
actions: 'Set selection',
|
|
},
|
|
|
|
'Enter sketch': [
|
|
{
|
|
target: 'animating to existing sketch',
|
|
cond: 'Selection is on face',
|
|
actions: ['set sketch metadata'],
|
|
},
|
|
'Sketch no face',
|
|
],
|
|
|
|
Extrude: {
|
|
target: 'idle',
|
|
cond: 'has valid extrude selection',
|
|
actions: ['AST extrude'],
|
|
internal: true,
|
|
},
|
|
},
|
|
|
|
entry: 'reset client scene mouse handlers',
|
|
},
|
|
|
|
Sketch: {
|
|
states: {
|
|
SketchIdle: {
|
|
on: {
|
|
'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'],
|
|
},
|
|
|
|
'Equip Line tool': 'Line tool',
|
|
|
|
'Equip tangential arc to': {
|
|
target: 'Tangential arc to',
|
|
cond: 'is editing existing sketch',
|
|
},
|
|
},
|
|
|
|
entry: 'setup client side sketch segments',
|
|
},
|
|
|
|
'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',
|
|
},
|
|
},
|
|
|
|
'Line tool': {
|
|
exit: [],
|
|
|
|
on: {
|
|
'Set selection': {
|
|
target: 'Line tool',
|
|
description: `This is just here to stop one of the higher level "Set selections" firing when we are just trying to set the IDE code without triggering a full engine-execute`,
|
|
internal: true,
|
|
},
|
|
|
|
'Equip tangential arc to': {
|
|
target: 'Tangential arc to',
|
|
cond: 'is editing existing sketch',
|
|
},
|
|
},
|
|
|
|
states: {
|
|
Init: {
|
|
always: [
|
|
{
|
|
target: 'normal',
|
|
cond: 'is editing existing sketch',
|
|
actions: 'set up draft line',
|
|
},
|
|
'No Points',
|
|
],
|
|
},
|
|
|
|
normal: {
|
|
on: {
|
|
'Set selection': {
|
|
target: 'normal',
|
|
internal: true,
|
|
},
|
|
},
|
|
},
|
|
|
|
'No Points': {
|
|
entry: 'setup noPoints onClick listener',
|
|
|
|
on: {
|
|
'Add start point': {
|
|
target: 'normal',
|
|
actions: 'set up draft line without teardown',
|
|
},
|
|
|
|
Cancel: '#Modeling.Sketch.undo startSketchOn',
|
|
},
|
|
},
|
|
},
|
|
|
|
initial: 'Init',
|
|
},
|
|
|
|
Init: {
|
|
always: [
|
|
{
|
|
target: 'SketchIdle',
|
|
cond: 'is editing existing sketch',
|
|
},
|
|
'Line tool',
|
|
],
|
|
},
|
|
|
|
'Tangential arc to': {
|
|
entry: 'set up draft arc',
|
|
|
|
on: {
|
|
'Set selection': {
|
|
target: 'Tangential arc to',
|
|
internal: true,
|
|
},
|
|
|
|
'Equip Line tool': 'Line tool',
|
|
},
|
|
},
|
|
|
|
'undo startSketchOn': {
|
|
invoke: {
|
|
src: 'AST-undo-startSketchOn',
|
|
id: 'AST-undo-startSketchOn',
|
|
onDone: '#Modeling.idle',
|
|
},
|
|
},
|
|
},
|
|
|
|
initial: 'Init',
|
|
|
|
on: {
|
|
CancelSketch: '.SketchIdle',
|
|
},
|
|
|
|
exit: [
|
|
'sketch exit execute',
|
|
'animate after sketch',
|
|
'tear down client sketch',
|
|
'remove sketch grid',
|
|
],
|
|
|
|
entry: ['add axis n grid', 'conditionally equip line tool'],
|
|
},
|
|
|
|
'Sketch no face': {
|
|
entry: 'show default planes',
|
|
|
|
exit: 'hide default planes',
|
|
on: {
|
|
'Select default plane': {
|
|
target: 'animating to plane',
|
|
actions: ['reset sketch metadata'],
|
|
},
|
|
},
|
|
},
|
|
|
|
'animating to plane': {
|
|
invoke: {
|
|
src: 'animate-to-face',
|
|
id: 'animate-to-face',
|
|
onDone: {
|
|
target: 'Sketch',
|
|
actions: 'set new sketch metadata',
|
|
},
|
|
},
|
|
|
|
on: {
|
|
'Set selection': {
|
|
target: 'animating to plane',
|
|
internal: true,
|
|
},
|
|
},
|
|
},
|
|
|
|
'animating to existing sketch': {
|
|
invoke: [
|
|
{
|
|
src: 'animate-to-sketch',
|
|
id: 'animate-to-sketch',
|
|
onDone: 'Sketch',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
|
|
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: ['reset sketch metadata'],
|
|
},
|
|
|
|
'Set selection': {
|
|
target: '#Modeling',
|
|
internal: true,
|
|
actions: 'Set selection',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
guards: {
|
|
'is editing existing sketch': ({ sketchPathToNode }) => {
|
|
// should check that the variable declaration is a pipeExpression
|
|
// and that the pipeExpression contains a "startProfileAt" callExpression
|
|
if (!sketchPathToNode) return false
|
|
const variableDeclaration = getNodeFromPath<VariableDeclarator>(
|
|
kclManager.ast,
|
|
sketchPathToNode,
|
|
'VariableDeclarator'
|
|
).node
|
|
if (variableDeclaration.type !== 'VariableDeclarator') return false
|
|
const pipeExpression = variableDeclaration.init
|
|
if (pipeExpression.type !== 'PipeExpression') return false
|
|
const hasStartProfileAt = pipeExpression.body.some(
|
|
(item) =>
|
|
item.type === 'CallExpression' &&
|
|
item.callee.name === 'startProfileAt'
|
|
)
|
|
return hasStartProfileAt && pipeExpression.body.length > 2
|
|
},
|
|
'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,
|
|
},
|
|
// end guards
|
|
actions: {
|
|
'set sketchMetadata from pathToNode': assign(({ sketchPathToNode }) => {
|
|
if (!sketchPathToNode) return {}
|
|
return getSketchMetadataFromPathToNode(sketchPathToNode)
|
|
}),
|
|
'hide default planes': () => {
|
|
sceneInfra.removeDefaultPlanes()
|
|
kclManager.hidePlanes()
|
|
},
|
|
'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 new sketch metadata': assign((_, { data }) => data),
|
|
// TODO implement source ranges for all of these constraints
|
|
// need to make the async like the modal constraints
|
|
'Make selection horizontal': ({ selectionRanges, sketchPathToNode }) => {
|
|
const { modifiedAst } = applyConstraintHorzVert(
|
|
selectionRanges,
|
|
'horizontal',
|
|
kclManager.ast,
|
|
kclManager.programMemory
|
|
)
|
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
|
sketchPathToNode || [],
|
|
modifiedAst
|
|
)
|
|
},
|
|
'Make selection vertical': ({ selectionRanges, sketchPathToNode }) => {
|
|
const { modifiedAst } = applyConstraintHorzVert(
|
|
selectionRanges,
|
|
'vertical',
|
|
kclManager.ast,
|
|
kclManager.programMemory
|
|
)
|
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
|
sketchPathToNode || [],
|
|
modifiedAst
|
|
)
|
|
},
|
|
'Constrain horizontally align': ({
|
|
selectionRanges,
|
|
sketchPathToNode,
|
|
}) => {
|
|
const { modifiedAst } = applyConstraintHorzVertAlign({
|
|
selectionRanges,
|
|
constraint: 'setVertDistance',
|
|
})
|
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
|
sketchPathToNode || [],
|
|
modifiedAst
|
|
)
|
|
},
|
|
'Constrain vertically align': ({ selectionRanges, sketchPathToNode }) => {
|
|
const { modifiedAst } = applyConstraintHorzVertAlign({
|
|
selectionRanges,
|
|
constraint: 'setHorzDistance',
|
|
})
|
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
|
sketchPathToNode || [],
|
|
modifiedAst
|
|
)
|
|
},
|
|
'Constrain snap to X': ({ selectionRanges, sketchPathToNode }) => {
|
|
const { modifiedAst } = applyConstraintAxisAlign({
|
|
selectionRanges,
|
|
constraint: 'snapToXAxis',
|
|
})
|
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
|
sketchPathToNode || [],
|
|
modifiedAst
|
|
)
|
|
},
|
|
'Constrain snap to Y': ({ selectionRanges, sketchPathToNode }) => {
|
|
const { modifiedAst } = applyConstraintAxisAlign({
|
|
selectionRanges,
|
|
constraint: 'snapToYAxis',
|
|
})
|
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
|
sketchPathToNode || [],
|
|
modifiedAst
|
|
)
|
|
},
|
|
'Constrain equal length': ({ selectionRanges, sketchPathToNode }) => {
|
|
const { modifiedAst } = applyConstraintEqualLength({
|
|
selectionRanges,
|
|
})
|
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
|
sketchPathToNode || [],
|
|
modifiedAst
|
|
)
|
|
},
|
|
'Constrain parallel': ({ selectionRanges, sketchPathToNode }) => {
|
|
const { modifiedAst } = applyConstraintEqualAngle({
|
|
selectionRanges,
|
|
})
|
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
|
sketchPathToNode || [],
|
|
modifiedAst
|
|
)
|
|
},
|
|
'Constrain remove constraints': ({
|
|
selectionRanges,
|
|
sketchPathToNode,
|
|
}) => {
|
|
const { modifiedAst } = applyRemoveConstrainingValues({
|
|
selectionRanges,
|
|
})
|
|
sceneEntitiesManager.updateAstAndRejigSketch(
|
|
sketchPathToNode || [],
|
|
modifiedAst
|
|
)
|
|
},
|
|
'AST extrude': (_, event) => {
|
|
if (!event.data) return
|
|
const { selection, distance } = event.data
|
|
let ast = kclManager.ast
|
|
if (
|
|
'variableName' in distance &&
|
|
distance.variableName &&
|
|
distance.insertIndex !== undefined
|
|
) {
|
|
console.log('adding variable!', distance)
|
|
const newBody = [...ast.body]
|
|
newBody.splice(
|
|
distance.insertIndex,
|
|
0,
|
|
distance.variableDeclarationAst
|
|
)
|
|
ast.body = newBody
|
|
}
|
|
const pathToNode = getNodePathFromSourceRange(
|
|
ast,
|
|
selection.codeBasedSelections[0].range
|
|
)
|
|
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
|
|
ast,
|
|
pathToNode,
|
|
true,
|
|
'variableName' in distance
|
|
? distance.variableIdentifierAst
|
|
: distance.valueAst
|
|
)
|
|
// TODO not handling focusPath correctly I think
|
|
kclManager.updateAst(modifiedAst, true, {
|
|
focusPath: pathToExtrudeArg,
|
|
})
|
|
},
|
|
'conditionally equip line tool': (_, { type }) => {
|
|
if (type === 'done.invoke.animate-to-face') {
|
|
sceneInfra.modelingSend('Equip Line tool')
|
|
}
|
|
},
|
|
'setup client side sketch segments': ({ sketchPathToNode }, { type }) => {
|
|
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
|
|
sceneEntitiesManager
|
|
.tearDownSketch({ removeAxis: false })
|
|
.then(() => {
|
|
sceneEntitiesManager.setupSketch({
|
|
sketchPathToNode: sketchPathToNode || [],
|
|
})
|
|
})
|
|
} else {
|
|
sceneEntitiesManager.setupSketch({
|
|
sketchPathToNode: sketchPathToNode || [],
|
|
})
|
|
}
|
|
},
|
|
'animate after sketch': () => {
|
|
sceneEntitiesManager.animateAfterSketch()
|
|
},
|
|
'tear down client sketch': () => {
|
|
if (sceneEntitiesManager.activeSegments) {
|
|
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
|
}
|
|
},
|
|
'remove sketch grid': () => sceneEntitiesManager.removeSketchGrid(),
|
|
'set up draft line': ({ sketchPathToNode }) => {
|
|
sceneEntitiesManager.setUpDraftLine(sketchPathToNode || [])
|
|
},
|
|
'set up draft arc': ({ sketchPathToNode }) => {
|
|
sceneEntitiesManager.setUpDraftArc(sketchPathToNode || [])
|
|
},
|
|
'set up draft line without teardown': ({ sketchPathToNode }) =>
|
|
sceneEntitiesManager.setupSketch({
|
|
sketchPathToNode: sketchPathToNode || [],
|
|
draftSegment: 'line',
|
|
}),
|
|
'show default planes': () => {
|
|
sceneInfra.showDefaultPlanes()
|
|
sceneEntitiesManager.setupDefaultPlaneHover()
|
|
kclManager.showPlanes()
|
|
},
|
|
'setup noPoints onClick listener': ({ sketchPathToNode }) => {
|
|
sceneEntitiesManager.createIntersectionPlane()
|
|
const sketchGroup = sketchGroupFromPathToNode({
|
|
pathToNode: sketchPathToNode || [],
|
|
ast: kclManager.ast,
|
|
programMemory: kclManager.programMemory,
|
|
})
|
|
const quaternion = quaternionFromSketchGroup(sketchGroup)
|
|
sceneEntitiesManager.intersectionPlane &&
|
|
sceneEntitiesManager.intersectionPlane.setRotationFromQuaternion(
|
|
quaternion
|
|
)
|
|
sceneInfra.setCallbacks({
|
|
onClick: async (args) => {
|
|
if (!args) return
|
|
if (args.event.which !== 1) return
|
|
const { intersection2d } = args
|
|
if (!intersection2d || !sketchPathToNode) return
|
|
const { modifiedAst } = addStartProfileAt(
|
|
kclManager.ast,
|
|
sketchPathToNode,
|
|
[intersection2d.x, intersection2d.y]
|
|
)
|
|
await kclManager.updateAst(modifiedAst, false)
|
|
sceneEntitiesManager.removeIntersectionPlane()
|
|
sceneInfra.modelingSend('Add start point')
|
|
},
|
|
})
|
|
},
|
|
'add axis n grid': ({ sketchPathToNode }) =>
|
|
sceneEntitiesManager.createSketchAxis(sketchPathToNode || []),
|
|
'reset client scene mouse handlers': () => {
|
|
// when not in sketch mode we don't need any mouse listeners
|
|
// (note the orbit controls are always active though)
|
|
sceneInfra.resetMouseListeners()
|
|
},
|
|
},
|
|
// end actions
|
|
}
|
|
)
|
|
|
|
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 {}
|
|
|
|
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
|
|
) || ''
|
|
}
|
|
return {
|
|
sketchPathToNode: pathToNode,
|
|
sketchEnginePathId,
|
|
}
|
|
}
|