ArtifactGraph reThink (PART 3) (#3140)

* adjust engine connection to opt out of webRTC connection

* refactor start and test setup

* add env to unit test

* spell config update

* fix beforeAll order bug

* initial integration of new artifact map with tests passing

* remove old artifact map and clean up

* graph artifact map

* have graph commited

* have graph commited

* remove bad file

* install playwright

* fmt

* commit permissions

* typo

* flesh out tests more

* Look at this (photo)Graph *in the voice of Nickelback*

* multi highlight

* redo image logic

* add in solid 2d data into artifactMap

* fix snapshots

* stabiles graph images

* Look at this (photo)Graph *in the voice of Nickelback*

* tweak tests

* rename blend to edgeCut

* Look at this (photo)Graph *in the voice of Nickelback*

* fix playw tests

* start of artifact map rename to graph

* rename file

* rename test

* rename clearup

* comments

* docs

* docs proof read

* few tweaks here and there

* typos

* delete get parent logic

* nit, combine if statements

* remove unused param

* fix silly test bug

* rename surfId to sufaceId

* rename types

* update comments

* add comment

* add extra check

* Look at this (photo)Graph *in the voice of Nickelback*

* pull out merge artifact function

* update comments

* fix test

* fmt

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
Kurt Hutten
2024-08-03 18:08:51 +10:00
committed by GitHub
parent 7bf6bc3048
commit e5bec2140e
37 changed files with 2285 additions and 587 deletions

View File

@ -29,6 +29,13 @@ import { Mesh, Object3D, Object3DEventMap } from 'three'
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
import { PathToNodeMap } from 'lang/std/sketchcombos'
import { err } from 'lib/trap'
import {
getArtifactOfTypes,
getArtifactsOfTypes,
getCapCodeRef,
getSolid2dCodeRef,
getWallCodeRef,
} from 'lang/std/artifactGraph'
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
@ -41,6 +48,7 @@ export type Selection = {
| 'line-end'
| 'line-mid'
| 'extrude-wall'
| 'solid2D'
| 'start-cap'
| 'end-cap'
| 'point'
@ -55,15 +63,12 @@ export type Selections = {
codeBasedSelections: Selection[]
}
export async function getEventForSelectWithPoint(
{
data,
}: Extract<
Models['OkModelingCmdResponse_type'],
{ type: 'select_with_point' }
>,
{ sketchEnginePathId }: { sketchEnginePathId?: string }
): Promise<ModelingMachineEvent | null> {
export async function getEventForSelectWithPoint({
data,
}: Extract<
Models['OkModelingCmdResponse_type'],
{ type: 'select_with_point' }
>): Promise<ModelingMachineEvent | null> {
if (!data?.entity_id) {
return {
type: 'Set selection',
@ -79,68 +84,64 @@ export async function getEventForSelectWithPoint(
},
}
}
let _artifact = engineCommandManager.artifactMap[data.entity_id]
if (!_artifact) {
// This logic for getting the parent id is for solid2ds as in edit mode it return the face id
// but we don't recognise that in the artifact map because we store the path id when the path is
// created, the solid2d is implicitly created with the close stdlib function
// there's plans to get the faceId back from the solid2d creation
// https://github.com/KittyCAD/engine/issues/2094
// at which point we can add it to the artifact map and remove this logic
const resp = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'entity_get_parent_id',
entity_id: data.entity_id,
},
cmd_id: uuidv4(),
})
const parentId =
resp?.success &&
resp?.resp?.type === 'modeling' &&
resp?.resp?.data?.modeling_response?.type === 'entity_get_parent_id'
? resp?.resp?.data?.modeling_response?.data?.entity_id
: ''
const parentArtifact = engineCommandManager.artifactMap[parentId]
if (parentArtifact) {
_artifact = parentArtifact
}
}
const sourceRange = _artifact?.range
if (_artifact) {
if (_artifact.type === 'extrudeCap')
return {
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: {
range: sourceRange,
type: _artifact?.cap === 'end' ? 'end-cap' : 'start-cap',
},
},
}
if (_artifact.type === 'extrudeWall')
return {
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: { range: sourceRange, type: 'extrude-wall' },
},
}
return {
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: { range: sourceRange, type: 'default' },
},
}
} else {
// if we don't recognise the entity, select nothing
let _artifact = engineCommandManager.artifactGraph.get(data.entity_id)
if (!_artifact)
return {
type: 'Set selection',
data: { selectionType: 'singleCodeCursor' },
}
if (_artifact.type === 'solid2D') {
const codeRef = getSolid2dCodeRef(
_artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return null
return {
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: { range: codeRef.range, type: 'solid2D' },
},
}
}
if (_artifact.type === 'cap') {
const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
if (err(codeRef)) return null
return {
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: {
range: codeRef.range,
type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap',
},
},
}
}
if (_artifact.type === 'wall') {
const codeRef = getWallCodeRef(
_artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return null
return {
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: { range: codeRef.range, type: 'extrude-wall' },
},
}
}
if (_artifact.type === 'segment' || _artifact.type === 'path') {
return {
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: { range: _artifact.codeRef.range, type: 'default' },
},
}
}
return null
}
export function getEventForSegmentSelection(
@ -347,7 +348,7 @@ function resetAndSetEngineEntitySelectionCmds(
export function isSketchPipe(selectionRanges: Selections) {
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast)) return false
return isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
engineCommandManager.artifactGraph,
selectionRanges
)
}
@ -499,11 +500,10 @@ function codeToIdSelections(
return codeBasedSelections
.flatMap(({ type, range, ...rest }): null | SelectionToEngine[] => {
// TODO #868: loops over all artifacts will become inefficient at a large scale
const entriesWithOverlap = Object.entries(
engineCommandManager.artifactMap || {}
)
const overlappingEntries = Array.from(engineCommandManager.artifactGraph)
.map(([id, artifact]) => {
return artifact.range && isOverlap(artifact.range, range)
if (!('codeRef' in artifact)) return false
return isOverlap(artifact.codeRef.range, range)
? {
artifact,
selection: { type, range, ...rest },
@ -512,31 +512,73 @@ function codeToIdSelections(
: false
})
.filter(Boolean)
/** TODO refactor
* selections in our app is a sourceRange plus some metadata
* The metadata is just a union type string of different types of artifacts or 3d features 'extrude-wall' 'segment' etc
* Because the source range is not enough to figure out what the user selected, so here we're using filtering through all the artifacts
* to find something that matches both the source range and the metadata.
*
* What we should migrate to is just storing what the user selected by what it matched in the artifactGraph it will simply the below a lot.
*
* In the case of a user moving the cursor them, we will still need to figure out what artifact from the graph matches best, but we will just need sane defaults
* and most of the time we can expect the user to be clicking in the 3d scene instead.
*/
let bestCandidate
entriesWithOverlap.forEach((entry) => {
overlappingEntries.forEach((entry) => {
if (!entry) return
if (type === 'default' && entry.artifact.type === 'segment') {
bestCandidate = entry
return
}
if (
type === 'start-cap' &&
entry.artifact.type === 'extrudeCap' &&
entry?.artifact?.cap === 'start'
) {
bestCandidate = entry
if (type === 'solid2D' && entry.artifact.type === 'path') {
const solid = engineCommandManager.artifactGraph.get(
entry.artifact.solid2dId || ''
)
if (solid?.type !== 'solid2D') return
bestCandidate = {
artifact: solid,
selection: { type, range, ...rest },
id: entry.artifact.solid2dId,
}
}
if (type === 'extrude-wall' && entry.artifact.type === 'segment') {
const wall = engineCommandManager.artifactGraph.get(
entry.artifact.surfaceId
)
if (wall?.type !== 'wall') return
bestCandidate = {
artifact: wall,
selection: { type, range, ...rest },
id: entry.artifact.surfaceId,
}
return
}
if (
type === 'end-cap' &&
entry.artifact.type === 'extrudeCap' &&
entry?.artifact?.cap === 'end'
(type === 'end-cap' || type === 'start-cap') &&
entry.artifact.type === 'path'
) {
bestCandidate = entry
return
}
if (type === 'extrude-wall' && entry.artifact.type === 'extrudeWall') {
bestCandidate = entry
const extrusion = getArtifactOfTypes(
{
key: entry.artifact.extrusionId,
types: ['extrusion'],
},
engineCommandManager.artifactGraph
)
if (err(extrusion)) return
const caps = getArtifactsOfTypes(
{ keys: extrusion.surfaceIds, types: ['cap'] },
engineCommandManager.artifactGraph
)
const cap = [...caps].find(
([_, cap]) => cap.subType === (type === 'end-cap' ? 'end' : 'start')
)
if (!cap) return
bestCandidate = {
artifact: entry.artifact,
selection: { type, range, ...rest },
id: cap[0],
}
return
}
})