ArtifactMap should be processed at the end of an execution (PART 2) (#3121)
* seperate out artifactmap functions into seperate file, change types quiet a bit with e2e still passing * more type changes * another increment * cull artifact map * remove excessive parentIds * rename props * final clean up * unused vars
This commit is contained in:
@ -26,7 +26,7 @@ import * as TOML from '@iarna/toml'
|
|||||||
import { LineInputsType } from 'lang/std/sketchcombos'
|
import { LineInputsType } from 'lang/std/sketchcombos'
|
||||||
import { Coords2d } from 'lang/std/sketch'
|
import { Coords2d } from 'lang/std/sketch'
|
||||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||||
import { EngineCommand } from 'lang/std/engineConnection'
|
import { EngineCommand } from 'lang/std/artifactMap'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { test, expect, Page, Download } from '@playwright/test'
|
import { expect, Page, Download } from '@playwright/test'
|
||||||
import { EngineCommand } from '../../src/lang/std/engineConnection'
|
import { EngineCommand } from 'lang/std/artifactMap'
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import pixelMatch from 'pixelmatch'
|
import pixelMatch from 'pixelmatch'
|
||||||
|
@ -2,7 +2,7 @@ import { MouseEventHandler, useEffect, useMemo, useRef } from 'react'
|
|||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||||
import { Stream } from './components/Stream'
|
import { Stream } from './components/Stream'
|
||||||
import { EngineCommand } from './lang/std/engineConnection'
|
import { EngineCommand } from 'lang/std/artifactMap'
|
||||||
import { throttle } from './lib/utils'
|
import { throttle } from './lib/utils'
|
||||||
import { AppHeader } from './components/AppHeader'
|
import { AppHeader } from './components/AppHeader'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
@ -17,11 +17,11 @@ import {
|
|||||||
ZOOM_MAGIC_NUMBER,
|
ZOOM_MAGIC_NUMBER,
|
||||||
} from './sceneInfra'
|
} from './sceneInfra'
|
||||||
import {
|
import {
|
||||||
EngineCommand,
|
|
||||||
Subscription,
|
Subscription,
|
||||||
EngineCommandManager,
|
EngineCommandManager,
|
||||||
UnreliableSubscription,
|
UnreliableSubscription,
|
||||||
} from 'lang/std/engineConnection'
|
} from 'lang/std/engineConnection'
|
||||||
|
import { EngineCommand } from 'lang/std/artifactMap'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { deg2Rad } from 'lib/utils2d'
|
import { deg2Rad } from 'lib/utils2d'
|
||||||
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
||||||
|
@ -95,10 +95,7 @@ import { createGridHelper, orthoScale, perspScale } from './helpers'
|
|||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { SegmentOverlayPayload, SketchDetails } from 'machines/modelingMachine'
|
import { SegmentOverlayPayload, SketchDetails } from 'machines/modelingMachine'
|
||||||
import {
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||||
ArtifactMapCommand,
|
|
||||||
EngineCommandManager,
|
|
||||||
} from 'lang/std/engineConnection'
|
|
||||||
import {
|
import {
|
||||||
getRectangleCallExpressions,
|
getRectangleCallExpressions,
|
||||||
updateRectangleSketch,
|
updateRectangleSketch,
|
||||||
@ -1562,25 +1559,24 @@ export class SceneEntities {
|
|||||||
// to get the sketch profile's face ID. If we clicked on an endcap,
|
// to get the sketch profile's face ID. If we clicked on an endcap,
|
||||||
// we already have it.
|
// we already have it.
|
||||||
const targetId =
|
const targetId =
|
||||||
'additionalData' in artifact &&
|
artifact?.type === 'extrudeWall' || artifact?.type === 'extrudeCap'
|
||||||
artifact.additionalData?.type === 'cap'
|
? artifact.segmentId
|
||||||
? _entity_id
|
: ''
|
||||||
: artifact.parentId
|
|
||||||
|
|
||||||
// tsc cannot infer that target can have extrusions
|
// tsc cannot infer that target can have extrusions
|
||||||
// from the commandType (why?) so we need to cast it
|
// from the commandType (why?) so we need to cast it
|
||||||
const target = this.engineCommandManager.artifactMap?.[
|
const target = this.engineCommandManager.artifactMap?.[targetId || '']
|
||||||
targetId || ''
|
const extrusionId =
|
||||||
] as ArtifactMapCommand & { extrusions?: string[] }
|
target?.type === 'startPath' ? target.extrusions[0] : ''
|
||||||
|
|
||||||
// TODO: We get the first extrusion command ID,
|
// TODO: We get the first extrusion command ID,
|
||||||
// which is fine while backend systems only support one extrusion.
|
// which is fine while backend systems only support one extrusion.
|
||||||
// but we need to more robustly handle resolving to the correct extrusion
|
// but we need to more robustly handle resolving to the correct extrusion
|
||||||
// if there are multiple.
|
// if there are multiple.
|
||||||
const extrusions =
|
const extrusions = this.engineCommandManager.artifactMap?.[extrusionId]
|
||||||
this.engineCommandManager.artifactMap?.[target?.extrusions?.[0] || '']
|
|
||||||
|
|
||||||
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info') return
|
if (artifact?.type !== 'extrudeCap' && artifact?.type !== 'extrudeWall')
|
||||||
|
return
|
||||||
|
|
||||||
const faceInfo = await getFaceDetails(_entity_id)
|
const faceInfo = await getFaceDetails(_entity_id)
|
||||||
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis) return
|
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis) return
|
||||||
@ -1606,7 +1602,7 @@ export class SceneEntities {
|
|||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
extrudePathToNode,
|
extrudePathToNode,
|
||||||
cap:
|
cap:
|
||||||
artifact?.additionalData?.type === 'cap'
|
artifact.type === 'extrudeCap'
|
||||||
? artifact.additionalData.info
|
? artifact.additionalData.info
|
||||||
: 'none',
|
: 'none',
|
||||||
faceId: _entity_id,
|
faceId: _entity_id,
|
||||||
|
@ -5,7 +5,6 @@ import { uuidv4 } from 'lib/utils'
|
|||||||
import { EngineCommandManager } from './std/engineConnection'
|
import { EngineCommandManager } from './std/engineConnection'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
|
||||||
import { deferExecution } from 'lib/utils'
|
|
||||||
import {
|
import {
|
||||||
CallExpression,
|
CallExpression,
|
||||||
initPromise,
|
initPromise,
|
||||||
|
280
src/lang/std/artifactMap.ts
Normal file
280
src/lang/std/artifactMap.ts
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
import { PathToNode, Program, SourceRange } from 'lang/wasm'
|
||||||
|
import { Models } from '@kittycad/lib'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
|
|
||||||
|
interface CommonCommandProperties {
|
||||||
|
range: SourceRange
|
||||||
|
pathToNode: PathToNode
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExtrudeArtifact extends CommonCommandProperties {
|
||||||
|
type: 'extrude'
|
||||||
|
target: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StartPathArtifact extends CommonCommandProperties {
|
||||||
|
type: 'startPath'
|
||||||
|
extrusions: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SegmentArtifact extends CommonCommandProperties {
|
||||||
|
type: 'segment'
|
||||||
|
subType: 'segment' | 'closeSegment'
|
||||||
|
pathId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExtrudeCapArtifact extends CommonCommandProperties {
|
||||||
|
type: 'extrudeCap'
|
||||||
|
additionalData: {
|
||||||
|
type: 'cap'
|
||||||
|
info: 'start' | 'end'
|
||||||
|
}
|
||||||
|
segmentId: string
|
||||||
|
}
|
||||||
|
interface ExtrudeWallArtifact extends CommonCommandProperties {
|
||||||
|
type: 'extrudeWall'
|
||||||
|
segmentId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PatternInstance extends CommonCommandProperties {
|
||||||
|
type: 'patternInstance'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ArtifactMapCommand =
|
||||||
|
| ExtrudeArtifact
|
||||||
|
| StartPathArtifact
|
||||||
|
| ExtrudeCapArtifact
|
||||||
|
| ExtrudeWallArtifact
|
||||||
|
| SegmentArtifact
|
||||||
|
| PatternInstance
|
||||||
|
|
||||||
|
export type EngineCommand = Models['WebSocketRequest_type']
|
||||||
|
|
||||||
|
type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ArtifactMap is a client-side representation of the artifacts that
|
||||||
|
* have been sent to the server-side engine. It is used to keep track of
|
||||||
|
* the state of each command, and to resolve the promise that was returned.
|
||||||
|
* It is also used to keep track of what entities are in the engine scene,
|
||||||
|
* so that we can associate IDs returned from the engine with the
|
||||||
|
* lines of KCL code that generated them.
|
||||||
|
*/
|
||||||
|
export interface ArtifactMap {
|
||||||
|
[commandId: string]: ArtifactMapCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ResponseMap {
|
||||||
|
[commandId: string]: OkWebSocketResponseData
|
||||||
|
}
|
||||||
|
export interface OrderedCommand {
|
||||||
|
command: EngineCommand
|
||||||
|
range: SourceRange
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createArtifactMap({
|
||||||
|
orderedCommands,
|
||||||
|
responseMap,
|
||||||
|
ast,
|
||||||
|
}: {
|
||||||
|
orderedCommands: Array<OrderedCommand>
|
||||||
|
responseMap: ResponseMap
|
||||||
|
ast: Program
|
||||||
|
}): ArtifactMap {
|
||||||
|
const artifactMap: ArtifactMap = {}
|
||||||
|
orderedCommands.forEach(({ command, range }) => {
|
||||||
|
// expect all to be `modeling_cmd_req` as batch commands have
|
||||||
|
// already been expanded before being added to orderedCommands
|
||||||
|
if (command.type !== 'modeling_cmd_req') return
|
||||||
|
const id = command.cmd_id
|
||||||
|
const response = responseMap[id]
|
||||||
|
const artifacts = handleIndividualResponse({
|
||||||
|
id,
|
||||||
|
pendingMsg: {
|
||||||
|
command,
|
||||||
|
range,
|
||||||
|
},
|
||||||
|
response,
|
||||||
|
ast,
|
||||||
|
prevArtifactMap: artifactMap,
|
||||||
|
})
|
||||||
|
artifacts.forEach(({ commandId, artifact }) => {
|
||||||
|
artifactMap[commandId] = artifact
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return artifactMap
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleIndividualResponse({
|
||||||
|
id,
|
||||||
|
pendingMsg,
|
||||||
|
response,
|
||||||
|
ast,
|
||||||
|
prevArtifactMap,
|
||||||
|
}: {
|
||||||
|
id: string
|
||||||
|
pendingMsg: {
|
||||||
|
command: EngineCommand
|
||||||
|
range: SourceRange
|
||||||
|
}
|
||||||
|
response: OkWebSocketResponseData
|
||||||
|
ast: Program
|
||||||
|
prevArtifactMap: ArtifactMap
|
||||||
|
}): Array<{
|
||||||
|
commandId: string
|
||||||
|
artifact: ArtifactMapCommand
|
||||||
|
}> {
|
||||||
|
const command = pendingMsg
|
||||||
|
if (command?.command?.type !== 'modeling_cmd_req') return []
|
||||||
|
if (response?.type !== 'modeling') return []
|
||||||
|
const command2 = command.command.cmd
|
||||||
|
|
||||||
|
const range = command.range
|
||||||
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
|
const modelingResponse = response.data.modeling_response
|
||||||
|
|
||||||
|
const artifacts: Array<{
|
||||||
|
commandId: string
|
||||||
|
artifact: ArtifactMapCommand
|
||||||
|
}> = []
|
||||||
|
|
||||||
|
if (command) {
|
||||||
|
if (
|
||||||
|
command2.type !== 'extrude' &&
|
||||||
|
command2.type !== 'extend_path' &&
|
||||||
|
command2.type !== 'solid3d_get_extrusion_face_info' &&
|
||||||
|
command2.type !== 'start_path' &&
|
||||||
|
command2.type !== 'close_path'
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
if (command2.type === 'extrude') {
|
||||||
|
artifacts.push({
|
||||||
|
commandId: id,
|
||||||
|
artifact: {
|
||||||
|
type: 'extrude',
|
||||||
|
range,
|
||||||
|
pathToNode,
|
||||||
|
target: command2.target,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const targetArtifact = { ...prevArtifactMap[command2.target] }
|
||||||
|
if (targetArtifact?.type === 'startPath') {
|
||||||
|
artifacts.push({
|
||||||
|
commandId: command2.target,
|
||||||
|
artifact: {
|
||||||
|
...targetArtifact,
|
||||||
|
type: 'startPath',
|
||||||
|
range: targetArtifact.range,
|
||||||
|
pathToNode: targetArtifact.pathToNode,
|
||||||
|
extrusions: targetArtifact?.extrusions
|
||||||
|
? [...targetArtifact?.extrusions, id]
|
||||||
|
: [id],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (command2.type === 'extend_path') {
|
||||||
|
artifacts.push({
|
||||||
|
commandId: id,
|
||||||
|
artifact: {
|
||||||
|
type: 'segment',
|
||||||
|
subType: 'segment',
|
||||||
|
range,
|
||||||
|
pathToNode,
|
||||||
|
pathId: command2.path,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (command2.type === 'close_path')
|
||||||
|
artifacts.push({
|
||||||
|
commandId: id,
|
||||||
|
artifact: {
|
||||||
|
type: 'segment',
|
||||||
|
subType: 'closeSegment',
|
||||||
|
range,
|
||||||
|
pathToNode,
|
||||||
|
pathId: command2.path_id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (command2.type === 'start_path') {
|
||||||
|
artifacts.push({
|
||||||
|
commandId: id,
|
||||||
|
artifact: {
|
||||||
|
type: 'startPath',
|
||||||
|
range,
|
||||||
|
pathToNode,
|
||||||
|
extrusions: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(command2.type === 'entity_linear_pattern' &&
|
||||||
|
modelingResponse.type === 'entity_linear_pattern') ||
|
||||||
|
(command2.type === 'entity_circular_pattern' &&
|
||||||
|
modelingResponse.type === 'entity_circular_pattern')
|
||||||
|
) {
|
||||||
|
// TODO this is not working perfectly, maybe it's like a selection filter issue
|
||||||
|
// but when clicking on a instance it does put the cursor somewhat relevant but
|
||||||
|
// edges and what not do not highlight the correct segment.
|
||||||
|
const entities = modelingResponse.data.entity_ids
|
||||||
|
entities?.forEach((entity: string) => {
|
||||||
|
artifacts.push({
|
||||||
|
commandId: entity,
|
||||||
|
artifact: {
|
||||||
|
range: range,
|
||||||
|
pathToNode,
|
||||||
|
type: 'patternInstance',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
command2.type === 'solid3d_get_extrusion_face_info' &&
|
||||||
|
modelingResponse.type === 'solid3d_get_extrusion_face_info'
|
||||||
|
) {
|
||||||
|
const edgeArtifact = prevArtifactMap[command2.edge_id]
|
||||||
|
const parent =
|
||||||
|
edgeArtifact?.type === 'segment'
|
||||||
|
? prevArtifactMap[edgeArtifact.pathId]
|
||||||
|
: null
|
||||||
|
modelingResponse.data.faces.forEach((face) => {
|
||||||
|
if (
|
||||||
|
face.cap !== 'none' &&
|
||||||
|
face.face_id &&
|
||||||
|
parent?.type === 'startPath'
|
||||||
|
) {
|
||||||
|
artifacts.push({
|
||||||
|
commandId: face.face_id,
|
||||||
|
artifact: {
|
||||||
|
// ...parent,
|
||||||
|
type: 'extrudeCap',
|
||||||
|
additionalData: {
|
||||||
|
type: 'cap',
|
||||||
|
info: face.cap === 'bottom' ? 'start' : 'end',
|
||||||
|
},
|
||||||
|
range: parent.range,
|
||||||
|
pathToNode: parent.pathToNode,
|
||||||
|
segmentId:
|
||||||
|
edgeArtifact?.type === 'segment' ? edgeArtifact.pathId : '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const curveArtifact = prevArtifactMap[face?.curve_id || '']
|
||||||
|
if (curveArtifact?.type === 'segment' && face?.face_id) {
|
||||||
|
artifacts.push({
|
||||||
|
commandId: face.face_id,
|
||||||
|
artifact: {
|
||||||
|
...curveArtifact,
|
||||||
|
type: 'extrudeWall',
|
||||||
|
range: curveArtifact.range,
|
||||||
|
pathToNode: curveArtifact.pathToNode,
|
||||||
|
segmentId: curveArtifact.pathId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return artifacts
|
||||||
|
}
|
@ -1,101 +1,29 @@
|
|||||||
import { PathToNode, Program, SourceRange } from 'lang/wasm'
|
import { Program, SourceRange } from 'lang/wasm'
|
||||||
import { VITE_KC_API_WS_MODELING_URL } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL } from 'env'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { exportSave } from 'lib/exportSave'
|
import { exportSave } from 'lib/exportSave'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
|
||||||
import { Themes, getThemeColorForEngine, getOppositeTheme } from 'lib/theme'
|
import { Themes, getThemeColorForEngine, getOppositeTheme } from 'lib/theme'
|
||||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
|
import {
|
||||||
|
ArtifactMap,
|
||||||
|
EngineCommand,
|
||||||
|
OrderedCommand,
|
||||||
|
ResponseMap,
|
||||||
|
createArtifactMap,
|
||||||
|
} from 'lang/std/artifactMap'
|
||||||
|
|
||||||
// TODO(paultag): This ought to be tweakable.
|
// TODO(paultag): This ought to be tweakable.
|
||||||
const pingIntervalMs = 10000
|
const pingIntervalMs = 10000
|
||||||
|
|
||||||
type CommandTypes = Models['ModelingCmd_type']['type'] | 'batch'
|
|
||||||
|
|
||||||
type CommandInfo =
|
|
||||||
| {
|
|
||||||
commandType: 'extrude'
|
|
||||||
// commandType: CommandTypes
|
|
||||||
range: SourceRange
|
|
||||||
pathToNode: PathToNode
|
|
||||||
/// uuid of the entity to extrude
|
|
||||||
target: string
|
|
||||||
parentId?: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
commandType: 'start_path'
|
|
||||||
// commandType: CommandTypes
|
|
||||||
range: SourceRange
|
|
||||||
pathToNode: PathToNode
|
|
||||||
/// uuid of the entity that have been extruded
|
|
||||||
extrusions: string[]
|
|
||||||
parentId?: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
commandType: CommandTypes
|
|
||||||
range: SourceRange
|
|
||||||
pathToNode: PathToNode
|
|
||||||
parentId?: string
|
|
||||||
additionalData?:
|
|
||||||
| {
|
|
||||||
type: 'cap'
|
|
||||||
info: 'start' | 'end'
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: 'batch-ids'
|
|
||||||
ids: string[]
|
|
||||||
info?: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function isHighlightSetEntity_type(
|
function isHighlightSetEntity_type(
|
||||||
data: any
|
data: any
|
||||||
): data is Models['HighlightSetEntity_type'] {
|
): data is Models['HighlightSetEntity_type'] {
|
||||||
return data.entity_id && data.sequence
|
return data.entity_id && data.sequence
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebSocketResponse = Models['WebSocketResponse_type']
|
|
||||||
type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
|
type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
|
||||||
|
|
||||||
type ResultCommand = CommandInfo & {
|
|
||||||
type: 'result'
|
|
||||||
data: any
|
|
||||||
raw: WebSocketResponse
|
|
||||||
headVertexId?: string
|
|
||||||
}
|
|
||||||
type FailedCommand = CommandInfo & {
|
|
||||||
type: 'failed'
|
|
||||||
errors: Models['FailureWebSocketResponse_type']['errors']
|
|
||||||
}
|
|
||||||
interface ResolveCommand {
|
|
||||||
id: string
|
|
||||||
commandType: CommandTypes
|
|
||||||
range: SourceRange
|
|
||||||
// We ALWAYS need the raw response because we pass it back to the rust side.
|
|
||||||
raw: WebSocketResponse
|
|
||||||
data?: Models['OkModelingCmdResponse_type']
|
|
||||||
errors?: Models['FailureWebSocketResponse_type']['errors']
|
|
||||||
}
|
|
||||||
type PendingCommand = CommandInfo & {
|
|
||||||
type: 'pending'
|
|
||||||
promise: Promise<any>
|
|
||||||
resolve: (val: ResolveCommand) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ArtifactMapCommand = ResultCommand | PendingCommand | FailedCommand
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ArtifactMap is a client-side representation of the artifacts that
|
|
||||||
* have been sent to the server-side engine. It is used to keep track of
|
|
||||||
* the state of each command, and to resolve the promise that was returned.
|
|
||||||
* It is also used to keep track of what entities are in the engine scene,
|
|
||||||
* so that we can associate IDs returned from the engine with the
|
|
||||||
* lines of KCL code that generated them.
|
|
||||||
*/
|
|
||||||
export interface ArtifactMap {
|
|
||||||
[commandId: string]: ArtifactMapCommand
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NewTrackArgs {
|
interface NewTrackArgs {
|
||||||
conn: EngineConnection
|
conn: EngineConnection
|
||||||
mediaStream: MediaStream
|
mediaStream: MediaStream
|
||||||
@ -877,7 +805,7 @@ class EngineConnection extends EventTarget {
|
|||||||
this.engineCommandManager.artifactMap[message.request_id]
|
this.engineCommandManager.artifactMap[message.request_id]
|
||||||
console.error(
|
console.error(
|
||||||
`Error in response to request ${message.request_id}:\n${errorsString}
|
`Error in response to request ${message.request_id}:\n${errorsString}
|
||||||
failed cmd type was ${artifactThatFailed?.commandType}`
|
failed cmd type was ${artifactThatFailed?.type}`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
console.error(`Error from server:\n${errorsString}`)
|
console.error(`Error from server:\n${errorsString}`)
|
||||||
@ -1109,7 +1037,6 @@ class EngineConnection extends EventTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EngineCommand = Models['WebSocketRequest_type']
|
|
||||||
type ModelTypes = Models['OkModelingCmdResponse_type']['type']
|
type ModelTypes = Models['OkModelingCmdResponse_type']['type']
|
||||||
|
|
||||||
type UnreliableResponses = Extract<
|
type UnreliableResponses = Extract<
|
||||||
@ -1196,15 +1123,12 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
* The orderedCommands array of all the the commands sent to the engine, un-folded from batches, and made into one long
|
* The orderedCommands array of all the the commands sent to the engine, un-folded from batches, and made into one long
|
||||||
* list of the individual commands, this is used to process all the commands into the artifactMap
|
* list of the individual commands, this is used to process all the commands into the artifactMap
|
||||||
*/
|
*/
|
||||||
orderedCommands: {
|
orderedCommands: Array<OrderedCommand> = []
|
||||||
command: EngineCommand
|
|
||||||
range: SourceRange
|
|
||||||
}[] = []
|
|
||||||
/**
|
/**
|
||||||
* A map of the responses to the @this.orderedCommands, when processing the commands into the artifactMap, this response map allow
|
* A map of the responses to the @this.orderedCommands, when processing the commands into the artifactMap, this response map allow
|
||||||
* us to look up the response by command id
|
* us to look up the response by command id
|
||||||
*/
|
*/
|
||||||
responseMap: { [commandId: string]: OkWebSocketResponseData } = {}
|
responseMap: ResponseMap = {}
|
||||||
/**
|
/**
|
||||||
* The client-side representation of the scene command artifacts that have been sent to the server;
|
* The client-side representation of the scene command artifacts that have been sent to the server;
|
||||||
* that is, the *non-modeling* commands and corresponding artifacts.
|
* that is, the *non-modeling* commands and corresponding artifacts.
|
||||||
@ -1515,9 +1439,9 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
Object.entries(message.resp.data.responses).forEach(
|
Object.entries(message.resp.data.responses).forEach(
|
||||||
([key, response]) => {
|
([commandId, response]) => {
|
||||||
if (!('response' in response)) return
|
if (!('response' in response)) return
|
||||||
const command = individualPendingResponses[key]
|
const command = individualPendingResponses[commandId]
|
||||||
if (!command) return
|
if (!command) return
|
||||||
if (command.type === 'modeling_cmd_req')
|
if (command.type === 'modeling_cmd_req')
|
||||||
this.addCommandLog({
|
this.addCommandLog({
|
||||||
@ -1528,11 +1452,11 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
modeling_response: response.response,
|
modeling_response: response.response,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
id: key,
|
id: commandId,
|
||||||
cmd_type: command?.cmd?.type,
|
cmd_type: command?.cmd?.type,
|
||||||
})
|
})
|
||||||
|
|
||||||
this.responseMap[key] = {
|
this.responseMap[commandId] = {
|
||||||
type: 'modeling',
|
type: 'modeling',
|
||||||
data: {
|
data: {
|
||||||
modeling_response: response.response,
|
modeling_response: response.response,
|
||||||
@ -1569,106 +1493,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
this.onEngineConnectionStarted
|
this.onEngineConnectionStarted
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
handleIndividualResponse({
|
|
||||||
id,
|
|
||||||
pendingMsg,
|
|
||||||
response,
|
|
||||||
}: {
|
|
||||||
id: string
|
|
||||||
pendingMsg: {
|
|
||||||
command: EngineCommand
|
|
||||||
range: SourceRange
|
|
||||||
}
|
|
||||||
response: OkWebSocketResponseData
|
|
||||||
}) {
|
|
||||||
const command = pendingMsg
|
|
||||||
if (command?.command?.type !== 'modeling_cmd_req') return
|
|
||||||
if (response?.type !== 'modeling') return
|
|
||||||
const command2 = command.command.cmd
|
|
||||||
|
|
||||||
const range = command.range
|
|
||||||
const pathToNode = getNodePathFromSourceRange(this.getAst(), range)
|
|
||||||
const getParentId = (): string | undefined => {
|
|
||||||
if (command2.type === 'extend_path') return command2.path
|
|
||||||
if (command2.type === 'solid3d_get_extrusion_face_info') {
|
|
||||||
const edgeArtifact = this.artifactMap[command2.edge_id]
|
|
||||||
// edges's parent id is to the original "start_path" artifact
|
|
||||||
if (edgeArtifact && edgeArtifact.parentId) {
|
|
||||||
return edgeArtifact.parentId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (command2.type === 'close_path') return command2.path_id
|
|
||||||
if (command2.type === 'extrude') return command2.target
|
|
||||||
// handle other commands that have a parent here
|
|
||||||
}
|
|
||||||
const modelingResponse = response.data.modeling_response
|
|
||||||
|
|
||||||
if (command) {
|
|
||||||
const parentId = getParentId()
|
|
||||||
const artifact = {
|
|
||||||
type: 'result',
|
|
||||||
range: range,
|
|
||||||
pathToNode,
|
|
||||||
commandType: command.command.cmd.type,
|
|
||||||
parentId: parentId,
|
|
||||||
} as ArtifactMapCommand & { extrusions?: string[] }
|
|
||||||
this.artifactMap[id] = artifact
|
|
||||||
if (command2.type === 'extrude') {
|
|
||||||
;(artifact as any).target = command2.target
|
|
||||||
if (this.artifactMap[command2.target]?.commandType === 'start_path') {
|
|
||||||
if ((this.artifactMap[command2.target] as any)?.extrusions?.length) {
|
|
||||||
;(this.artifactMap[command2.target] as any).extrusions.push(id)
|
|
||||||
} else {
|
|
||||||
;(this.artifactMap[command2.target] as any).extrusions = [id]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.artifactMap[id] = artifact
|
|
||||||
if (
|
|
||||||
(command2.type === 'entity_linear_pattern' &&
|
|
||||||
modelingResponse.type === 'entity_linear_pattern') ||
|
|
||||||
(command2.type === 'entity_circular_pattern' &&
|
|
||||||
modelingResponse.type === 'entity_circular_pattern')
|
|
||||||
) {
|
|
||||||
const entities = modelingResponse.data.entity_ids
|
|
||||||
entities?.forEach((entity: string) => {
|
|
||||||
this.artifactMap[entity] = artifact
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
command2.type === 'solid3d_get_extrusion_face_info' &&
|
|
||||||
modelingResponse.type === 'solid3d_get_extrusion_face_info'
|
|
||||||
) {
|
|
||||||
const parent = this.artifactMap[parentId || '']
|
|
||||||
modelingResponse.data.faces.forEach((face) => {
|
|
||||||
if (face.cap !== 'none' && face.face_id && parent) {
|
|
||||||
this.artifactMap[face.face_id] = {
|
|
||||||
...parent,
|
|
||||||
commandType: 'solid3d_get_extrusion_face_info',
|
|
||||||
additionalData: {
|
|
||||||
type: 'cap',
|
|
||||||
info: face.cap === 'bottom' ? 'start' : 'end',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const curveArtifact = this.artifactMap[face?.curve_id || '']
|
|
||||||
if (curveArtifact && face?.face_id) {
|
|
||||||
this.artifactMap[face.face_id] = {
|
|
||||||
...curveArtifact,
|
|
||||||
commandType: 'solid3d_get_extrusion_face_info',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (command) {
|
|
||||||
this.artifactMap[id] = {
|
|
||||||
type: 'result',
|
|
||||||
commandType: command2.type,
|
|
||||||
range,
|
|
||||||
pathToNode,
|
|
||||||
} as ArtifactMapCommand & { extrusions?: string[] }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleResize({
|
handleResize({
|
||||||
streamWidth,
|
streamWidth,
|
||||||
@ -1965,29 +1789,11 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
* When this is done when we build the artifact map synchronously.
|
* When this is done when we build the artifact map synchronously.
|
||||||
*/
|
*/
|
||||||
async waitForAllCommands() {
|
async waitForAllCommands() {
|
||||||
const pendingCommands = Object.values(this.artifactMap).filter(
|
await Promise.all(Object.values(this.pendingCommands).map((a) => a.promise))
|
||||||
({ type }) => type === 'pending'
|
this.artifactMap = createArtifactMap({
|
||||||
) as PendingCommand[]
|
orderedCommands: this.orderedCommands,
|
||||||
const proms = pendingCommands.map(({ promise }) => promise)
|
responseMap: this.responseMap,
|
||||||
|
ast: this.getAst(),
|
||||||
const otherPending = Object.values(this.pendingCommands).map(
|
|
||||||
(a) => a.promise
|
|
||||||
)
|
|
||||||
await Promise.all([...proms, otherPending])
|
|
||||||
this.orderedCommands.forEach(({ command, range }) => {
|
|
||||||
// expect all to be `modeling_cmd_req` as batch commands have
|
|
||||||
// already been expanded before being added to orderedCommands
|
|
||||||
if (command.type !== 'modeling_cmd_req') return
|
|
||||||
const id = command.cmd_id
|
|
||||||
const response = this.responseMap[id]
|
|
||||||
this.handleIndividualResponse({
|
|
||||||
id,
|
|
||||||
pendingMsg: {
|
|
||||||
command,
|
|
||||||
range,
|
|
||||||
},
|
|
||||||
response,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
private async initPlanes() {
|
private async initPlanes() {
|
||||||
@ -2047,13 +1853,11 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
): string | undefined {
|
): string | undefined {
|
||||||
const values = Object.entries(this.artifactMap)
|
const values = Object.entries(this.artifactMap)
|
||||||
for (const [id, data] of values) {
|
for (const [id, data] of values) {
|
||||||
if (data.type !== 'result') continue
|
// // Our range selection seems to just select the cursor position, so either
|
||||||
|
// // of these can be right...
|
||||||
// Our range selection seems to just select the cursor position, so either
|
|
||||||
// of these can be right...
|
|
||||||
if (
|
if (
|
||||||
(data.range[0] === range[0] || data.range[1] === range[1]) &&
|
(data.range[0] === range[0] || data.range[1] === range[1]) &&
|
||||||
data.commandType === commandTypeToTarget
|
data.type === commandTypeToTarget
|
||||||
)
|
)
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { Program, PathToNode } from './wasm'
|
import { Program, PathToNode } from './wasm'
|
||||||
import { getNodeFromPath } from './queryAst'
|
import { getNodeFromPath } from './queryAst'
|
||||||
import { ArtifactMap } from './std/engineConnection'
|
import {
|
||||||
|
ArtifactMap,
|
||||||
|
ArtifactMapCommand,
|
||||||
|
SegmentArtifact,
|
||||||
|
StartPathArtifact,
|
||||||
|
} from 'lang/std/artifactMap'
|
||||||
import { isOverlap } from 'lib/utils'
|
import { isOverlap } from 'lib/utils'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
|
||||||
@ -49,24 +54,22 @@ export function isCursorInSketchCommandRange(
|
|||||||
artifactMap: ArtifactMap,
|
artifactMap: ArtifactMap,
|
||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
): string | false {
|
): string | false {
|
||||||
const overlapingEntries: [string, ArtifactMap[string]][] = Object.entries(
|
const overlappingEntries = Object.entries(artifactMap).filter(
|
||||||
artifactMap
|
([id, artifact]: [string, ArtifactMapCommand]) =>
|
||||||
).filter(([id, artifact]: [string, ArtifactMap[string]]) =>
|
selectionRanges.codeBasedSelections.some(
|
||||||
selectionRanges.codeBasedSelections.some(
|
(selection) =>
|
||||||
(selection) =>
|
Array.isArray(selection?.range) &&
|
||||||
Array.isArray(selection?.range) &&
|
Array.isArray(artifact?.range) &&
|
||||||
Array.isArray(artifact?.range) &&
|
isOverlap(selection.range, artifact.range) &&
|
||||||
isOverlap(selection.range, artifact.range) &&
|
(artifact.type === 'startPath' || artifact.type === 'segment')
|
||||||
(artifact.commandType === 'start_path' ||
|
)
|
||||||
artifact.commandType === 'extend_path' ||
|
) as [string, StartPathArtifact | SegmentArtifact][]
|
||||||
artifact.commandType === 'close_path')
|
const secondEntry = overlappingEntries?.[0]?.[1]
|
||||||
)
|
const parentId = secondEntry?.type === 'segment' ? secondEntry.pathId : false
|
||||||
)
|
let result = parentId
|
||||||
let result =
|
? parentId
|
||||||
overlapingEntries.length && overlapingEntries[0][1].parentId
|
: overlappingEntries.find(
|
||||||
? overlapingEntries[0][1].parentId
|
([, artifact]) => artifact.type === 'startPath'
|
||||||
: overlapingEntries.find(
|
)?.[0] || false
|
||||||
([, artifact]) => artifact.commandType === 'start_path'
|
|
||||||
)?.[0] || false
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -108,21 +108,21 @@ export async function getEventForSelectWithPoint(
|
|||||||
}
|
}
|
||||||
const sourceRange = _artifact?.range
|
const sourceRange = _artifact?.range
|
||||||
if (_artifact) {
|
if (_artifact) {
|
||||||
if (_artifact.commandType === 'solid3d_get_extrusion_face_info') {
|
if (_artifact.type === 'extrudeCap')
|
||||||
if (_artifact?.additionalData)
|
return {
|
||||||
return {
|
type: 'Set selection',
|
||||||
type: 'Set selection',
|
data: {
|
||||||
data: {
|
selectionType: 'singleCodeCursor',
|
||||||
selectionType: 'singleCodeCursor',
|
selection: {
|
||||||
selection: {
|
range: sourceRange,
|
||||||
range: sourceRange,
|
type:
|
||||||
type:
|
_artifact?.additionalData.info === 'end'
|
||||||
_artifact?.additionalData.info === 'end'
|
? 'end-cap'
|
||||||
? 'end-cap'
|
: 'start-cap',
|
||||||
: 'start-cap',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
|
}
|
||||||
|
if (_artifact.type === 'extrudeWall')
|
||||||
return {
|
return {
|
||||||
type: 'Set selection',
|
type: 'Set selection',
|
||||||
data: {
|
data: {
|
||||||
@ -130,7 +130,6 @@ export async function getEventForSelectWithPoint(
|
|||||||
selection: { range: sourceRange, type: 'extrude-wall' },
|
selection: { range: sourceRange, type: 'extrude-wall' },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
type: 'Set selection',
|
type: 'Set selection',
|
||||||
data: {
|
data: {
|
||||||
@ -519,16 +518,13 @@ function codeToIdSelections(
|
|||||||
let bestCandidate
|
let bestCandidate
|
||||||
entriesWithOverlap.forEach((entry) => {
|
entriesWithOverlap.forEach((entry) => {
|
||||||
if (!entry) return
|
if (!entry) return
|
||||||
if (
|
if (type === 'default' && entry.artifact.type === 'segment') {
|
||||||
type === 'default' &&
|
|
||||||
entry.artifact.commandType === 'extend_path'
|
|
||||||
) {
|
|
||||||
bestCandidate = entry
|
bestCandidate = entry
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
type === 'start-cap' &&
|
type === 'start-cap' &&
|
||||||
entry.artifact.commandType === 'solid3d_get_extrusion_face_info' &&
|
entry.artifact.type === 'extrudeCap' &&
|
||||||
entry?.artifact?.additionalData?.info === 'start'
|
entry?.artifact?.additionalData?.info === 'start'
|
||||||
) {
|
) {
|
||||||
bestCandidate = entry
|
bestCandidate = entry
|
||||||
@ -536,16 +532,13 @@ function codeToIdSelections(
|
|||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
type === 'end-cap' &&
|
type === 'end-cap' &&
|
||||||
entry.artifact.commandType === 'solid3d_get_extrusion_face_info' &&
|
entry.artifact.type === 'extrudeCap' &&
|
||||||
entry?.artifact?.additionalData?.info === 'end'
|
entry?.artifact?.additionalData?.info === 'end'
|
||||||
) {
|
) {
|
||||||
bestCandidate = entry
|
bestCandidate = entry
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (
|
if (type === 'extrude-wall' && entry.artifact.type === 'extrudeWall') {
|
||||||
type === 'extrude-wall' &&
|
|
||||||
entry.artifact.commandType === 'solid3d_get_extrusion_face_info'
|
|
||||||
) {
|
|
||||||
bestCandidate = entry
|
bestCandidate = entry
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { Program, ProgramMemory, _executor, SourceRange } from '../lang/wasm'
|
import { Program, ProgramMemory, _executor, SourceRange } from '../lang/wasm'
|
||||||
import {
|
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||||
EngineCommandManager,
|
import { EngineCommand } from 'lang/std/artifactMap'
|
||||||
EngineCommand,
|
|
||||||
} from '../lang/std/engineConnection'
|
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
|
@ -1142,7 +1142,7 @@ export const modelingMachine = createMachine(
|
|||||||
const sketchGroup = kclManager.programMemory.get(sketchVar)
|
const sketchGroup = kclManager.programMemory.get(sketchVar)
|
||||||
if (sketchGroup?.type !== 'SketchGroup') return
|
if (sketchGroup?.type !== 'SketchGroup') return
|
||||||
const idArtifact = engineCommandManager.artifactMap[sketchGroup.id]
|
const idArtifact = engineCommandManager.artifactMap[sketchGroup.id]
|
||||||
if (idArtifact.commandType !== 'start_path') return
|
if (idArtifact.type !== 'startPath') return
|
||||||
const extrusionArtifactId = (idArtifact as any)?.extrusions?.[0]
|
const extrusionArtifactId = (idArtifact as any)?.extrusions?.[0]
|
||||||
if (typeof extrusionArtifactId !== 'string') return
|
if (typeof extrusionArtifactId !== 'string') return
|
||||||
const extrusionArtifact = (engineCommandManager.artifactMap as any)[
|
const extrusionArtifact = (engineCommandManager.artifactMap as any)[
|
||||||
|
Reference in New Issue
Block a user