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

View File

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

View File

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

View File

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

View File

@ -566,7 +566,7 @@ export const ModelingMachineProvider = ({
// See if the selection is "close enough" to be coerced to the plane later // See if the selection is "close enough" to be coerced to the plane later
const maybePlane = getPlaneFromArtifact( const maybePlane = getPlaneFromArtifact(
selectionRanges.graphSelections[0].artifact, selectionRanges.graphSelections[0].artifact,
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
return !err(maybePlane) return !err(maybePlane)
} }
@ -579,7 +579,7 @@ export const ModelingMachineProvider = ({
return false return false
} }
return !!isCursorInSketchCommandRange( return !!isCursorInSketchCommandRange(
engineCommandManager.artifactGraph, kclManager.artifactGraph,
selectionRanges selectionRanges
) )
}, },
@ -841,7 +841,7 @@ export const ModelingMachineProvider = ({
const artifact = selectionRanges.graphSelections[0].artifact const artifact = selectionRanges.graphSelections[0].artifact
const plane = getPlaneFromArtifact( const plane = getPlaneFromArtifact(
artifact, artifact,
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if (err(plane)) return Promise.reject(plane) 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 // 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 || '' info?.sketchDetails?.faceId || ''
) )
const sketchArtifact = const sketchArtifact = kclManager.artifactGraph.get(mainPath)
engineCommandManager.artifactGraph.get(mainPath)
if (sketchArtifact?.type !== 'path') if (sketchArtifact?.type !== 'path')
return Promise.reject(new Error('No sketch artifact')) return Promise.reject(new Error('No sketch artifact'))
const sketchPaths = getPathsFromArtifact({ const sketchPaths = getPathsFromArtifact({
artifact: engineCommandManager.artifactGraph.get(plane.id), artifact: kclManager.artifactGraph.get(plane.id),
sketchPathToNode: sketchArtifact?.codeRef?.pathToNode, sketchPathToNode: sketchArtifact?.codeRef?.pathToNode,
artifactGraph: engineCommandManager.artifactGraph, artifactGraph: kclManager.artifactGraph,
ast: kclManager.ast, ast: kclManager.ast,
}) })
if (err(sketchPaths)) return Promise.reject(sketchPaths) if (err(sketchPaths)) return Promise.reject(sketchPaths)
@ -1743,7 +1742,7 @@ export const ModelingMachineProvider = ({
prompt: input.prompt, prompt: input.prompt,
selections: input.selection, selections: input.selection,
token, token,
artifactGraph: engineCommandManager.artifactGraph, artifactGraph: kclManager.artifactGraph,
projectName: context.project.name, projectName: context.project.name,
}) })
}), }),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,10 +5,12 @@ import {
compilationErrorsToDiagnostics, compilationErrorsToDiagnostics,
kclErrorsToDiagnostics, kclErrorsToDiagnostics,
} from './errors' } from './errors'
import { uuidv4 } from 'lib/utils' import { uuidv4, isOverlap, deferExecution } from 'lib/utils'
import { EngineCommandManager } from './std/engineConnection' import { EngineCommandManager } from './std/engineConnection'
import { err, reportRejection } from 'lib/trap' import { err, reportRejection } from 'lib/trap'
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants' import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import { buildArtifactIndex } from 'lib/artifactIndex'
import { ArtifactIndex } from 'lib/artifactIndex'
import { import {
emptyExecState, emptyExecState,
@ -24,6 +26,7 @@ import {
SourceRange, SourceRange,
topLevelRange, topLevelRange,
VariableMap, VariableMap,
ArtifactGraph,
} from 'lang/wasm' } from 'lang/wasm'
import { getNodeFromPath, getSettingsAnnotation } from './queryAst' import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
import { import {
@ -53,6 +56,14 @@ interface ExecuteArgs {
} }
export class KclManager { 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> = { private _ast: Node<Program> = {
body: [], body: [],
shebang: null, 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> { async safeParse(code: string): Promise<Node<Program> | null> {
const result = parse(code) const result = parse(code)
this.diagnostics = [] this.diagnostics = []
@ -330,6 +382,7 @@ export class KclManager {
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) { } catch (e) {
console.error(e)
this.wasmInitFailed = true 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 // Do not send send scene commands if the program was interrupted, go to clean up
if (!isInterrupted) { if (!isInterrupted) {
this.addDiagnostics(await lintAst({ ast: ast })) this.addDiagnostics(await lintAst({ ast: ast }))
setSelectionFilterToDefault(this.engineCommandManager) await setSelectionFilterToDefault(this.engineCommandManager)
if (args.zoomToFit) { if (args.zoomToFit) {
let zoomObjectId: string | undefined = '' let zoomObjectId: string | undefined = ''
if (args.zoomOnRangeAndType) { if (args.zoomOnRangeAndType) {
zoomObjectId = this.engineCommandManager?.mapRangeToObjectId( zoomObjectId = this.mapRangeToObjectId(
args.zoomOnRangeAndType.range, args.zoomOnRangeAndType.range,
args.zoomOnRangeAndType.type args.zoomOnRangeAndType.type
) )
@ -429,7 +482,7 @@ export class KclManager {
} }
this.ast = { ...ast } this.ast = { ...ast }
// updateArtifactGraph relies on updated executeState/variables // updateArtifactGraph relies on updated executeState/variables
this.engineCommandManager.updateArtifactGraph(execState.artifactGraph) await this.updateArtifactGraph(execState.artifactGraph)
this._executeCallback() this._executeCallback()
if (!isInterrupted) { if (!isInterrupted) {
sceneInfra.modelingSend({ type: 'code edit during sketch' }) sceneInfra.modelingSend({ type: 'code edit during sketch' })

View File

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

View File

@ -118,7 +118,7 @@ export function modifyAstWithEdgeTreatmentAndTag(
const astResult = insertParametersIntoAst(clonedAst, parameters) const astResult = insertParametersIntoAst(clonedAst, parameters)
if (err(astResult)) return astResult 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) // Step 1: modify ast with tags and group them by extrude nodes (bodies)
const extrudeToTagsMap: Map< const extrudeToTagsMap: Map<

View File

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

View File

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

View File

@ -1,13 +1,8 @@
import { import { defaultSourceRange, SourceRange } from 'lang/wasm'
ArtifactGraph,
defaultSourceRange,
ExecState,
SourceRange,
} from 'lang/wasm'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env' import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { deferExecution, isOverlap, uuidv4 } from 'lib/utils' import { uuidv4, binaryToUuid } from 'lib/utils'
import { BSON, Binary as BSONBinary } from 'bson' import { BSON } from 'bson'
import { import {
Themes, Themes,
getThemeColorForEngine, getThemeColorForEngine,
@ -22,8 +17,6 @@ import { KclManager } from 'lang/KclSingleton'
import { reportRejection } from 'lib/trap' import { reportRejection } from 'lib/trap'
import { markOnce } from 'lib/performance' import { markOnce } from 'lib/performance'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import { buildArtifactIndex } from 'lib/artifactIndex'
import { ArtifactIndex } from 'lib/artifactIndex'
// TODO(paultag): This ought to be tweakable. // TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 5_000 const pingIntervalMs = 5_000
@ -1047,11 +1040,9 @@ class EngineConnection extends EventTarget {
}) })
.join('\n') .join('\n')
if (message.request_id) { if (message.request_id) {
const artifactThatFailed =
this.engineCommandManager.artifactGraph.get(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?.type}` failed`
) )
} else { } else {
console.error(`Error from server:\n${errorsString}`) 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. * 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} * Also all commands that are sent are kept track of in WASM and their responses are kept in {@link responseMap}
* Both of these data structures are used to process the {@link artifactGraph}.
*/ */
interface PendingMessage { interface PendingMessage {
@ -1369,12 +1359,6 @@ interface PendingMessage {
isSceneCommand: boolean isSceneCommand: boolean
} }
export class EngineCommandManager extends EventTarget { 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 * 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 [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 * us to look up the response by command id
*/ */
responseMap: ResponseMap = {} responseMap: ResponseMap = {}
@ -1878,7 +1862,7 @@ export class EngineCommandManager extends EventTarget {
registerCommandLogCallback(callback: (command: CommandLog[]) => void) { registerCommandLogCallback(callback: (command: CommandLog[]) => void) {
this._commandLogCallBack = callback this._commandLogCallBack = callback
} }
sendSceneCommand( async sendSceneCommand(
command: EngineCommand, command: EngineCommand,
forceWebsocket = false forceWebsocket = false
): Promise<Models['WebSocketResponse_type'] | null> { ): Promise<Models['WebSocketResponse_type'] | null> {
@ -2032,13 +2016,6 @@ export class EngineCommandManager extends EventTarget {
return promise 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 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. * 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) 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 * Reject all of the modeling pendingCommands created from sendModelingCommandFromWasm
@ -2121,24 +2088,6 @@ export class EngineCommandManager extends EventTarget {
}, },
}).catch(reportRejection) }).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>() { function promiseFactory<T>() {
@ -2150,65 +2099,3 @@ function promiseFactory<T>() {
}) })
return { promise, resolve, reject } 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 { Models } from '@kittycad/lib'
import { engineCommandManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
import { uuidv4 } from 'lib/utils' import { uuidv4 } from 'lib/utils'
import { CommandBarContext } from 'machines/commandBarMachine' import { CommandBarContext } from 'machines/commandBarMachine'
import { Selections } from 'lib/selections' 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: // 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 // 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 // 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() .values()
.find((v) => v.type === 'sweep')?.pathId .find((v) => v.type === 'sweep')?.pathId

View File

@ -200,17 +200,6 @@ export class CoreDumpManager {
// engine_command_manager // engine_command_manager
debugLog('CoreDump: engineCommandManager', this.engineCommandManager) 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 // command logs - this.engineCommandManager.commandLogs
if (this.engineCommandManager?.commandLogs) { if (this.engineCommandManager?.commandLogs) {
debugLog( debugLog(
@ -274,6 +263,21 @@ export class CoreDumpManager {
clientState.kcl_manager.ast = structuredClone(kclManager.ast) 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 // KCL Errors
debugLog('CoreDump: KCL Errors', kclManager?.kclErrors) debugLog('CoreDump: KCL Errors', kclManager?.kclErrors)
if (kclManager?.kclErrors) { if (kclManager?.kclErrors) {

View File

@ -8,7 +8,7 @@ import {
getWallCodeRef, getWallCodeRef,
} from 'lang/std/artifactGraph' } from 'lang/std/artifactGraph'
import { Operation } from '@rust/kcl-lib/bindings/Operation' 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 { err } from './trap'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils' import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { sourceRangeFromRust } from 'lang/wasm' import { sourceRangeFromRust } from 'lang/wasm'
@ -20,7 +20,6 @@ import {
} from './commandBarConfigs/modelingCommandConfig' } from './commandBarConfigs/modelingCommandConfig'
import { isDefaultPlaneStr } from './planes' import { isDefaultPlaneStr } from './planes'
import { Selection, Selections } from './selections' import { Selection, Selections } from './selections'
import { rustContext } from './singletons'
import { KclExpression } from './commandTypes' import { KclExpression } from './commandTypes'
type ExecuteCommandEvent = CommandBarMachineEvent & { type ExecuteCommandEvent = CommandBarMachineEvent & {
@ -73,7 +72,7 @@ const prepareToEditExtrude: PrepareToEditCallback =
key: artifact.pathId, key: artifact.pathId,
types: ['path'], types: ['path'],
}, },
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if ( if (
err(pathArtifact) || err(pathArtifact) ||
@ -86,7 +85,7 @@ const prepareToEditExtrude: PrepareToEditCallback =
key: pathArtifact.solid2dId, key: pathArtifact.solid2dId,
types: ['solid2d'], types: ['solid2d'],
}, },
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if (err(solid2DArtifact) || solid2DArtifact.type !== 'solid2d') { if (err(solid2DArtifact) || solid2DArtifact.type !== 'solid2d') {
return baseCommand return baseCommand
@ -157,7 +156,7 @@ const prepareToEditEdgeTreatment: PrepareToEditCallback = async ({
key: artifact.consumedEdgeId, key: artifact.consumedEdgeId,
types: ['segment', 'sweepEdge'], types: ['segment', 'sweepEdge'],
}, },
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if (err(edgeArtifact)) { if (err(edgeArtifact)) {
return { reason: "Couldn't find edge artifact" } return { reason: "Couldn't find edge artifact" }
@ -165,7 +164,7 @@ const prepareToEditEdgeTreatment: PrepareToEditCallback = async ({
let edgeCodeRef = getEdgeCutConsumedCodeRef( let edgeCodeRef = getEdgeCutConsumedCodeRef(
artifact, artifact,
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if (err(edgeCodeRef)) { if (err(edgeCodeRef)) {
return { reason: "Couldn't find edge coderef" } return { reason: "Couldn't find edge coderef" }
@ -272,16 +271,13 @@ const prepareToEditShell: PrepareToEditCallback =
// that we can query in another loop later // that we can query in another loop later
const sweepId = operation.unlabeledArg.value.value.artifactId const sweepId = operation.unlabeledArg.value.value.artifactId
const candidates: Map<string, Selection> = new Map() const candidates: Map<string, Selection> = new Map()
for (const artifact of engineCommandManager.artifactGraph.values()) { for (const artifact of kclManager.artifactGraph.values()) {
if ( if (
artifact.type === 'cap' && artifact.type === 'cap' &&
artifact.sweepId === sweepId && artifact.sweepId === sweepId &&
artifact.subType artifact.subType
) { ) {
const codeRef = getCapCodeRef( const codeRef = getCapCodeRef(artifact, kclManager.artifactGraph)
artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) { if (err(codeRef)) {
return baseCommand return baseCommand
} }
@ -297,7 +293,7 @@ const prepareToEditShell: PrepareToEditCallback =
) { ) {
const segArtifact = getArtifactOfTypes( const segArtifact = getArtifactOfTypes(
{ key: artifact.segId, types: ['segment'] }, { key: artifact.segId, types: ['segment'] },
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if (err(segArtifact)) { if (err(segArtifact)) {
return baseCommand return baseCommand
@ -461,7 +457,7 @@ const prepareToEditSweep: PrepareToEditCallback = async ({
key: artifact.pathId, key: artifact.pathId,
types: ['path'], types: ['path'],
}, },
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if ( if (
@ -477,7 +473,7 @@ const prepareToEditSweep: PrepareToEditCallback = async ({
key: pathArtifact.solid2dId, key: pathArtifact.solid2dId,
types: ['solid2d'], types: ['solid2d'],
}, },
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if (err(targetArtifact) || targetArtifact.type !== 'solid2d') { if (err(targetArtifact) || targetArtifact.type !== 'solid2d') {
@ -508,7 +504,7 @@ const prepareToEditSweep: PrepareToEditCallback = async ({
key: operation.labeledArgs.path.value.value.artifactId, key: operation.labeledArgs.path.value.value.artifactId,
types: ['path'], types: ['path'],
}, },
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if (err(trajectoryPathArtifact) || trajectoryPathArtifact.type !== 'path') { if (err(trajectoryPathArtifact) || trajectoryPathArtifact.type !== 'path') {
@ -520,7 +516,7 @@ const prepareToEditSweep: PrepareToEditCallback = async ({
key: trajectoryPathArtifact.segIds[0], key: trajectoryPathArtifact.segIds[0],
types: ['segment'], types: ['segment'],
}, },
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if (err(trajectoryArtifact) || trajectoryArtifact.type !== 'segment') { if (err(trajectoryArtifact) || trajectoryArtifact.type !== 'segment') {
@ -607,7 +603,7 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
key: axisValue.artifact_id, key: axisValue.artifact_id,
types: ['segment'], types: ['segment'],
}, },
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if (err(artifact)) { if (err(artifact)) {
return { reason: "Couldn't find related edge artifact" } return { reason: "Couldn't find related edge artifact" }
@ -630,16 +626,13 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
key: axisValue.value, key: axisValue.value,
types: ['sweepEdge'], types: ['sweepEdge'],
}, },
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if (err(artifact)) { if (err(artifact)) {
return { reason: "Couldn't find related edge artifact" } return { reason: "Couldn't find related edge artifact" }
} }
const codeRef = getSweepEdgeCodeRef( const codeRef = getSweepEdgeCodeRef(artifact, kclManager.artifactGraph)
artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) { if (err(codeRef)) {
return { reason: "Couldn't find related edge code ref" } 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 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 (p) => p.type === 'wall' && p.sweepId === sweepId
) )
if (!wallArtifact || wallArtifact.type !== 'wall') { if (!wallArtifact || wallArtifact.type !== 'wall') {
@ -676,10 +669,7 @@ const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
} }
} }
const wallCodeRef = getWallCodeRef( const wallCodeRef = getWallCodeRef(wallArtifact, kclManager.artifactGraph)
wallArtifact,
engineCommandManager.artifactGraph
)
if (err(wallCodeRef)) { if (err(wallCodeRef)) {
return { return {
reason: "Cylinder arg found doesn't point to a valid sweep code ref", 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( const codeRefs = getCodeRefsByArtifactId(
data.entity_id, data.entity_id,
engineCommandManager.artifactGraph kclManager.artifactGraph
) )
if (_artifact && codeRefs) { if (_artifact && codeRefs) {
return { return {
@ -140,15 +140,15 @@ export function getEventForSegmentSelection(
// id does not match up with the artifact graph when in sketch mode, because mock executions // 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 // 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 // we can reliably use `type === 'segment'` since it's in sketch mode and we're concerned with segments
const segWithMatchingPathToNode__Id = [ const segWithMatchingPathToNode__Id = [...kclManager.artifactGraph].find(
...engineCommandManager.artifactGraph, (entry) => {
].find((entry) => { return (
return ( entry[1].type === 'segment' &&
entry[1].type === 'segment' && JSON.stringify(entry[1].codeRef.pathToNode) ===
JSON.stringify(entry[1].codeRef.pathToNode) === JSON.stringify(group?.userData?.pathToNode)
JSON.stringify(group?.userData?.pathToNode) )
) }
})?.[0] )?.[0]
const id = segWithMatchingPathToNode__Id const id = segWithMatchingPathToNode__Id
@ -172,7 +172,7 @@ export function getEventForSegmentSelection(
} }
} }
if (!id || !group) return null if (!id || !group) return null
const artifact = engineCommandManager.artifactGraph.get(id) const artifact = kclManager.artifactGraph.get(id)
if (!artifact) return null if (!artifact) return null
const node = getNodeFromPath<Expr>(kclManager.ast, group.userData.pathToNode) const node = getNodeFromPath<Expr>(kclManager.ast, group.userData.pathToNode)
if (err(node)) return null if (err(node)) return null
@ -208,10 +208,8 @@ export function handleSelectionBatch({
selectionToEngine.push({ selectionToEngine.push({
id: artifact?.id, id: artifact?.id,
range: range:
getCodeRefsByArtifactId( getCodeRefsByArtifactId(artifact.id, kclManager.artifactGraph)?.[0]
artifact.id, .range || defaultSourceRange(),
engineCommandManager.artifactGraph
)?.[0].range || defaultSourceRange(),
}) })
}) })
const engineEvents: Models['WebSocketRequest_type'][] = const engineEvents: Models['WebSocketRequest_type'][] =
@ -291,7 +289,7 @@ export function processCodeMirrorRanges({
const idBasedSelections: SelectionToEngine[] = codeToIdSelections( const idBasedSelections: SelectionToEngine[] = codeToIdSelections(
codeBasedSelections, codeBasedSelections,
artifactGraph, artifactGraph,
engineCommandManager.artifactIndex kclManager.artifactIndex
) )
const selections: Selection[] = [] const selections: Selection[] = []
for (const { id, range } of idBasedSelections) { for (const { id, range } of idBasedSelections) {
@ -396,10 +394,7 @@ function resetAndSetEngineEntitySelectionCmds(
*/ */
export function isSketchPipe(selectionRanges: Selections) { export function isSketchPipe(selectionRanges: Selections) {
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast)) return false if (!isSingleCursorInPipe(selectionRanges, kclManager.ast)) return false
return isCursorInSketchCommandRange( return isCursorInSketchCommandRange(kclManager.artifactGraph, selectionRanges)
engineCommandManager.artifactGraph,
selectionRanges
)
} }
// This accounts for non-geometry selections under "other" // This accounts for non-geometry selections under "other"
@ -692,12 +687,9 @@ export function updateSelections(
if (err(nodeMeta)) return undefined if (err(nodeMeta)) return undefined
const node = nodeMeta.node const node = nodeMeta.node
let artifact: Artifact | null = null 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) { if (previousSelection?.artifact?.type === a.type) {
const codeRefs = getCodeRefsByArtifactId( const codeRefs = getCodeRefsByArtifactId(id, kclManager.artifactGraph)
id,
engineCommandManager.artifactGraph
)
if (!codeRefs) continue if (!codeRefs) continue
if ( if (
JSON.stringify(codeRefs[0].pathToNode) === JSON.stringify(codeRefs[0].pathToNode) ===

View File

@ -4,6 +4,7 @@ import { v4 } from 'uuid'
import { isDesktop } from './isDesktop' import { isDesktop } from './isDesktop'
import { AnyMachineSnapshot } from 'xstate' import { AnyMachineSnapshot } from 'xstate'
import { AsyncFn } from './types' import { AsyncFn } from './types'
import { Binary as BSONBinary } from 'bson'
export const uuidv4 = v4 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 // If sum is positive, the points are in clockwise order
return sum > 0 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, enterEditFlow,
EnterEditFlowProps, EnterEditFlowProps,
} from 'lib/operations' } from 'lib/operations'
import { engineCommandManager, kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { Operation } from '@rust/kcl-lib/bindings/Operation' import { Operation } from '@rust/kcl-lib/bindings/Operation'
@ -275,7 +275,7 @@ export const featureTreeMachine = setup({
const artifact = context.targetSourceRange const artifact = context.targetSourceRange
? getArtifactFromRange( ? getArtifactFromRange(
context.targetSourceRange, context.targetSourceRange,
engineCommandManager.artifactGraph kclManager.artifactGraph
) ?? undefined ) ?? undefined
: undefined : undefined
return { return {
@ -329,7 +329,7 @@ export const featureTreeMachine = setup({
const artifact = context.targetSourceRange const artifact = context.targetSourceRange
? getArtifactFromRange( ? getArtifactFromRange(
context.targetSourceRange, context.targetSourceRange,
engineCommandManager.artifactGraph kclManager.artifactGraph
) ?? undefined ) ?? undefined
: undefined : undefined
return { return {
@ -383,7 +383,7 @@ export const featureTreeMachine = setup({
const artifact = context.targetSourceRange const artifact = context.targetSourceRange
? getArtifactFromRange( ? getArtifactFromRange(
context.targetSourceRange, context.targetSourceRange,
engineCommandManager.artifactGraph kclManager.artifactGraph
) ?? undefined ) ?? undefined
: undefined : undefined
return { return {

View File

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