From 95781143eb9512561b165ce7342eac7b044e8f55 Mon Sep 17 00:00:00 2001 From: Kurt Hutten Date: Thu, 25 Jul 2024 19:03:56 +1000 Subject: [PATCH] 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 --- e2e/playwright/flow-tests.spec.ts | 2 +- e2e/playwright/test-utils.ts | 4 +- src/App.tsx | 2 +- src/clientSideScene/CameraControls.ts | 2 +- src/clientSideScene/sceneEntities.ts | 26 +-- src/lang/KclSingleton.ts | 1 - src/lang/std/artifactMap.ts | 280 ++++++++++++++++++++++++++ src/lang/std/engineConnection.ts | 242 +++------------------- src/lang/util.ts | 43 ++-- src/lib/selections.ts | 43 ++-- src/lib/testHelpers.ts | 6 +- src/machines/modelingMachine.ts | 2 +- 12 files changed, 363 insertions(+), 290 deletions(-) create mode 100644 src/lang/std/artifactMap.ts diff --git a/e2e/playwright/flow-tests.spec.ts b/e2e/playwright/flow-tests.spec.ts index 2b8d38f98..2a996b0b1 100644 --- a/e2e/playwright/flow-tests.spec.ts +++ b/e2e/playwright/flow-tests.spec.ts @@ -26,7 +26,7 @@ import * as TOML from '@iarna/toml' import { LineInputsType } from 'lang/std/sketchcombos' import { Coords2d } from 'lang/std/sketch' 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 { bracket } from 'lib/exampleKcl' diff --git a/e2e/playwright/test-utils.ts b/e2e/playwright/test-utils.ts index 43cd31e68..77e56a710 100644 --- a/e2e/playwright/test-utils.ts +++ b/e2e/playwright/test-utils.ts @@ -1,5 +1,5 @@ -import { test, expect, Page, Download } from '@playwright/test' -import { EngineCommand } from '../../src/lang/std/engineConnection' +import { expect, Page, Download } from '@playwright/test' +import { EngineCommand } from 'lang/std/artifactMap' import os from 'os' import fsp from 'fs/promises' import pixelMatch from 'pixelmatch' diff --git a/src/App.tsx b/src/App.tsx index 0733b9da5..7e13746a8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,7 @@ import { MouseEventHandler, useEffect, useMemo, useRef } from 'react' import { uuidv4 } from 'lib/utils' import { useHotKeyListener } from './hooks/useHotKeyListener' import { Stream } from './components/Stream' -import { EngineCommand } from './lang/std/engineConnection' +import { EngineCommand } from 'lang/std/artifactMap' import { throttle } from './lib/utils' import { AppHeader } from './components/AppHeader' import { useHotkeys } from 'react-hotkeys-hook' diff --git a/src/clientSideScene/CameraControls.ts b/src/clientSideScene/CameraControls.ts index 4bbec9f68..be033053c 100644 --- a/src/clientSideScene/CameraControls.ts +++ b/src/clientSideScene/CameraControls.ts @@ -17,11 +17,11 @@ import { ZOOM_MAGIC_NUMBER, } from './sceneInfra' import { - EngineCommand, Subscription, EngineCommandManager, UnreliableSubscription, } from 'lang/std/engineConnection' +import { EngineCommand } from 'lang/std/artifactMap' import { uuidv4 } from 'lib/utils' import { deg2Rad } from 'lib/utils2d' import { isReducedMotion, roundOff, throttle } from 'lib/utils' diff --git a/src/clientSideScene/sceneEntities.ts b/src/clientSideScene/sceneEntities.ts index e72efd5c5..f4d6a8dd0 100644 --- a/src/clientSideScene/sceneEntities.ts +++ b/src/clientSideScene/sceneEntities.ts @@ -95,10 +95,7 @@ import { createGridHelper, orthoScale, perspScale } from './helpers' import { Models } from '@kittycad/lib' import { uuidv4 } from 'lib/utils' import { SegmentOverlayPayload, SketchDetails } from 'machines/modelingMachine' -import { - ArtifactMapCommand, - EngineCommandManager, -} from 'lang/std/engineConnection' +import { EngineCommandManager } from 'lang/std/engineConnection' import { getRectangleCallExpressions, updateRectangleSketch, @@ -1562,25 +1559,24 @@ export class SceneEntities { // to get the sketch profile's face ID. If we clicked on an endcap, // we already have it. const targetId = - 'additionalData' in artifact && - artifact.additionalData?.type === 'cap' - ? _entity_id - : artifact.parentId + artifact?.type === 'extrudeWall' || artifact?.type === 'extrudeCap' + ? artifact.segmentId + : '' // tsc cannot infer that target can have extrusions // from the commandType (why?) so we need to cast it - const target = this.engineCommandManager.artifactMap?.[ - targetId || '' - ] as ArtifactMapCommand & { extrusions?: string[] } + const target = this.engineCommandManager.artifactMap?.[targetId || ''] + const extrusionId = + target?.type === 'startPath' ? target.extrusions[0] : '' // TODO: We get the first extrusion command ID, // which is fine while backend systems only support one extrusion. // but we need to more robustly handle resolving to the correct extrusion // if there are multiple. - const extrusions = - this.engineCommandManager.artifactMap?.[target?.extrusions?.[0] || ''] + const extrusions = this.engineCommandManager.artifactMap?.[extrusionId] - if (artifact?.commandType !== 'solid3d_get_extrusion_face_info') return + if (artifact?.type !== 'extrudeCap' && artifact?.type !== 'extrudeWall') + return const faceInfo = await getFaceDetails(_entity_id) if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis) return @@ -1606,7 +1602,7 @@ export class SceneEntities { sketchPathToNode, extrudePathToNode, cap: - artifact?.additionalData?.type === 'cap' + artifact.type === 'extrudeCap' ? artifact.additionalData.info : 'none', faceId: _entity_id, diff --git a/src/lang/KclSingleton.ts b/src/lang/KclSingleton.ts index 5a5b69773..0d9de4f44 100644 --- a/src/lang/KclSingleton.ts +++ b/src/lang/KclSingleton.ts @@ -5,7 +5,6 @@ import { uuidv4 } from 'lib/utils' import { EngineCommandManager } from './std/engineConnection' import { err } from 'lib/trap' -import { deferExecution } from 'lib/utils' import { CallExpression, initPromise, diff --git a/src/lang/std/artifactMap.ts b/src/lang/std/artifactMap.ts new file mode 100644 index 000000000..234d595f6 --- /dev/null +++ b/src/lang/std/artifactMap.ts @@ -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 + 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 +} diff --git a/src/lang/std/engineConnection.ts b/src/lang/std/engineConnection.ts index a8c35ecdf..efadbe3c8 100644 --- a/src/lang/std/engineConnection.ts +++ b/src/lang/std/engineConnection.ts @@ -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 { Models } from '@kittycad/lib' import { exportSave } from 'lib/exportSave' import { uuidv4 } from 'lib/utils' -import { getNodePathFromSourceRange } from 'lang/queryAst' import { Themes, getThemeColorForEngine, getOppositeTheme } from 'lib/theme' 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. 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( data: any ): data is Models['HighlightSetEntity_type'] { return data.entity_id && data.sequence } -type WebSocketResponse = Models['WebSocketResponse_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 - 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 { conn: EngineConnection mediaStream: MediaStream @@ -877,7 +805,7 @@ class EngineConnection extends EventTarget { this.engineCommandManager.artifactMap[message.request_id] console.error( `Error in response to request ${message.request_id}:\n${errorsString} - failed cmd type was ${artifactThatFailed?.commandType}` + failed cmd type was ${artifactThatFailed?.type}` ) } else { 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 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 * list of the individual commands, this is used to process all the commands into the artifactMap */ - orderedCommands: { - command: EngineCommand - range: SourceRange - }[] = [] + orderedCommands: Array = [] /** * 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 */ - responseMap: { [commandId: string]: OkWebSocketResponseData } = {} + responseMap: ResponseMap = {} /** * 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. @@ -1515,9 +1439,9 @@ export class EngineCommandManager extends EventTarget { } }) Object.entries(message.resp.data.responses).forEach( - ([key, response]) => { + ([commandId, response]) => { if (!('response' in response)) return - const command = individualPendingResponses[key] + const command = individualPendingResponses[commandId] if (!command) return if (command.type === 'modeling_cmd_req') this.addCommandLog({ @@ -1528,11 +1452,11 @@ export class EngineCommandManager extends EventTarget { modeling_response: response.response, }, }, - id: key, + id: commandId, cmd_type: command?.cmd?.type, }) - this.responseMap[key] = { + this.responseMap[commandId] = { type: 'modeling', data: { modeling_response: response.response, @@ -1569,106 +1493,6 @@ export class EngineCommandManager extends EventTarget { 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({ streamWidth, @@ -1965,29 +1789,11 @@ export class EngineCommandManager extends EventTarget { * When this is done when we build the artifact map synchronously. */ async waitForAllCommands() { - const pendingCommands = Object.values(this.artifactMap).filter( - ({ type }) => type === 'pending' - ) as PendingCommand[] - const proms = pendingCommands.map(({ promise }) => promise) - - 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, - }) + await Promise.all(Object.values(this.pendingCommands).map((a) => a.promise)) + this.artifactMap = createArtifactMap({ + orderedCommands: this.orderedCommands, + responseMap: this.responseMap, + ast: this.getAst(), }) } private async initPlanes() { @@ -2047,13 +1853,11 @@ export class EngineCommandManager extends EventTarget { ): string | undefined { const values = Object.entries(this.artifactMap) 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 ( (data.range[0] === range[0] || data.range[1] === range[1]) && - data.commandType === commandTypeToTarget + data.type === commandTypeToTarget ) return id } diff --git a/src/lang/util.ts b/src/lang/util.ts index 2a8b60d14..f8e4ccad4 100644 --- a/src/lang/util.ts +++ b/src/lang/util.ts @@ -1,7 +1,12 @@ import { Selections } from 'lib/selections' import { Program, PathToNode } from './wasm' import { getNodeFromPath } from './queryAst' -import { ArtifactMap } from './std/engineConnection' +import { + ArtifactMap, + ArtifactMapCommand, + SegmentArtifact, + StartPathArtifact, +} from 'lang/std/artifactMap' import { isOverlap } from 'lib/utils' import { err } from 'lib/trap' @@ -49,24 +54,22 @@ export function isCursorInSketchCommandRange( artifactMap: ArtifactMap, selectionRanges: Selections ): string | false { - const overlapingEntries: [string, ArtifactMap[string]][] = Object.entries( - artifactMap - ).filter(([id, artifact]: [string, ArtifactMap[string]]) => - selectionRanges.codeBasedSelections.some( - (selection) => - Array.isArray(selection?.range) && - Array.isArray(artifact?.range) && - isOverlap(selection.range, artifact.range) && - (artifact.commandType === 'start_path' || - artifact.commandType === 'extend_path' || - artifact.commandType === 'close_path') - ) - ) - let result = - overlapingEntries.length && overlapingEntries[0][1].parentId - ? overlapingEntries[0][1].parentId - : overlapingEntries.find( - ([, artifact]) => artifact.commandType === 'start_path' - )?.[0] || false + const overlappingEntries = Object.entries(artifactMap).filter( + ([id, artifact]: [string, ArtifactMapCommand]) => + selectionRanges.codeBasedSelections.some( + (selection) => + Array.isArray(selection?.range) && + Array.isArray(artifact?.range) && + isOverlap(selection.range, artifact.range) && + (artifact.type === 'startPath' || artifact.type === 'segment') + ) + ) as [string, StartPathArtifact | SegmentArtifact][] + const secondEntry = overlappingEntries?.[0]?.[1] + const parentId = secondEntry?.type === 'segment' ? secondEntry.pathId : false + let result = parentId + ? parentId + : overlappingEntries.find( + ([, artifact]) => artifact.type === 'startPath' + )?.[0] || false return result } diff --git a/src/lib/selections.ts b/src/lib/selections.ts index 5ca72c03a..984a4c077 100644 --- a/src/lib/selections.ts +++ b/src/lib/selections.ts @@ -108,21 +108,21 @@ export async function getEventForSelectWithPoint( } const sourceRange = _artifact?.range if (_artifact) { - if (_artifact.commandType === 'solid3d_get_extrusion_face_info') { - if (_artifact?.additionalData) - return { - type: 'Set selection', - data: { - selectionType: 'singleCodeCursor', - selection: { - range: sourceRange, - type: - _artifact?.additionalData.info === 'end' - ? 'end-cap' - : 'start-cap', - }, + if (_artifact.type === 'extrudeCap') + return { + type: 'Set selection', + data: { + selectionType: 'singleCodeCursor', + selection: { + range: sourceRange, + type: + _artifact?.additionalData.info === 'end' + ? 'end-cap' + : 'start-cap', }, - } + }, + } + if (_artifact.type === 'extrudeWall') return { type: 'Set selection', data: { @@ -130,7 +130,6 @@ export async function getEventForSelectWithPoint( selection: { range: sourceRange, type: 'extrude-wall' }, }, } - } return { type: 'Set selection', data: { @@ -519,16 +518,13 @@ function codeToIdSelections( let bestCandidate entriesWithOverlap.forEach((entry) => { if (!entry) return - if ( - type === 'default' && - entry.artifact.commandType === 'extend_path' - ) { + if (type === 'default' && entry.artifact.type === 'segment') { bestCandidate = entry return } if ( type === 'start-cap' && - entry.artifact.commandType === 'solid3d_get_extrusion_face_info' && + entry.artifact.type === 'extrudeCap' && entry?.artifact?.additionalData?.info === 'start' ) { bestCandidate = entry @@ -536,16 +532,13 @@ function codeToIdSelections( } if ( type === 'end-cap' && - entry.artifact.commandType === 'solid3d_get_extrusion_face_info' && + entry.artifact.type === 'extrudeCap' && entry?.artifact?.additionalData?.info === 'end' ) { bestCandidate = entry return } - if ( - type === 'extrude-wall' && - entry.artifact.commandType === 'solid3d_get_extrusion_face_info' - ) { + if (type === 'extrude-wall' && entry.artifact.type === 'extrudeWall') { bestCandidate = entry return } diff --git a/src/lib/testHelpers.ts b/src/lib/testHelpers.ts index c5e5277c5..243d84852 100644 --- a/src/lib/testHelpers.ts +++ b/src/lib/testHelpers.ts @@ -1,8 +1,6 @@ import { Program, ProgramMemory, _executor, SourceRange } from '../lang/wasm' -import { - EngineCommandManager, - EngineCommand, -} from '../lang/std/engineConnection' +import { EngineCommandManager } from 'lang/std/engineConnection' +import { EngineCommand } from 'lang/std/artifactMap' import { Models } from '@kittycad/lib' import { v4 as uuidv4 } from 'uuid' import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes' diff --git a/src/machines/modelingMachine.ts b/src/machines/modelingMachine.ts index 576620329..901d83958 100644 --- a/src/machines/modelingMachine.ts +++ b/src/machines/modelingMachine.ts @@ -1142,7 +1142,7 @@ export const modelingMachine = createMachine( const sketchGroup = kclManager.programMemory.get(sketchVar) if (sketchGroup?.type !== 'SketchGroup') return const idArtifact = engineCommandManager.artifactMap[sketchGroup.id] - if (idArtifact.commandType !== 'start_path') return + if (idArtifact.type !== 'startPath') return const extrusionArtifactId = (idArtifact as any)?.extrusions?.[0] if (typeof extrusionArtifactId !== 'string') return const extrusionArtifact = (engineCommandManager.artifactMap as any)[