Move artifact graph out of engine connection (#6062)

* cleanups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* cleanups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fmt

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
This commit is contained in:
Jess Frazelle
2025-03-29 17:25:26 -07:00
committed by GitHub
parent db5ce7ba85
commit 57d78b6094
23 changed files with 229 additions and 247 deletions

View File

@ -1,6 +1,6 @@
import { useRef, useMemo, memo, useCallback, useState } from 'react'
import { isCursorInSketchCommandRange } from 'lang/util'
import { editorManager, engineCommandManager, kclManager } from 'lib/singletons'
import { editorManager, kclManager } from 'lib/singletons'
import { useModelingContext } from 'hooks/useModelingContext'
import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus'
@ -45,10 +45,10 @@ export function Toolbar({
)
return false
return isCursorInSketchCommandRange(
engineCommandManager.artifactGraph,
kclManager.artifactGraph,
context.selectionRanges
)
}, [engineCommandManager.artifactGraph, context.selectionRanges])
}, [kclManager.artifactGraph, context.selectionRanges])
const toolbarButtonsRef = useRef<HTMLUListElement>(null)
const { overallState } = useNetworkContext()

View File

@ -3700,7 +3700,7 @@ function computeSelectionFromSourceRangeAndAST(
sourceRange: SourceRange,
ast: Node<Program>
): Selections {
const artifactGraph = engineCommandManager.artifactGraph
const artifactGraph = kclManager.artifactGraph
const artifact = getArtifactFromRange(sourceRange, artifactGraph) || undefined
const selection: Selections = {
graphSelections: [

View File

@ -1,5 +1,5 @@
import { useModelingContext } from 'hooks/useModelingContext'
import { editorManager, engineCommandManager, kclManager } from 'lib/singletons'
import { editorManager, kclManager } from 'lib/singletons'
import { getNodeFromPath } from 'lang/queryAst'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { useEffect, useRef, useState } from 'react'
@ -135,12 +135,10 @@ function DisplayObj({
const range = topLevelRange(obj?.start || 0, obj.end || 0)
const idInfo = codeToIdSelections(
[{ codeRef: codeRefFromRange(range, kclManager.ast) }],
engineCommandManager.artifactGraph,
engineCommandManager.artifactIndex
kclManager.artifactGraph,
kclManager.artifactIndex
)[0]
const artifact = engineCommandManager.artifactGraph.get(
idInfo?.id || ''
)
const artifact = kclManager.artifactGraph.get(idInfo?.id || '')
if (!artifact) return
send({
type: 'Set selection',

View File

@ -1,13 +1,13 @@
import { useMemo } from 'react'
import { engineCommandManager } from 'lib/singletons'
import { kclManager } from 'lib/singletons'
import { expandPlane, PlaneArtifactRich } from 'lang/std/artifactGraph'
import { ArtifactGraph } from 'lang/wasm'
import { DebugDisplayArray, GenericObj } from './DebugDisplayObj'
export function DebugArtifactGraph() {
const artifactGraphTree = useMemo(() => {
return computeTree(engineCommandManager.artifactGraph)
}, [engineCommandManager.artifactGraph])
return computeTree(kclManager.artifactGraph)
}, [kclManager.artifactGraph])
const filterKeys: string[] = ['codeRef', 'pathToNode']
return (

View File

@ -566,7 +566,7 @@ export const ModelingMachineProvider = ({
// See if the selection is "close enough" to be coerced to the plane later
const maybePlane = getPlaneFromArtifact(
selectionRanges.graphSelections[0].artifact,
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
return !err(maybePlane)
}
@ -579,7 +579,7 @@ export const ModelingMachineProvider = ({
return false
}
return !!isCursorInSketchCommandRange(
engineCommandManager.artifactGraph,
kclManager.artifactGraph,
selectionRanges
)
},
@ -841,7 +841,7 @@ export const ModelingMachineProvider = ({
const artifact = selectionRanges.graphSelections[0].artifact
const plane = getPlaneFromArtifact(
artifact,
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(plane)) return Promise.reject(plane)
// if the user selected a segment, make sure we enter the right sketch as there can be multiple on a plane
@ -917,14 +917,13 @@ export const ModelingMachineProvider = ({
info?.sketchDetails?.faceId || ''
)
const sketchArtifact =
engineCommandManager.artifactGraph.get(mainPath)
const sketchArtifact = kclManager.artifactGraph.get(mainPath)
if (sketchArtifact?.type !== 'path')
return Promise.reject(new Error('No sketch artifact'))
const sketchPaths = getPathsFromArtifact({
artifact: engineCommandManager.artifactGraph.get(plane.id),
artifact: kclManager.artifactGraph.get(plane.id),
sketchPathToNode: sketchArtifact?.codeRef?.pathToNode,
artifactGraph: engineCommandManager.artifactGraph,
artifactGraph: kclManager.artifactGraph,
ast: kclManager.ast,
})
if (err(sketchPaths)) return Promise.reject(sketchPaths)
@ -1743,7 +1742,7 @@ export const ModelingMachineProvider = ({
prompt: input.prompt,
selections: input.selection,
token,
artifactGraph: engineCommandManager.artifactGraph,
artifactGraph: kclManager.artifactGraph,
projectName: context.project.name,
})
}),

View File

@ -13,7 +13,7 @@ import {
getOperationLabel,
stdLibMap,
} from 'lib/operations'
import { editorManager, engineCommandManager, kclManager } from 'lib/singletons'
import { editorManager, kclManager } from 'lib/singletons'
import { ComponentProps, useEffect, useMemo, useRef, useState } from 'react'
import { Operation } from '@rust/kcl-lib/bindings/Operation'
import { Actor, Prop } from 'xstate'
@ -58,7 +58,7 @@ export const FeatureTreePane = () => {
const artifact = context.targetSourceRange
? getArtifactFromRange(
context.targetSourceRange,
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
: null

View File

@ -304,7 +304,7 @@ export const Stream = () => {
}
const path = getArtifactOfTypes(
{ key: entity_id, types: ['path', 'solid2d', 'segment', 'helix'] },
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(path)) {
return path

View File

@ -16,7 +16,7 @@ import { TransformInfo } from 'lang/std/stdTypes'
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap'
import { Node } from '@rust/kcl-lib/bindings/Node'
@ -45,7 +45,7 @@ export function intersectInfo({
selectionRanges.graphSelections.length > 1 &&
isLinesParallelAndConstrained(
kclManager.ast,
engineCommandManager.artifactGraph,
kclManager.artifactGraph,
kclManager.variables,
selectionRanges.graphSelections[0],
selectionRanges.graphSelections[1]

View File

@ -374,7 +374,7 @@ export default class EditorManager {
selectionRanges: this._selectionRanges,
isShiftDown: this._isShiftDown,
ast: kclManager.ast,
artifactGraph: engineCommandManager.artifactGraph,
artifactGraph: kclManager.artifactGraph,
})
if (!eventInfo) {

View File

@ -39,7 +39,7 @@ export function useEngineConnectionSubscriptions() {
if (data?.entity_id) {
const codeRefs = getCodeRefsByArtifactId(
data.entity_id,
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (codeRefs) {
editorManager.setHighlightRange(codeRefs.map(({ range }) => range))
@ -140,8 +140,7 @@ export function useEngineConnectionSubscriptions() {
})
return
}
const artifact =
engineCommandManager.artifactGraph.get(planeOrFaceId)
const artifact = kclManager.artifactGraph.get(planeOrFaceId)
if (artifact?.type === 'plane') {
const planeInfo = await getFaceDetails(planeOrFaceId)
@ -179,7 +178,7 @@ export function useEngineConnectionSubscriptions() {
const faceId = planeOrFaceId
const extrusion = getSweepFromSuspectedSweepSurface(
faceId,
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (
@ -193,9 +192,9 @@ export function useEngineConnectionSubscriptions() {
const codeRef =
artifact.type === 'cap'
? getCapCodeRef(artifact, engineCommandManager.artifactGraph)
? getCapCodeRef(artifact, kclManager.artifactGraph)
: artifact.type === 'wall'
? getWallCodeRef(artifact, engineCommandManager.artifactGraph)
? getWallCodeRef(artifact, kclManager.artifactGraph)
: artifact.codeRef
const faceInfo = await getFaceDetails(faceId)
@ -221,7 +220,7 @@ export function useEngineConnectionSubscriptions() {
key: artifact.consumedEdgeId,
types: ['segment', 'sweepEdge'],
},
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(consumedArtifact)) return null
if (consumedArtifact.type === 'segment') {
@ -232,7 +231,7 @@ export function useEngineConnectionSubscriptions() {
} else {
const segment = getArtifactOfTypes(
{ key: consumedArtifact.segId, types: ['segment'] },
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(segment)) return null
chamferInfo = {

View File

@ -5,10 +5,12 @@ import {
compilationErrorsToDiagnostics,
kclErrorsToDiagnostics,
} from './errors'
import { uuidv4 } from 'lib/utils'
import { uuidv4, isOverlap, deferExecution } from 'lib/utils'
import { EngineCommandManager } from './std/engineConnection'
import { err, reportRejection } from 'lib/trap'
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import { buildArtifactIndex } from 'lib/artifactIndex'
import { ArtifactIndex } from 'lib/artifactIndex'
import {
emptyExecState,
@ -24,6 +26,7 @@ import {
SourceRange,
topLevelRange,
VariableMap,
ArtifactGraph,
} from 'lang/wasm'
import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
import {
@ -53,6 +56,14 @@ interface ExecuteArgs {
}
export class KclManager {
/**
* The artifactGraph is a client-side representation of the commands that have been sent
* see: src/lang/std/artifactGraph-README.md for a full explanation.
*/
artifactGraph: ArtifactGraph = new Map()
artifactIndex: ArtifactIndex = []
defaultPlanesShown: boolean = false
private _ast: Node<Program> = {
body: [],
shebang: null,
@ -289,6 +300,47 @@ export class KclManager {
}
}
private async updateArtifactGraph(
execStateArtifactGraph: ExecState['artifactGraph']
) {
this.artifactGraph = execStateArtifactGraph
this.artifactIndex = buildArtifactIndex(execStateArtifactGraph)
if (this.artifactGraph.size) {
// TODO: we wanna remove this logic from xstate, it is racey
// This defer is bullshit but playwright wants it
// It was like this in engineConnection.ts already
deferExecution((a?: null) => {
this.engineCommandManager.modelingSend({
type: 'Artifact graph emptied',
})
}, 200)(null)
} else {
deferExecution((a?: null) => {
this.engineCommandManager.modelingSend({
type: 'Artifact graph populated',
})
}, 200)(null)
}
}
// Some "objects" have the same source range, such as sketch_mode_start and start_path.
// So when passing a range, we need to also specify the command type
private mapRangeToObjectId(
range: SourceRange,
commandTypeToTarget: string
): string | undefined {
for (const [artifactId, artifact] of this.artifactGraph) {
if (
'codeRef' in artifact &&
artifact.codeRef &&
isOverlap(range, artifact.codeRef.range)
) {
if (commandTypeToTarget === artifact.type) return artifactId
}
}
return undefined
}
async safeParse(code: string): Promise<Node<Program> | null> {
const result = parse(code)
this.diagnostics = []
@ -330,6 +382,7 @@ export class KclManager {
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
console.error(e)
this.wasmInitFailed = true
}
}
@ -370,12 +423,12 @@ export class KclManager {
// Do not send send scene commands if the program was interrupted, go to clean up
if (!isInterrupted) {
this.addDiagnostics(await lintAst({ ast: ast }))
setSelectionFilterToDefault(this.engineCommandManager)
await setSelectionFilterToDefault(this.engineCommandManager)
if (args.zoomToFit) {
let zoomObjectId: string | undefined = ''
if (args.zoomOnRangeAndType) {
zoomObjectId = this.engineCommandManager?.mapRangeToObjectId(
zoomObjectId = this.mapRangeToObjectId(
args.zoomOnRangeAndType.range,
args.zoomOnRangeAndType.type
)
@ -429,7 +482,7 @@ export class KclManager {
}
this.ast = { ...ast }
// updateArtifactGraph relies on updated executeState/variables
this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
await this.updateArtifactGraph(execState.artifactGraph)
this._executeCallback()
if (!isInterrupted) {
sceneInfra.modelingSend({ type: 'code edit during sketch' })

View File

@ -139,7 +139,7 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
// executeAst and artifactGraph
await kclManager.executeAst({ ast })
const artifactGraph = engineCommandManager.artifactGraph
const artifactGraph = kclManager.artifactGraph
// find artifact
const maybeArtifact = [...artifactGraph].find(([, artifact]) => {
@ -348,7 +348,7 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
// executeAst
await kclManager.executeAst({ ast })
const artifactGraph = engineCommandManager.artifactGraph
const artifactGraph = kclManager.artifactGraph
const selection: Selections = {
graphSelections: segmentRanges.map((segmentRange) => {
@ -390,7 +390,7 @@ const runDeleteEdgeTreatmentTest = async (
// update artifact graph
await kclManager.executeAst({ ast })
const artifactGraph = engineCommandManager.artifactGraph
const artifactGraph = kclManager.artifactGraph
// define snippet range
const edgeTreatmentRange = topLevelRange(

View File

@ -118,7 +118,7 @@ export function modifyAstWithEdgeTreatmentAndTag(
const astResult = insertParametersIntoAst(clonedAst, parameters)
if (err(astResult)) return astResult
const artifactGraph = dependencies.engineCommandManager.artifactGraph
const artifactGraph = dependencies.kclManager.artifactGraph
// Step 1: modify ast with tags and group them by extrude nodes (bodies)
const extrudeToTagsMap: Map<

View File

@ -42,11 +42,11 @@ export async function applySubtractFromTargetOperatorSelections(
}
const orderedChildrenTarget = findAllChildrenAndOrderByPlaceInCode(
target.artifact,
dependencies.engineCommandManager.artifactGraph
dependencies.kclManager.artifactGraph
)
const orderedChildrenTool = findAllChildrenAndOrderByPlaceInCode(
tool.artifact,
dependencies.engineCommandManager.artifactGraph
dependencies.kclManager.artifactGraph
)
const lastVarTarget = getLastVariable(orderedChildrenTarget, ast)
@ -89,7 +89,7 @@ export async function applyUnionFromTargetOperatorSelections(
const orderedChildrenEach = artifacts.map((artifact) =>
findAllChildrenAndOrderByPlaceInCode(
artifact,
dependencies.engineCommandManager.artifactGraph
dependencies.kclManager.artifactGraph
)
)
@ -136,7 +136,7 @@ export async function applyIntersectFromTargetOperatorSelections(
const orderedChildrenEach = artifacts.map((artifact) =>
findAllChildrenAndOrderByPlaceInCode(
artifact,
dependencies.engineCommandManager.artifactGraph
dependencies.kclManager.artifactGraph
)
)

View File

@ -4,7 +4,6 @@ import { deleteFromSelection } from 'lang/modifyAst'
import {
codeManager,
editorManager,
engineCommandManager,
kclManager,
rustContext,
} from 'lib/singletons'
@ -25,7 +24,7 @@ export async function deleteSelectionPromise(
ast,
selection,
kclManager.variables,
engineCommandManager.artifactGraph,
kclManager.artifactGraph,
getFaceDetails
)
if (err(modifiedAst)) {

View File

@ -1,13 +1,8 @@
import {
ArtifactGraph,
defaultSourceRange,
ExecState,
SourceRange,
} from 'lang/wasm'
import { defaultSourceRange, SourceRange } from 'lang/wasm'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
import { Models } from '@kittycad/lib'
import { deferExecution, isOverlap, uuidv4 } from 'lib/utils'
import { BSON, Binary as BSONBinary } from 'bson'
import { uuidv4, binaryToUuid } from 'lib/utils'
import { BSON } from 'bson'
import {
Themes,
getThemeColorForEngine,
@ -22,8 +17,6 @@ import { KclManager } from 'lang/KclSingleton'
import { reportRejection } from 'lib/trap'
import { markOnce } from 'lib/performance'
import { MachineManager } from 'components/MachineManagerProvider'
import { buildArtifactIndex } from 'lib/artifactIndex'
import { ArtifactIndex } from 'lib/artifactIndex'
// TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 5_000
@ -1047,11 +1040,9 @@ class EngineConnection extends EventTarget {
})
.join('\n')
if (message.request_id) {
const artifactThatFailed =
this.engineCommandManager.artifactGraph.get(message.request_id)
console.error(
`Error in response to request ${message.request_id}:\n${errorsString}
failed cmd type was ${artifactThatFailed?.type}`
failed`
)
} else {
console.error(`Error from server:\n${errorsString}`)
@ -1355,8 +1346,7 @@ export enum EngineCommandManagerEvents {
*
* As commands are send their state is tracked in {@link pendingCommands} and clear as soon as we receive a response.
*
* Also all commands that are sent are kept track of in WASM artifactCommands and their responses are kept in {@link responseMap}
* Both of these data structures are used to process the {@link artifactGraph}.
* Also all commands that are sent are kept track of in WASM and their responses are kept in {@link responseMap}
*/
interface PendingMessage {
@ -1369,12 +1359,6 @@ interface PendingMessage {
isSceneCommand: boolean
}
export class EngineCommandManager extends EventTarget {
/**
* The artifactGraph is a client-side representation of the commands that have been sent
* see: src/lang/std/artifactGraph-README.md for a full explanation.
*/
artifactGraph: ArtifactGraph = new Map()
artifactIndex: ArtifactIndex = []
/**
* The pendingCommands object is a map of the commands that have been sent to the engine that are still waiting on a reply
*/
@ -1382,7 +1366,7 @@ export class EngineCommandManager extends EventTarget {
[commandId: string]: PendingMessage
} = {}
/**
* A map of the responses to the WASM artifactCommands, when processing the commands into the artifactGraph, this response map allow
* A map of the responses to the WASM, this response map allow
* us to look up the response by command id
*/
responseMap: ResponseMap = {}
@ -1878,7 +1862,7 @@ export class EngineCommandManager extends EventTarget {
registerCommandLogCallback(callback: (command: CommandLog[]) => void) {
this._commandLogCallBack = callback
}
sendSceneCommand(
async sendSceneCommand(
command: EngineCommand,
forceWebsocket = false
): Promise<Models['WebSocketResponse_type'] | null> {
@ -2032,13 +2016,6 @@ export class EngineCommandManager extends EventTarget {
return promise
}
deferredArtifactPopulated = deferExecution((a?: null) => {
this.modelingSend({ type: 'Artifact graph populated' })
}, 200)
deferredArtifactEmptied = deferExecution((a?: null) => {
this.modelingSend({ type: 'Artifact graph emptied' })
}, 200)
/**
* When an execution takes place we want to wait until we've got replies for all of the commands
* When this is done when we build the artifact map synchronously.
@ -2048,16 +2025,6 @@ export class EngineCommandManager extends EventTarget {
Object.values(this.pendingCommands).map((a) => a.promise)
)
}
updateArtifactGraph(execStateArtifactGraph: ExecState['artifactGraph']) {
this.artifactGraph = execStateArtifactGraph
this.artifactIndex = buildArtifactIndex(execStateArtifactGraph)
// TODO check if these still need to be deferred once e2e tests are working again.
if (this.artifactGraph.size) {
this.deferredArtifactEmptied(null)
} else {
this.deferredArtifactPopulated(null)
}
}
/**
* Reject all of the modeling pendingCommands created from sendModelingCommandFromWasm
@ -2121,24 +2088,6 @@ export class EngineCommandManager extends EventTarget {
},
}).catch(reportRejection)
}
// Some "objects" have the same source range, such as sketch_mode_start and start_path.
// So when passing a range, we need to also specify the command type
mapRangeToObjectId(
range: SourceRange,
commandTypeToTarget: string
): string | undefined {
for (const [artifactId, artifact] of this.artifactGraph) {
if (
'codeRef' in artifact &&
artifact.codeRef &&
isOverlap(range, artifact.codeRef.range)
) {
if (commandTypeToTarget === artifact.type) return artifactId
}
}
return undefined
}
}
function promiseFactory<T>() {
@ -2150,65 +2099,3 @@ function promiseFactory<T>() {
})
return { promise, resolve, reject }
}
/**
* Converts a binary buffer to a UUID string.
*
* @param buffer - The binary buffer containing the UUID bytes.
* @returns A string representation of the UUID in the format 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.
*/
function binaryToUuid(
binaryData: Buffer | Uint8Array | BSONBinary | string
): string {
if (typeof binaryData === 'string') {
return binaryData
}
let buffer: Uint8Array
// Handle MongoDB BSON Binary object
if (
binaryData &&
'_bsontype' in binaryData &&
binaryData._bsontype === 'Binary'
) {
// Extract the buffer from the BSON Binary object
buffer = binaryData.buffer
}
// Handle case where buffer property exists (some MongoDB drivers structure)
else if (binaryData && binaryData.buffer instanceof Uint8Array) {
buffer = binaryData.buffer
}
// Handle direct Buffer or Uint8Array
else if (binaryData instanceof Uint8Array || Buffer.isBuffer(binaryData)) {
buffer = binaryData
} else {
console.error(
'Invalid input type: expected MongoDB BSON Binary, Buffer, or Uint8Array'
)
return ''
}
// Ensure we have exactly 16 bytes (128 bits) for a UUID
if (buffer.length !== 16) {
// For debugging
console.log('Buffer length:', buffer.length)
console.log('Buffer content:', Array.from(buffer))
console.error('UUID must be exactly 16 bytes')
return ''
}
// Convert each byte to a hex string and pad with zeros if needed
const hexValues = Array.from(buffer).map((byte) =>
byte.toString(16).padStart(2, '0')
)
// Format into UUID structure (8-4-4-4-12 characters)
return [
hexValues.slice(0, 4).join(''),
hexValues.slice(4, 6).join(''),
hexValues.slice(6, 8).join(''),
hexValues.slice(8, 10).join(''),
hexValues.slice(10, 16).join(''),
].join('-')
}

View File

@ -1,5 +1,5 @@
import { Models } from '@kittycad/lib'
import { engineCommandManager } from 'lib/singletons'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { uuidv4 } from 'lib/utils'
import { CommandBarContext } from 'machines/commandBarMachine'
import { Selections } from 'lib/selections'
@ -188,7 +188,7 @@ export const shellValidator = async ({
// So we're listing out the sweeps as if they were solids and taking the first one, just like in Rust for Shell:
// https://github.com/KittyCAD/modeling-app/blob/e61fff115b9fa94aaace6307b1842cc15d41655e/src/wasm-lib/kcl/src/std/shell.rs#L237-L238
// TODO: This is one cheap way to make sketch-on-face supported now but will likely fail multiple solids
const object_id = engineCommandManager.artifactGraph
const object_id = kclManager.artifactGraph
.values()
.find((v) => v.type === 'sweep')?.pathId

View File

@ -200,17 +200,6 @@ export class CoreDumpManager {
// engine_command_manager
debugLog('CoreDump: engineCommandManager', this.engineCommandManager)
// artifact map - this.engineCommandManager.artifactGraph
if (this.engineCommandManager?.artifactGraph) {
debugLog(
'CoreDump: Engine Command Manager artifact map',
this.engineCommandManager.artifactGraph
)
clientState.engine_command_manager.artifact_map = structuredClone(
this.engineCommandManager.artifactGraph
)
}
// command logs - this.engineCommandManager.commandLogs
if (this.engineCommandManager?.commandLogs) {
debugLog(
@ -274,6 +263,21 @@ export class CoreDumpManager {
clientState.kcl_manager.ast = structuredClone(kclManager.ast)
}
// artifact map - this.kclManager.artifactGraph
debugLog(
'CoreDump: KCL Manager artifact map',
kclManager?.artifactGraph
)
if (kclManager.artifactGraph) {
debugLog(
'CoreDump: Engine Command Manager artifact map',
kclManager.artifactGraph
)
clientState.engine_command_manager.artifact_map = structuredClone(
kclManager.artifactGraph
)
}
// KCL Errors
debugLog('CoreDump: KCL Errors', kclManager?.kclErrors)
if (kclManager?.kclErrors) {

View File

@ -8,7 +8,7 @@ import {
getWallCodeRef,
} from 'lang/std/artifactGraph'
import { Operation } from '@rust/kcl-lib/bindings/Operation'
import { codeManager, engineCommandManager, kclManager } from './singletons'
import { codeManager, kclManager, rustContext } from './singletons'
import { err } from './trap'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { sourceRangeFromRust } from 'lang/wasm'
@ -20,7 +20,6 @@ import {
} from './commandBarConfigs/modelingCommandConfig'
import { isDefaultPlaneStr } from './planes'
import { Selection, Selections } from './selections'
import { rustContext } from './singletons'
import { KclExpression } from './commandTypes'
type ExecuteCommandEvent = CommandBarMachineEvent & {
@ -73,7 +72,7 @@ const prepareToEditExtrude: PrepareToEditCallback =
key: artifact.pathId,
types: ['path'],
},
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (
err(pathArtifact) ||
@ -86,7 +85,7 @@ const prepareToEditExtrude: PrepareToEditCallback =
key: pathArtifact.solid2dId,
types: ['solid2d'],
},
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(solid2DArtifact) || solid2DArtifact.type !== 'solid2d') {
return baseCommand
@ -157,7 +156,7 @@ const prepareToEditEdgeTreatment: PrepareToEditCallback = async ({
key: artifact.consumedEdgeId,
types: ['segment', 'sweepEdge'],
},
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(edgeArtifact)) {
return { reason: "Couldn't find edge artifact" }
@ -165,7 +164,7 @@ const prepareToEditEdgeTreatment: PrepareToEditCallback = async ({
let edgeCodeRef = getEdgeCutConsumedCodeRef(
artifact,
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(edgeCodeRef)) {
return { reason: "Couldn't find edge coderef" }
@ -272,16 +271,13 @@ const prepareToEditShell: PrepareToEditCallback =
// that we can query in another loop later
const sweepId = operation.unlabeledArg.value.value.artifactId
const candidates: Map<string, Selection> = new Map()
for (const artifact of engineCommandManager.artifactGraph.values()) {
for (const artifact of kclManager.artifactGraph.values()) {
if (
artifact.type === 'cap' &&
artifact.sweepId === sweepId &&
artifact.subType
) {
const codeRef = getCapCodeRef(
artifact,
engineCommandManager.artifactGraph
)
const codeRef = getCapCodeRef(artifact, kclManager.artifactGraph)
if (err(codeRef)) {
return baseCommand
}
@ -297,7 +293,7 @@ const prepareToEditShell: PrepareToEditCallback =
) {
const segArtifact = getArtifactOfTypes(
{ key: artifact.segId, types: ['segment'] },
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(segArtifact)) {
return baseCommand
@ -461,7 +457,7 @@ const prepareToEditSweep: PrepareToEditCallback = async ({
key: artifact.pathId,
types: ['path'],
},
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (
@ -477,7 +473,7 @@ const prepareToEditSweep: PrepareToEditCallback = async ({
key: pathArtifact.solid2dId,
types: ['solid2d'],
},
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(targetArtifact) || targetArtifact.type !== 'solid2d') {
@ -508,7 +504,7 @@ const prepareToEditSweep: PrepareToEditCallback = async ({
key: operation.labeledArgs.path.value.value.artifactId,
types: ['path'],
},
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(trajectoryPathArtifact) || trajectoryPathArtifact.type !== 'path') {
@ -520,7 +516,7 @@ const prepareToEditSweep: PrepareToEditCallback = async ({
key: trajectoryPathArtifact.segIds[0],
types: ['segment'],
},
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(trajectoryArtifact) || trajectoryArtifact.type !== 'segment') {
@ -607,7 +603,7 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
key: axisValue.artifact_id,
types: ['segment'],
},
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(artifact)) {
return { reason: "Couldn't find related edge artifact" }
@ -630,16 +626,13 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
key: axisValue.value,
types: ['sweepEdge'],
},
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(artifact)) {
return { reason: "Couldn't find related edge artifact" }
}
const codeRef = getSweepEdgeCodeRef(
artifact,
engineCommandManager.artifactGraph
)
const codeRef = getSweepEdgeCodeRef(artifact, kclManager.artifactGraph)
if (err(codeRef)) {
return { reason: "Couldn't find related edge code ref" }
}
@ -667,7 +660,7 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
}
const sweepId = operation.labeledArgs.cylinder.value.value.artifactId
const wallArtifact = [...engineCommandManager.artifactGraph.values()].find(
const wallArtifact = [...kclManager.artifactGraph.values()].find(
(p) => p.type === 'wall' && p.sweepId === sweepId
)
if (!wallArtifact || wallArtifact.type !== 'wall') {
@ -676,10 +669,7 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
}
}
const wallCodeRef = getWallCodeRef(
wallArtifact,
engineCommandManager.artifactGraph
)
const wallCodeRef = getWallCodeRef(wallArtifact, kclManager.artifactGraph)
if (err(wallCodeRef)) {
return {
reason: "Cylinder arg found doesn't point to a valid sweep code ref",

View File

@ -102,10 +102,10 @@ export async function getEventForSelectWithPoint({
}
}
let _artifact = engineCommandManager.artifactGraph.get(data.entity_id)
let _artifact = kclManager.artifactGraph.get(data.entity_id)
const codeRefs = getCodeRefsByArtifactId(
data.entity_id,
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (_artifact && codeRefs) {
return {
@ -140,15 +140,15 @@ export function getEventForSegmentSelection(
// id does not match up with the artifact graph when in sketch mode, because mock executions
// do not update the artifact graph, therefore we match up the pathToNode instead
// we can reliably use `type === 'segment'` since it's in sketch mode and we're concerned with segments
const segWithMatchingPathToNode__Id = [
...engineCommandManager.artifactGraph,
].find((entry) => {
return (
entry[1].type === 'segment' &&
JSON.stringify(entry[1].codeRef.pathToNode) ===
JSON.stringify(group?.userData?.pathToNode)
)
})?.[0]
const segWithMatchingPathToNode__Id = [...kclManager.artifactGraph].find(
(entry) => {
return (
entry[1].type === 'segment' &&
JSON.stringify(entry[1].codeRef.pathToNode) ===
JSON.stringify(group?.userData?.pathToNode)
)
}
)?.[0]
const id = segWithMatchingPathToNode__Id
@ -172,7 +172,7 @@ export function getEventForSegmentSelection(
}
}
if (!id || !group) return null
const artifact = engineCommandManager.artifactGraph.get(id)
const artifact = kclManager.artifactGraph.get(id)
if (!artifact) return null
const node = getNodeFromPath<Expr>(kclManager.ast, group.userData.pathToNode)
if (err(node)) return null
@ -208,10 +208,8 @@ export function handleSelectionBatch({
selectionToEngine.push({
id: artifact?.id,
range:
getCodeRefsByArtifactId(
artifact.id,
engineCommandManager.artifactGraph
)?.[0].range || defaultSourceRange(),
getCodeRefsByArtifactId(artifact.id, kclManager.artifactGraph)?.[0]
.range || defaultSourceRange(),
})
})
const engineEvents: Models['WebSocketRequest_type'][] =
@ -291,7 +289,7 @@ export function processCodeMirrorRanges({
const idBasedSelections: SelectionToEngine[] = codeToIdSelections(
codeBasedSelections,
artifactGraph,
engineCommandManager.artifactIndex
kclManager.artifactIndex
)
const selections: Selection[] = []
for (const { id, range } of idBasedSelections) {
@ -396,10 +394,7 @@ function resetAndSetEngineEntitySelectionCmds(
*/
export function isSketchPipe(selectionRanges: Selections) {
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast)) return false
return isCursorInSketchCommandRange(
engineCommandManager.artifactGraph,
selectionRanges
)
return isCursorInSketchCommandRange(kclManager.artifactGraph, selectionRanges)
}
// This accounts for non-geometry selections under "other"
@ -692,12 +687,9 @@ export function updateSelections(
if (err(nodeMeta)) return undefined
const node = nodeMeta.node
let artifact: Artifact | null = null
for (const [id, a] of engineCommandManager.artifactGraph) {
for (const [id, a] of kclManager.artifactGraph) {
if (previousSelection?.artifact?.type === a.type) {
const codeRefs = getCodeRefsByArtifactId(
id,
engineCommandManager.artifactGraph
)
const codeRefs = getCodeRefsByArtifactId(id, kclManager.artifactGraph)
if (!codeRefs) continue
if (
JSON.stringify(codeRefs[0].pathToNode) ===

View File

@ -4,6 +4,7 @@ import { v4 } from 'uuid'
import { isDesktop } from './isDesktop'
import { AnyMachineSnapshot } from 'xstate'
import { AsyncFn } from './types'
import { Binary as BSONBinary } from 'bson'
export const uuidv4 = v4
@ -406,3 +407,65 @@ export function isClockwise(points: [number, number][]): boolean {
// If sum is positive, the points are in clockwise order
return sum > 0
}
/**
* Converts a binary buffer to a UUID string.
*
* @param buffer - The binary buffer containing the UUID bytes.
* @returns A string representation of the UUID in the format 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.
*/
export function binaryToUuid(
binaryData: Buffer | Uint8Array | BSONBinary | string
): string {
if (typeof binaryData === 'string') {
return binaryData
}
let buffer: Uint8Array
// Handle MongoDB BSON Binary object
if (
binaryData &&
'_bsontype' in binaryData &&
binaryData._bsontype === 'Binary'
) {
// Extract the buffer from the BSON Binary object
buffer = binaryData.buffer
}
// Handle case where buffer property exists (some MongoDB drivers structure)
else if (binaryData && binaryData.buffer instanceof Uint8Array) {
buffer = binaryData.buffer
}
// Handle direct Buffer or Uint8Array
else if (binaryData instanceof Uint8Array || Buffer.isBuffer(binaryData)) {
buffer = binaryData
} else {
console.error(
'Invalid input type: expected MongoDB BSON Binary, Buffer, or Uint8Array'
)
return ''
}
// Ensure we have exactly 16 bytes (128 bits) for a UUID
if (buffer.length !== 16) {
// For debugging
console.log('Buffer length:', buffer.length)
console.log('Buffer content:', Array.from(buffer))
console.error('UUID must be exactly 16 bytes')
return ''
}
// Convert each byte to a hex string and pad with zeros if needed
const hexValues = Array.from(buffer).map((byte) =>
byte.toString(16).padStart(2, '0')
)
// Format into UUID structure (8-4-4-4-12 characters)
return [
hexValues.slice(0, 4).join(''),
hexValues.slice(4, 6).join(''),
hexValues.slice(6, 8).join(''),
hexValues.slice(8, 10).join(''),
hexValues.slice(10, 16).join(''),
].join('-')
}

View File

@ -5,7 +5,7 @@ import {
enterEditFlow,
EnterEditFlowProps,
} from 'lib/operations'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap'
import toast from 'react-hot-toast'
import { Operation } from '@rust/kcl-lib/bindings/Operation'
@ -275,7 +275,7 @@ export const featureTreeMachine = setup({
const artifact = context.targetSourceRange
? getArtifactFromRange(
context.targetSourceRange,
engineCommandManager.artifactGraph
kclManager.artifactGraph
) ?? undefined
: undefined
return {
@ -329,7 +329,7 @@ export const featureTreeMachine = setup({
const artifact = context.targetSourceRange
? getArtifactFromRange(
context.targetSourceRange,
engineCommandManager.artifactGraph
kclManager.artifactGraph
) ?? undefined
: undefined
return {
@ -383,7 +383,7 @@ export const featureTreeMachine = setup({
const artifact = context.targetSourceRange
? getArtifactFromRange(
context.targetSourceRange,
engineCommandManager.artifactGraph
kclManager.artifactGraph
) ?? undefined
: undefined
return {

View File

@ -766,7 +766,7 @@ export const modelingMachine = setup({
axisOrEdge,
axis,
edge,
engineCommandManager.artifactGraph,
kclManager.artifactGraph,
selection.graphSelections[0]?.artifact
)
if (trap(revolveSketchRes)) return
@ -1123,9 +1123,7 @@ export const modelingMachine = setup({
}),
're-eval nodePaths': assign(({ context: { sketchDetails } }) => {
if (!sketchDetails) return {}
const planeArtifact = [
...engineCommandManager.artifactGraph.values(),
].find(
const planeArtifact = [...kclManager.artifactGraph.values()].find(
(artifact) =>
artifact.type === 'plane' &&
stringifyPathToNode(artifact.codeRef.pathToNode) ===
@ -1134,7 +1132,7 @@ export const modelingMachine = setup({
if (planeArtifact?.type !== 'plane') return {}
const newPaths = getPathsFromPlaneArtifact(
planeArtifact,
engineCommandManager.artifactGraph,
kclManager.artifactGraph,
kclManager.ast
)
return {
@ -1794,7 +1792,7 @@ export const modelingMachine = setup({
node: ast,
pathToNode,
artifact: selection.graphSelections[0].artifact,
artifactGraph: engineCommandManager.artifactGraph,
artifactGraph: kclManager.artifactGraph,
distance:
'variableName' in distance
? distance.variableIdentifierAst
@ -1987,7 +1985,7 @@ export const modelingMachine = setup({
const extrudeLookupResult = getPathToExtrudeForSegmentSelection(
clonedAstForGetExtrude,
cylinder.graphSelections[0],
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(extrudeLookupResult)) {
return extrudeLookupResult
@ -2249,7 +2247,7 @@ export const modelingMachine = setup({
const extrudeLookupResult = getPathToExtrudeForSegmentSelection(
clonedAstForGetExtrude,
graphSelection,
engineCommandManager.artifactGraph
kclManager.artifactGraph
)
if (err(extrudeLookupResult)) {
return new Error(