Selections Refactor (#4381)
* selection stuff
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)
* trigger CI
* fix bugs
* some edge cut stuff
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)
* trigger CI
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)
* fix sketch mode issues
* fix more tests, selection in sketch related
* more test fixing
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)
* Trigger ci
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)
* Trigger ci
* more sketch mode selection fixes
* fix unit tests
* rename function
* remove .only
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* lint
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* fix bad pathToNode issue
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)
* fix sketch on face
* migrate a more selections types
* migrate a more selections types
* fix code selection of fillets
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* fix bad path to node, looks like a race
* migrate a more selections types
* migrate a more selections types
* fix cmd bar selections
* fix cmd bar selections
* fix display issues
* migrate a more selections types
* Revert "migrate a more selections types"
This reverts commit 0d0e453bbb.
* migrate a more selections types
* clean up1
* clean up 2
* fix types after main merge
* review tweaks
* fix wall selection bug
* Update src/lang/std/engineConnection.ts
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
* add franks TODO comment
* fix type after main merge, plus a touch of clean up
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
			
			
| 
		 Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB  | 
| 
		 Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB  | 
| 
		 Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB  | 
| 
		 Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB  | 
| 
		 Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB  | 
@ -38,9 +38,8 @@ export function Toolbar({
 | 
			
		||||
    '!border-transparent hover:!border-chalkboard-20 dark:enabled:hover:!border-primary pressed:!border-primary ui-open:!border-primary'
 | 
			
		||||
 | 
			
		||||
  const sketchPathId = useMemo(() => {
 | 
			
		||||
    if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
 | 
			
		||||
    if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast))
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
    return isCursorInSketchCommandRange(
 | 
			
		||||
      engineCommandManager.artifactGraph,
 | 
			
		||||
      context.selectionRanges
 | 
			
		||||
 | 
			
		||||
@ -578,10 +578,8 @@ export class SceneEntities {
 | 
			
		||||
        draftExpressionsIndices &&
 | 
			
		||||
        index <= draftExpressionsIndices.end &&
 | 
			
		||||
        index >= draftExpressionsIndices.start
 | 
			
		||||
      const isSelected = selectionRanges?.codeBasedSelections.some(
 | 
			
		||||
        (selection) => {
 | 
			
		||||
          return isOverlap(selection.range, segment.__geoMeta.sourceRange)
 | 
			
		||||
        }
 | 
			
		||||
      const isSelected = selectionRanges?.graphSelections.some((selection) =>
 | 
			
		||||
        isOverlap(selection?.codeRef?.range, segment.__geoMeta.sourceRange)
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      let seg: Group
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,17 @@
 | 
			
		||||
import { useModelingContext } from 'hooks/useModelingContext'
 | 
			
		||||
import { editorManager, kclManager } from 'lib/singletons'
 | 
			
		||||
import { editorManager, engineCommandManager, kclManager } from 'lib/singletons'
 | 
			
		||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
 | 
			
		||||
import { useEffect, useRef, useState } from 'react'
 | 
			
		||||
import { trap } from 'lib/trap'
 | 
			
		||||
import { codeToIdSelections } from 'lib/selections'
 | 
			
		||||
import { codeRefFromRange } from 'lang/std/artifactGraph'
 | 
			
		||||
 | 
			
		||||
export function AstExplorer() {
 | 
			
		||||
  const { context } = useModelingContext()
 | 
			
		||||
  const pathToNode = getNodePathFromSourceRange(
 | 
			
		||||
    // TODO maybe need to have callback to make sure it stays in sync
 | 
			
		||||
    kclManager.ast,
 | 
			
		||||
    context.selectionRanges.codeBasedSelections?.[0]?.range
 | 
			
		||||
    context.selectionRanges.graphSelections?.[0]?.codeRef?.range
 | 
			
		||||
  )
 | 
			
		||||
  const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
 | 
			
		||||
 | 
			
		||||
@ -121,13 +123,21 @@ function DisplayObj({
 | 
			
		||||
        editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
 | 
			
		||||
      }}
 | 
			
		||||
      onClick={(e) => {
 | 
			
		||||
        const range: [number, number] = [obj?.start || 0, obj.end || 0]
 | 
			
		||||
        const idInfo = codeToIdSelections([
 | 
			
		||||
          { codeRef: codeRefFromRange(range, kclManager.ast) },
 | 
			
		||||
        ])[0]
 | 
			
		||||
        const artifact = engineCommandManager.artifactGraph.get(
 | 
			
		||||
          idInfo?.id || ''
 | 
			
		||||
        )
 | 
			
		||||
        if (!artifact) return
 | 
			
		||||
        send({
 | 
			
		||||
          type: 'Set selection',
 | 
			
		||||
          data: {
 | 
			
		||||
            selectionType: 'singleCodeCursor',
 | 
			
		||||
            selection: {
 | 
			
		||||
              type: 'default',
 | 
			
		||||
              range: [obj?.start || 0, obj.end || 0],
 | 
			
		||||
              artifact: artifact,
 | 
			
		||||
              codeRef: codeRefFromRange(range, kclManager.ast),
 | 
			
		||||
            },
 | 
			
		||||
          },
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
@ -96,7 +96,8 @@ export function useCalc({
 | 
			
		||||
} {
 | 
			
		||||
  const { programMemory } = useKclContext()
 | 
			
		||||
  const { context } = useModelingContext()
 | 
			
		||||
  const selectionRange = context.selectionRanges.codeBasedSelections[0].range
 | 
			
		||||
  const selectionRange =
 | 
			
		||||
    context.selectionRanges?.graphSelections[0]?.codeRef?.range
 | 
			
		||||
  const inputRef = useRef<HTMLInputElement>(null)
 | 
			
		||||
  const [availableVarInfo, setAvailableVarInfo] = useState<
 | 
			
		||||
    ReturnType<typeof findAllPreviousVariables>
 | 
			
		||||
@ -157,6 +158,7 @@ export function useCalc({
 | 
			
		||||
        engineCommandManager,
 | 
			
		||||
        useFakeExecutor: true,
 | 
			
		||||
        programMemoryOverride: kclManager.programMemory.clone(),
 | 
			
		||||
        idGenerator: kclManager.execState.idGenerator,
 | 
			
		||||
      }).then(({ execState }) => {
 | 
			
		||||
        const resultDeclaration = ast.body.find(
 | 
			
		||||
          (a) =>
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { useSelector } from '@xstate/react'
 | 
			
		||||
import { useCommandsContext } from 'hooks/useCommandsContext'
 | 
			
		||||
import { useKclContext } from 'lang/KclProvider'
 | 
			
		||||
import { Artifact } from 'lang/std/artifactGraph'
 | 
			
		||||
import { CommandArgument } from 'lib/commandTypes'
 | 
			
		||||
import {
 | 
			
		||||
  Selection,
 | 
			
		||||
  canSubmitSelectionArg,
 | 
			
		||||
  getSelectionType,
 | 
			
		||||
  getSelectionTypeDisplayText,
 | 
			
		||||
@ -12,13 +12,13 @@ import { modelingMachine } from 'machines/modelingMachine'
 | 
			
		||||
import { useEffect, useMemo, useRef, useState } from 'react'
 | 
			
		||||
import { StateFrom } from 'xstate'
 | 
			
		||||
 | 
			
		||||
const semanticEntityNames: { [key: string]: Array<Selection['type']> } = {
 | 
			
		||||
  face: ['extrude-wall', 'start-cap', 'end-cap'],
 | 
			
		||||
  edge: ['edge', 'line', 'arc'],
 | 
			
		||||
  point: ['point', 'line-end', 'line-mid'],
 | 
			
		||||
const semanticEntityNames: { [key: string]: Array<Artifact['type']> } = {
 | 
			
		||||
  face: ['wall', 'cap', 'solid2D'],
 | 
			
		||||
  edge: ['segment', 'sweepEdge', 'edgeCutEdge'],
 | 
			
		||||
  point: [],
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getSemanticSelectionType(selectionType: Array<Selection['type']>) {
 | 
			
		||||
function getSemanticSelectionType(selectionType: Array<Artifact['type']>) {
 | 
			
		||||
  const semanticSelectionType = new Set()
 | 
			
		||||
  selectionType.forEach((type) => {
 | 
			
		||||
    Object.entries(semanticEntityNames).forEach(([entity, entityTypes]) => {
 | 
			
		||||
@ -49,8 +49,12 @@ function CommandBarSelectionInput({
 | 
			
		||||
  const [hasSubmitted, setHasSubmitted] = useState(false)
 | 
			
		||||
  const selection = useSelector(arg.machineActor, selectionSelector)
 | 
			
		||||
  const selectionsByType = useMemo(() => {
 | 
			
		||||
    const selectionRangeEnd = selection?.codeBasedSelections[0]?.range[1]
 | 
			
		||||
    return !selectionRangeEnd || selectionRangeEnd === code.length
 | 
			
		||||
    const selectionRangeEnd = !selection
 | 
			
		||||
      ? null
 | 
			
		||||
      : selection?.graphSelections[0]?.codeRef?.range[1]
 | 
			
		||||
    return !selectionRangeEnd || selectionRangeEnd === code.length || !selection
 | 
			
		||||
      ? 'none'
 | 
			
		||||
      : !selection
 | 
			
		||||
      ? 'none'
 | 
			
		||||
      : getSelectionType(selection)
 | 
			
		||||
  }, [selection, code])
 | 
			
		||||
 | 
			
		||||
@ -1170,15 +1170,15 @@ const CustomIconMap = {
 | 
			
		||||
      xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
    >
 | 
			
		||||
      <path
 | 
			
		||||
        fill-rule="evenodd"
 | 
			
		||||
        clip-rule="evenodd"
 | 
			
		||||
        fillRule="evenodd"
 | 
			
		||||
        clipRule="evenodd"
 | 
			
		||||
        d="M7.95705 5.99046C7.05643 6.44935 6.33654 7.19809 5.91336 8.11602C5.49019 9.03396 5.38838 10.0676 5.62434 11.0505C5.8603 12.0334 6.42029 12.9081 7.21408 13.5339C8.00787 14.1597 8.98922 14.5 10 14.5C11.0108 14.5 11.9921 14.1597 12.7859 13.5339C13.5797 12.9082 14.1397 12.0334 14.3757 11.0505C14.6116 10.0676 14.5098 9.03396 14.0866 8.11603C13.6635 7.19809 12.9436 6.44935 12.043 5.99046L12.497 5.09946C13.5977 5.66032 14.4776 6.57544 14.9948 7.69737C15.512 8.81929 15.6364 10.0827 15.348 11.2839C15.0596 12.4852 14.3752 13.5544 13.405 14.3192C12.4348 15.0841 11.2354 15.5 10 15.5C8.7646 15.5 7.56517 15.0841 6.59499 14.3192C5.6248 13.5544 4.94037 12.4852 4.65197 11.2839C4.36357 10.0827 4.488 8.81929 5.00522 7.69736C5.52243 6.57544 6.40231 5.66032 7.50306 5.09946L7.95705 5.99046Z"
 | 
			
		||||
        fill="currentColor"
 | 
			
		||||
      />
 | 
			
		||||
      <path d="M10 5.5V4M10 4H8M10 4H12" stroke="currentColor" />
 | 
			
		||||
      <path
 | 
			
		||||
        fill-rule="evenodd"
 | 
			
		||||
        clip-rule="evenodd"
 | 
			
		||||
        fillRule="evenodd"
 | 
			
		||||
        clipRule="evenodd"
 | 
			
		||||
        d="M12.8536 7.85356L10.3536 10.3536C10.1583 10.5488 9.84171 10.5488 9.64645 10.3536C9.45118 10.1583 9.45118 9.84172 9.64645 9.64645L12.1464 7.14645L12.8536 7.85356Z"
 | 
			
		||||
        fill="currentColor"
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
@ -64,7 +64,10 @@ export const EngineCommands = () => {
 | 
			
		||||
          )
 | 
			
		||||
        })}
 | 
			
		||||
      </div>
 | 
			
		||||
      <button data-testid="clear-commands" onClick={clearEngineCommands}>
 | 
			
		||||
      <button
 | 
			
		||||
        data-testid="clear-commands"
 | 
			
		||||
        onClick={() => clearEngineCommands()}
 | 
			
		||||
      >
 | 
			
		||||
        Clear
 | 
			
		||||
      </button>
 | 
			
		||||
      <br />
 | 
			
		||||
 | 
			
		||||
@ -43,12 +43,12 @@ import {
 | 
			
		||||
} from './Toolbar/SetAngleBetween'
 | 
			
		||||
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
 | 
			
		||||
import {
 | 
			
		||||
  Selections,
 | 
			
		||||
  canSweepSelection,
 | 
			
		||||
  handleSelectionBatch,
 | 
			
		||||
  isSelectionLastLine,
 | 
			
		||||
  isRangeBetweenCharacters,
 | 
			
		||||
  isSketchPipe,
 | 
			
		||||
  Selections,
 | 
			
		||||
  updateSelections,
 | 
			
		||||
} from 'lib/selections'
 | 
			
		||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
 | 
			
		||||
@ -318,7 +318,7 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
              })
 | 
			
		||||
            }
 | 
			
		||||
            let selections: Selections = {
 | 
			
		||||
              codeBasedSelections: [],
 | 
			
		||||
              graphSelections: [],
 | 
			
		||||
              otherSelections: [],
 | 
			
		||||
            }
 | 
			
		||||
            if (setSelections.selectionType === 'singleCodeCursor') {
 | 
			
		||||
@ -328,7 +328,7 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
                !editorManager.isShiftDown
 | 
			
		||||
              ) {
 | 
			
		||||
                selections = {
 | 
			
		||||
                  codeBasedSelections: [],
 | 
			
		||||
                  graphSelections: [],
 | 
			
		||||
                  otherSelections: [],
 | 
			
		||||
                }
 | 
			
		||||
              } else if (
 | 
			
		||||
@ -336,13 +336,13 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
                !editorManager.isShiftDown
 | 
			
		||||
              ) {
 | 
			
		||||
                selections = {
 | 
			
		||||
                  codeBasedSelections: [setSelections.selection],
 | 
			
		||||
                  graphSelections: [setSelections.selection],
 | 
			
		||||
                  otherSelections: [],
 | 
			
		||||
                }
 | 
			
		||||
              } else if (setSelections.selection && editorManager.isShiftDown) {
 | 
			
		||||
                selections = {
 | 
			
		||||
                  codeBasedSelections: [
 | 
			
		||||
                    ...selectionRanges.codeBasedSelections,
 | 
			
		||||
                  graphSelections: [
 | 
			
		||||
                    ...selectionRanges.graphSelections,
 | 
			
		||||
                    setSelections.selection,
 | 
			
		||||
                  ],
 | 
			
		||||
                  otherSelections: selectionRanges.otherSelections,
 | 
			
		||||
@ -378,18 +378,18 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
            if (setSelections.selectionType === 'otherSelection') {
 | 
			
		||||
              if (editorManager.isShiftDown) {
 | 
			
		||||
                selections = {
 | 
			
		||||
                  codeBasedSelections: selectionRanges.codeBasedSelections,
 | 
			
		||||
                  graphSelections: selectionRanges.graphSelections,
 | 
			
		||||
                  otherSelections: [setSelections.selection],
 | 
			
		||||
                }
 | 
			
		||||
              } else {
 | 
			
		||||
                selections = {
 | 
			
		||||
                  codeBasedSelections: [],
 | 
			
		||||
                  graphSelections: [],
 | 
			
		||||
                  otherSelections: [setSelections.selection],
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
              const { engineEvents, updateSceneObjectColors } =
 | 
			
		||||
                handleSelectionBatch({
 | 
			
		||||
                  selections,
 | 
			
		||||
                  selections: selections,
 | 
			
		||||
                })
 | 
			
		||||
              engineEvents &&
 | 
			
		||||
                engineEvents.forEach((event) => {
 | 
			
		||||
@ -558,7 +558,7 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
          // A user can begin extruding if they either have 1+ faces selected or nothing selected
 | 
			
		||||
          // TODO: I believe this guard only allows for extruding a single face at a time
 | 
			
		||||
          const hasNoSelection =
 | 
			
		||||
            selectionRanges.codeBasedSelections.length === 0 ||
 | 
			
		||||
            selectionRanges.graphSelections.length === 0 ||
 | 
			
		||||
            isRangeBetweenCharacters(selectionRanges) ||
 | 
			
		||||
            isSelectionLastLine(selectionRanges, codeManager.code)
 | 
			
		||||
 | 
			
		||||
@ -570,21 +570,24 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
          }
 | 
			
		||||
          if (!isSketchPipe(selectionRanges)) return false
 | 
			
		||||
 | 
			
		||||
          return canSweepSelection(selectionRanges)
 | 
			
		||||
          const canSweep = canSweepSelection(selectionRanges)
 | 
			
		||||
          if (err(canSweep)) return false
 | 
			
		||||
          return canSweep
 | 
			
		||||
        },
 | 
			
		||||
        'has valid selection for deletion': ({
 | 
			
		||||
          context: { selectionRanges },
 | 
			
		||||
        }) => {
 | 
			
		||||
          if (!commandBarState.matches('Closed')) return false
 | 
			
		||||
          if (selectionRanges.codeBasedSelections.length <= 0) return false
 | 
			
		||||
          if (selectionRanges.graphSelections.length <= 0) return false
 | 
			
		||||
          return true
 | 
			
		||||
        },
 | 
			
		||||
        'has valid fillet selection': ({ context: { selectionRanges } }) =>
 | 
			
		||||
          hasValidFilletSelection({
 | 
			
		||||
        'has valid fillet selection': ({ context: { selectionRanges } }) => {
 | 
			
		||||
          return hasValidFilletSelection({
 | 
			
		||||
            selectionRanges,
 | 
			
		||||
            ast: kclManager.ast,
 | 
			
		||||
            code: codeManager.code,
 | 
			
		||||
          }),
 | 
			
		||||
          })
 | 
			
		||||
        },
 | 
			
		||||
        'Selection is on face': ({ context: { selectionRanges }, event }) => {
 | 
			
		||||
          if (event.type !== 'Enter sketch') return false
 | 
			
		||||
          if (event.data?.forceNewSketch) return false
 | 
			
		||||
@ -691,7 +694,8 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
        }),
 | 
			
		||||
        'animate-to-sketch': fromPromise(
 | 
			
		||||
          async ({ input: { selectionRanges } }) => {
 | 
			
		||||
            const sourceRange = selectionRanges.codeBasedSelections[0].range
 | 
			
		||||
            const sourceRange =
 | 
			
		||||
              selectionRanges.graphSelections[0]?.codeRef?.range
 | 
			
		||||
            const sketchPathToNode = getNodePathFromSourceRange(
 | 
			
		||||
              kclManager.ast,
 | 
			
		||||
              sourceRange
 | 
			
		||||
@ -713,6 +717,7 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ),
 | 
			
		||||
 | 
			
		||||
        'Get horizontal info': fromPromise(
 | 
			
		||||
          async ({ input: { selectionRanges, sketchDetails } }) => {
 | 
			
		||||
            const { modifiedAst, pathToNodeMap } =
 | 
			
		||||
@ -848,7 +853,9 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
        'Get length info': fromPromise(
 | 
			
		||||
          async ({ input: { selectionRanges, sketchDetails } }) => {
 | 
			
		||||
            const { modifiedAst, pathToNodeMap } =
 | 
			
		||||
              await applyConstraintAngleLength({ selectionRanges })
 | 
			
		||||
              await applyConstraintAngleLength({
 | 
			
		||||
                selectionRanges,
 | 
			
		||||
              })
 | 
			
		||||
            const _modifiedAst = parse(recast(modifiedAst))
 | 
			
		||||
            if (!sketchDetails)
 | 
			
		||||
              return Promise.reject(new Error('No sketch details'))
 | 
			
		||||
 | 
			
		||||
@ -25,8 +25,8 @@ export function equalAngleInfo({
 | 
			
		||||
      enabled: boolean
 | 
			
		||||
    }
 | 
			
		||||
  | Error {
 | 
			
		||||
  const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
 | 
			
		||||
    getNodePathFromSourceRange(kclManager.ast, range)
 | 
			
		||||
  const paths = selectionRanges.graphSelections.map(({ codeRef }) =>
 | 
			
		||||
    getNodePathFromSourceRange(kclManager.ast, codeRef.range)
 | 
			
		||||
  )
 | 
			
		||||
  const _nodes = paths.map((pathToNode) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
 | 
			
		||||
@ -64,7 +64,7 @@ export function equalAngleInfo({
 | 
			
		||||
  const transforms = getTransformInfos(
 | 
			
		||||
    {
 | 
			
		||||
      ...selectionRanges,
 | 
			
		||||
      codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
 | 
			
		||||
      graphSelections: selectionRanges.graphSelections.slice(1),
 | 
			
		||||
    },
 | 
			
		||||
    kclManager.ast,
 | 
			
		||||
    'equalAngle'
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,7 @@
 | 
			
		||||
import { toolTips } from 'lang/langHelpers'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  getNodePathFromSourceRange,
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
} from '../../lang/queryAst'
 | 
			
		||||
import { getNodeFromPath } from '../../lang/queryAst'
 | 
			
		||||
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
 | 
			
		||||
import {
 | 
			
		||||
  transformSecondarySketchLinesTagFirst,
 | 
			
		||||
@ -26,11 +23,8 @@ export function setEqualLengthInfo({
 | 
			
		||||
      enabled: boolean
 | 
			
		||||
    }
 | 
			
		||||
  | Error {
 | 
			
		||||
  const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
 | 
			
		||||
    getNodePathFromSourceRange(kclManager.ast, range)
 | 
			
		||||
  )
 | 
			
		||||
  const _nodes = paths.map((pathToNode) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
 | 
			
		||||
  const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
 | 
			
		||||
    if (err(tmp)) return tmp
 | 
			
		||||
    return tmp.node
 | 
			
		||||
  })
 | 
			
		||||
@ -38,10 +32,10 @@ export function setEqualLengthInfo({
 | 
			
		||||
  if (err(_err1)) return _err1
 | 
			
		||||
  const nodes = _nodes as Expr[]
 | 
			
		||||
 | 
			
		||||
  const _varDecs = paths.map((pathToNode) => {
 | 
			
		||||
  const _varDecs = selectionRanges.graphSelections.map(({ codeRef }) => {
 | 
			
		||||
    const tmp = getNodeFromPath<VariableDeclarator>(
 | 
			
		||||
      kclManager.ast,
 | 
			
		||||
      pathToNode,
 | 
			
		||||
      codeRef.pathToNode,
 | 
			
		||||
      'VariableDeclarator'
 | 
			
		||||
    )
 | 
			
		||||
    if (err(tmp)) return tmp
 | 
			
		||||
@ -65,7 +59,7 @@ export function setEqualLengthInfo({
 | 
			
		||||
  const transforms = getTransformInfos(
 | 
			
		||||
    {
 | 
			
		||||
      ...selectionRanges,
 | 
			
		||||
      codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
 | 
			
		||||
      graphSelections: selectionRanges.graphSelections.slice(1),
 | 
			
		||||
    },
 | 
			
		||||
    kclManager.ast,
 | 
			
		||||
    'equalLength'
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,7 @@
 | 
			
		||||
import { toolTips } from 'lang/langHelpers'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { Program, ProgramMemory, Expr } from '../../lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  getNodePathFromSourceRange,
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
} from '../../lang/queryAst'
 | 
			
		||||
import { getNodeFromPath } from '../../lang/queryAst'
 | 
			
		||||
import {
 | 
			
		||||
  PathToNodeMap,
 | 
			
		||||
  getTransformInfos,
 | 
			
		||||
@ -24,11 +21,8 @@ export function horzVertInfo(
 | 
			
		||||
      enabled: boolean
 | 
			
		||||
    }
 | 
			
		||||
  | Error {
 | 
			
		||||
  const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
 | 
			
		||||
    getNodePathFromSourceRange(kclManager.ast, range)
 | 
			
		||||
  )
 | 
			
		||||
  const _nodes = paths.map((pathToNode) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
 | 
			
		||||
  const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
 | 
			
		||||
    if (err(tmp)) return tmp
 | 
			
		||||
    return tmp.node
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,7 @@
 | 
			
		||||
import { toolTips } from 'lang/langHelpers'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import {
 | 
			
		||||
  getNodePathFromSourceRange,
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
  isLinesParallelAndConstrained,
 | 
			
		||||
} from '../../lang/queryAst'
 | 
			
		||||
@ -17,7 +16,7 @@ import { TransformInfo } from 'lang/std/stdTypes'
 | 
			
		||||
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
 | 
			
		||||
import { createVariableDeclaration } from '../../lang/modifyAst'
 | 
			
		||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
 | 
			
		||||
import { kclManager } from 'lib/singletons'
 | 
			
		||||
import { engineCommandManager, kclManager } from 'lib/singletons'
 | 
			
		||||
import { err } from 'lib/trap'
 | 
			
		||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
 | 
			
		||||
 | 
			
		||||
@ -34,7 +33,7 @@ export function intersectInfo({
 | 
			
		||||
      forcedSelectionRanges: Selections
 | 
			
		||||
    }
 | 
			
		||||
  | Error {
 | 
			
		||||
  if (selectionRanges.codeBasedSelections.length < 2) {
 | 
			
		||||
  if (selectionRanges.graphSelections.length < 2) {
 | 
			
		||||
    return {
 | 
			
		||||
      enabled: false,
 | 
			
		||||
      transforms: [],
 | 
			
		||||
@ -43,38 +42,35 @@ export function intersectInfo({
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const previousSegment =
 | 
			
		||||
    selectionRanges.codeBasedSelections.length > 1 &&
 | 
			
		||||
    selectionRanges.graphSelections.length > 1 &&
 | 
			
		||||
    isLinesParallelAndConstrained(
 | 
			
		||||
      kclManager.ast,
 | 
			
		||||
      engineCommandManager.artifactGraph,
 | 
			
		||||
      kclManager.programMemory,
 | 
			
		||||
      selectionRanges.codeBasedSelections[0],
 | 
			
		||||
      selectionRanges.codeBasedSelections[1]
 | 
			
		||||
      selectionRanges.graphSelections[0],
 | 
			
		||||
      selectionRanges.graphSelections[1]
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
  if (err(previousSegment)) return previousSegment
 | 
			
		||||
 | 
			
		||||
  const artifact = selectionRanges.graphSelections[1]?.artifact
 | 
			
		||||
  const shouldUsePreviousSegment =
 | 
			
		||||
    selectionRanges.codeBasedSelections?.[1]?.type !== 'line-end' &&
 | 
			
		||||
    (!artifact || artifact.type === 'segment') &&
 | 
			
		||||
    previousSegment &&
 | 
			
		||||
    previousSegment.isParallelAndConstrained
 | 
			
		||||
 | 
			
		||||
  const _forcedSelectionRanges: typeof selectionRanges = {
 | 
			
		||||
    ...selectionRanges,
 | 
			
		||||
    codeBasedSelections: [
 | 
			
		||||
      selectionRanges.codeBasedSelections?.[0],
 | 
			
		||||
      shouldUsePreviousSegment
 | 
			
		||||
        ? {
 | 
			
		||||
            range: previousSegment.sourceRange,
 | 
			
		||||
            type: 'line-end',
 | 
			
		||||
          }
 | 
			
		||||
        : selectionRanges.codeBasedSelections?.[1],
 | 
			
		||||
    graphSelections: [
 | 
			
		||||
      selectionRanges.graphSelections?.[0],
 | 
			
		||||
      shouldUsePreviousSegment && previousSegment.selection
 | 
			
		||||
        ? previousSegment.selection
 | 
			
		||||
        : selectionRanges.graphSelections?.[1],
 | 
			
		||||
    ],
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const paths = _forcedSelectionRanges.codeBasedSelections.map(({ range }) =>
 | 
			
		||||
    getNodePathFromSourceRange(kclManager.ast, range)
 | 
			
		||||
  )
 | 
			
		||||
  const _nodes = paths.map((pathToNode) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
 | 
			
		||||
  const _nodes = _forcedSelectionRanges.graphSelections.map(({ codeRef }) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
 | 
			
		||||
    if (err(tmp)) return tmp
 | 
			
		||||
    return tmp.node
 | 
			
		||||
  })
 | 
			
		||||
@ -82,10 +78,10 @@ export function intersectInfo({
 | 
			
		||||
  if (err(_err1)) return _err1
 | 
			
		||||
  const nodes = _nodes as Expr[]
 | 
			
		||||
 | 
			
		||||
  const _varDecs = paths.map((pathToNode) => {
 | 
			
		||||
  const _varDecs = _forcedSelectionRanges.graphSelections.map(({ codeRef }) => {
 | 
			
		||||
    const tmp = getNodeFromPath<VariableDeclarator>(
 | 
			
		||||
      kclManager.ast,
 | 
			
		||||
      pathToNode,
 | 
			
		||||
      codeRef.pathToNode,
 | 
			
		||||
      'VariableDeclarator'
 | 
			
		||||
    )
 | 
			
		||||
    if (err(tmp)) return tmp
 | 
			
		||||
@ -112,18 +108,19 @@ export function intersectInfo({
 | 
			
		||||
  const theTransforms = getTransformInfos(
 | 
			
		||||
    {
 | 
			
		||||
      ...selectionRanges,
 | 
			
		||||
      codeBasedSelections: _forcedSelectionRanges.codeBasedSelections.slice(1),
 | 
			
		||||
      graphSelections: _forcedSelectionRanges.graphSelections.slice(1),
 | 
			
		||||
    },
 | 
			
		||||
    kclManager.ast,
 | 
			
		||||
    'intersect'
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const forcedArtifact = _forcedSelectionRanges?.graphSelections?.[1]?.artifact
 | 
			
		||||
  const _enableEqual =
 | 
			
		||||
    secondaryVarDecs.length === 1 &&
 | 
			
		||||
    isAllTooltips &&
 | 
			
		||||
    isOthersLinkedToPrimary &&
 | 
			
		||||
    theTransforms.every(Boolean) &&
 | 
			
		||||
    _forcedSelectionRanges?.codeBasedSelections?.[1]?.type === 'line-end'
 | 
			
		||||
    (!forcedArtifact || forcedArtifact.type === 'segment')
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    enabled: _enableEqual,
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,7 @@
 | 
			
		||||
import { toolTips } from 'lang/langHelpers'
 | 
			
		||||
import { Selection, Selections } from 'lib/selections'
 | 
			
		||||
import { PathToNode, Program, Expr } from '../../lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  getNodePathFromSourceRange,
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
} from '../../lang/queryAst'
 | 
			
		||||
import { getNodeFromPath } from '../../lang/queryAst'
 | 
			
		||||
import {
 | 
			
		||||
  PathToNodeMap,
 | 
			
		||||
  getRemoveConstraintsTransforms,
 | 
			
		||||
@ -14,6 +11,7 @@ import { TransformInfo } from 'lang/std/stdTypes'
 | 
			
		||||
import { kclManager } from 'lib/singletons'
 | 
			
		||||
import { err } from 'lib/trap'
 | 
			
		||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
 | 
			
		||||
import { codeRefFromRange } from 'lang/std/artifactGraph'
 | 
			
		||||
 | 
			
		||||
export function removeConstrainingValuesInfo({
 | 
			
		||||
  selectionRanges,
 | 
			
		||||
@ -28,13 +26,8 @@ export function removeConstrainingValuesInfo({
 | 
			
		||||
      updatedSelectionRanges: Selections
 | 
			
		||||
    }
 | 
			
		||||
  | Error {
 | 
			
		||||
  const paths =
 | 
			
		||||
    pathToNodes ||
 | 
			
		||||
    selectionRanges.codeBasedSelections.map(({ range }) =>
 | 
			
		||||
      getNodePathFromSourceRange(kclManager.ast, range)
 | 
			
		||||
    )
 | 
			
		||||
  const _nodes = paths.map((pathToNode) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
 | 
			
		||||
  const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
 | 
			
		||||
    if (err(tmp)) return tmp
 | 
			
		||||
    return tmp.node
 | 
			
		||||
  })
 | 
			
		||||
@ -45,10 +38,9 @@ export function removeConstrainingValuesInfo({
 | 
			
		||||
  const updatedSelectionRanges = pathToNodes
 | 
			
		||||
    ? {
 | 
			
		||||
        otherSelections: [],
 | 
			
		||||
        codeBasedSelections: nodes.map(
 | 
			
		||||
        graphSelections: nodes.map(
 | 
			
		||||
          (node): Selection => ({
 | 
			
		||||
            range: [node.start, node.end],
 | 
			
		||||
            type: 'default',
 | 
			
		||||
            codeRef: codeRefFromRange([node.start, node.end], kclManager.ast),
 | 
			
		||||
          })
 | 
			
		||||
        ),
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,7 @@
 | 
			
		||||
import { toolTips } from 'lang/langHelpers'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { Program, Expr } from '../../lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  getNodePathFromSourceRange,
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
} from '../../lang/queryAst'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { getNodeFromPath } from '../../lang/queryAst'
 | 
			
		||||
import {
 | 
			
		||||
  getTransformInfos,
 | 
			
		||||
  transformAstSketchLines,
 | 
			
		||||
@ -47,13 +44,10 @@ export function absDistanceInfo({
 | 
			
		||||
      : constraint === 'snapToYAxis'
 | 
			
		||||
      ? 'xAbs'
 | 
			
		||||
      : 'yAbs'
 | 
			
		||||
  const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
 | 
			
		||||
    getNodePathFromSourceRange(kclManager.ast, range)
 | 
			
		||||
  )
 | 
			
		||||
  const _nodes = paths.map((pathToNode) => {
 | 
			
		||||
  const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(
 | 
			
		||||
      kclManager.ast,
 | 
			
		||||
      pathToNode,
 | 
			
		||||
      codeRef.pathToNode,
 | 
			
		||||
      'CallExpression'
 | 
			
		||||
    )
 | 
			
		||||
    if (err(tmp)) return tmp
 | 
			
		||||
@ -84,7 +78,7 @@ export function absDistanceInfo({
 | 
			
		||||
  const enabled =
 | 
			
		||||
    isAllTooltips &&
 | 
			
		||||
    transforms.every(Boolean) &&
 | 
			
		||||
    selectionRanges.codeBasedSelections.length === 1 &&
 | 
			
		||||
    selectionRanges.graphSelections.length === 1 &&
 | 
			
		||||
    (enableX || enableY)
 | 
			
		||||
 | 
			
		||||
  return { enabled, transforms }
 | 
			
		||||
@ -109,7 +103,7 @@ export async function applyConstraintAbsDistance({
 | 
			
		||||
 | 
			
		||||
  const transform1 = transformAstSketchLines({
 | 
			
		||||
    ast: structuredClone(kclManager.ast),
 | 
			
		||||
    selectionRanges: selectionRanges,
 | 
			
		||||
    selectionRanges,
 | 
			
		||||
    transformInfos,
 | 
			
		||||
    programMemory: kclManager.programMemory,
 | 
			
		||||
    referenceSegName: '',
 | 
			
		||||
@ -129,7 +123,7 @@ export async function applyConstraintAbsDistance({
 | 
			
		||||
 | 
			
		||||
  const transform2 = transformAstSketchLines({
 | 
			
		||||
    ast: structuredClone(kclManager.ast),
 | 
			
		||||
    selectionRanges: selectionRanges,
 | 
			
		||||
    selectionRanges,
 | 
			
		||||
    transformInfos,
 | 
			
		||||
    programMemory: kclManager.programMemory,
 | 
			
		||||
    referenceSegName: '',
 | 
			
		||||
@ -177,7 +171,7 @@ export function applyConstraintAxisAlign({
 | 
			
		||||
 | 
			
		||||
  return transformAstSketchLines({
 | 
			
		||||
    ast: structuredClone(kclManager.ast),
 | 
			
		||||
    selectionRanges: selectionRanges,
 | 
			
		||||
    selectionRanges,
 | 
			
		||||
    transformInfos,
 | 
			
		||||
    programMemory: kclManager.programMemory,
 | 
			
		||||
    referenceSegName: '',
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,7 @@
 | 
			
		||||
import { toolTips } from 'lang/langHelpers'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  getNodePathFromSourceRange,
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
} from '../../lang/queryAst'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { getNodeFromPath } from '../../lang/queryAst'
 | 
			
		||||
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
 | 
			
		||||
import {
 | 
			
		||||
  transformSecondarySketchLinesTagFirst,
 | 
			
		||||
@ -31,12 +28,8 @@ export function angleBetweenInfo({
 | 
			
		||||
      enabled: boolean
 | 
			
		||||
    }
 | 
			
		||||
  | Error {
 | 
			
		||||
  const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
 | 
			
		||||
    getNodePathFromSourceRange(kclManager.ast, range)
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const _nodes = paths.map((pathToNode) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
 | 
			
		||||
  const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
 | 
			
		||||
    if (err(tmp)) return tmp
 | 
			
		||||
    return tmp.node
 | 
			
		||||
  })
 | 
			
		||||
@ -44,10 +37,10 @@ export function angleBetweenInfo({
 | 
			
		||||
  if (err(_err1)) return _err1
 | 
			
		||||
  const nodes = _nodes as Expr[]
 | 
			
		||||
 | 
			
		||||
  const _varDecs = paths.map((pathToNode) => {
 | 
			
		||||
  const _varDecs = selectionRanges.graphSelections.map(({ codeRef }) => {
 | 
			
		||||
    const tmp = getNodeFromPath<VariableDeclarator>(
 | 
			
		||||
      kclManager.ast,
 | 
			
		||||
      pathToNode,
 | 
			
		||||
      codeRef.pathToNode,
 | 
			
		||||
      'VariableDeclarator'
 | 
			
		||||
    )
 | 
			
		||||
    if (err(tmp)) return tmp
 | 
			
		||||
@ -71,7 +64,7 @@ export function angleBetweenInfo({
 | 
			
		||||
  const theTransforms = getTransformInfos(
 | 
			
		||||
    {
 | 
			
		||||
      ...selectionRanges,
 | 
			
		||||
      codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
 | 
			
		||||
      graphSelections: selectionRanges.graphSelections.slice(1),
 | 
			
		||||
    },
 | 
			
		||||
    kclManager.ast,
 | 
			
		||||
    'setAngleBetween'
 | 
			
		||||
@ -88,10 +81,8 @@ export function angleBetweenInfo({
 | 
			
		||||
 | 
			
		||||
export async function applyConstraintAngleBetween({
 | 
			
		||||
  selectionRanges,
 | 
			
		||||
}: // constraint,
 | 
			
		||||
{
 | 
			
		||||
}: {
 | 
			
		||||
  selectionRanges: Selections
 | 
			
		||||
  // constraint: 'setHorzDistance' | 'setVertDistance'
 | 
			
		||||
}): Promise<{
 | 
			
		||||
  modifiedAst: Program
 | 
			
		||||
  pathToNodeMap: PathToNodeMap
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,6 @@
 | 
			
		||||
import { toolTips } from 'lang/langHelpers'
 | 
			
		||||
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  getNodePathFromSourceRange,
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
} from '../../lang/queryAst'
 | 
			
		||||
import { getNodeFromPath } from '../../lang/queryAst'
 | 
			
		||||
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
 | 
			
		||||
import {
 | 
			
		||||
  transformSecondarySketchLinesTagFirst,
 | 
			
		||||
@ -34,11 +31,8 @@ export function horzVertDistanceInfo({
 | 
			
		||||
      enabled: boolean
 | 
			
		||||
    }
 | 
			
		||||
  | Error {
 | 
			
		||||
  const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
 | 
			
		||||
    getNodePathFromSourceRange(kclManager.ast, range)
 | 
			
		||||
  )
 | 
			
		||||
  const _nodes = paths.map((pathToNode) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
 | 
			
		||||
  const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
 | 
			
		||||
    const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
 | 
			
		||||
    if (err(tmp)) return tmp
 | 
			
		||||
    return tmp.node
 | 
			
		||||
  })
 | 
			
		||||
@ -47,10 +41,10 @@ export function horzVertDistanceInfo({
 | 
			
		||||
  if (hasErr) return nodesWErrs[0]
 | 
			
		||||
  const nodes = _nodes as Expr[]
 | 
			
		||||
 | 
			
		||||
  const _varDecs = paths.map((pathToNode) => {
 | 
			
		||||
  const _varDecs = selectionRanges.graphSelections.map(({ codeRef }) => {
 | 
			
		||||
    const tmp = getNodeFromPath<VariableDeclarator>(
 | 
			
		||||
      kclManager.ast,
 | 
			
		||||
      pathToNode,
 | 
			
		||||
      codeRef.pathToNode,
 | 
			
		||||
      'VariableDeclarator'
 | 
			
		||||
    )
 | 
			
		||||
    if (err(tmp)) return tmp
 | 
			
		||||
@ -77,7 +71,7 @@ export function horzVertDistanceInfo({
 | 
			
		||||
  const theTransforms = getTransformInfos(
 | 
			
		||||
    {
 | 
			
		||||
      ...selectionRanges,
 | 
			
		||||
      codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
 | 
			
		||||
      graphSelections: selectionRanges.graphSelections.slice(1),
 | 
			
		||||
    },
 | 
			
		||||
    kclManager.ast,
 | 
			
		||||
    constraint
 | 
			
		||||
@ -104,7 +98,7 @@ export async function applyConstraintHorzVertDistance({
 | 
			
		||||
  pathToNodeMap: PathToNodeMap
 | 
			
		||||
}> {
 | 
			
		||||
  const info = horzVertDistanceInfo({
 | 
			
		||||
    selectionRanges,
 | 
			
		||||
    selectionRanges: selectionRanges,
 | 
			
		||||
    constraint,
 | 
			
		||||
  })
 | 
			
		||||
  if (err(info)) return Promise.reject(info)
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,7 @@
 | 
			
		||||
import { toolTips } from 'lang/langHelpers'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { Program, Expr } from '../../lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  getNodePathFromSourceRange,
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
} from '../../lang/queryAst'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { getNodeFromPath } from '../../lang/queryAst'
 | 
			
		||||
import {
 | 
			
		||||
  PathToNodeMap,
 | 
			
		||||
  getTransformInfos,
 | 
			
		||||
@ -40,15 +37,11 @@ export function angleLengthInfo({
 | 
			
		||||
      enabled: boolean
 | 
			
		||||
    }
 | 
			
		||||
  | Error {
 | 
			
		||||
  const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
 | 
			
		||||
    getNodePathFromSourceRange(kclManager.ast, range)
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const nodes = paths.map((pathToNode) =>
 | 
			
		||||
    getNodeFromPath<Expr>(kclManager.ast, pathToNode, 'CallExpression')
 | 
			
		||||
  const nodes = selectionRanges.graphSelections.map(({ codeRef }) =>
 | 
			
		||||
    getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode, 'CallExpression')
 | 
			
		||||
  )
 | 
			
		||||
  const _err1 = nodes.find(err)
 | 
			
		||||
  if (err(_err1)) return _err1
 | 
			
		||||
  if (_err1 instanceof Error) return _err1
 | 
			
		||||
 | 
			
		||||
  const isAllTooltips = nodes.every((meta) => {
 | 
			
		||||
    if (err(meta)) return false
 | 
			
		||||
@ -64,7 +57,7 @@ export function angleLengthInfo({
 | 
			
		||||
    angleOrLength
 | 
			
		||||
  )
 | 
			
		||||
  const enabled =
 | 
			
		||||
    selectionRanges.codeBasedSelections.length <= 1 &&
 | 
			
		||||
    selectionRanges.graphSelections.length <= 1 &&
 | 
			
		||||
    isAllTooltips &&
 | 
			
		||||
    transforms.every(Boolean)
 | 
			
		||||
  return { enabled, transforms }
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
import { EditorView, ViewUpdate } from '@codemirror/view'
 | 
			
		||||
import { syntaxTree } from '@codemirror/language'
 | 
			
		||||
import { EditorSelection, Annotation, Transaction } from '@codemirror/state'
 | 
			
		||||
import { engineCommandManager } from 'lib/singletons'
 | 
			
		||||
import { engineCommandManager, kclManager } from 'lib/singletons'
 | 
			
		||||
import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine'
 | 
			
		||||
import { Selections, processCodeMirrorRanges, Selection } from 'lib/selections'
 | 
			
		||||
import { Selections, Selection, processCodeMirrorRanges } from 'lib/selections'
 | 
			
		||||
import { undo, redo } from '@codemirror/commands'
 | 
			
		||||
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
 | 
			
		||||
import { addLineHighlight, addLineHighlightEvent } from './highlightextension'
 | 
			
		||||
@ -31,7 +31,7 @@ export default class EditorManager {
 | 
			
		||||
  private _isShiftDown: boolean = false
 | 
			
		||||
  private _selectionRanges: Selections = {
 | 
			
		||||
    otherSelections: [],
 | 
			
		||||
    codeBasedSelections: [],
 | 
			
		||||
    graphSelections: [],
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _lastEvent: { event: string; time: number } | null = null
 | 
			
		||||
@ -138,10 +138,10 @@ export default class EditorManager {
 | 
			
		||||
    return this._highlightRange
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setHighlightRange(selections: Array<Selection['range']>): void {
 | 
			
		||||
    this._highlightRange = selections
 | 
			
		||||
  setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
 | 
			
		||||
    this._highlightRange = range
 | 
			
		||||
 | 
			
		||||
    const selectionsWithSafeEnds = selections.map((s): [number, number] => {
 | 
			
		||||
    const selectionsWithSafeEnds = range.map((s): [number, number] => {
 | 
			
		||||
      const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])
 | 
			
		||||
      return [s[0], safeEnd]
 | 
			
		||||
    })
 | 
			
		||||
@ -254,21 +254,23 @@ export default class EditorManager {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  selectRange(selections: Selections) {
 | 
			
		||||
    if (selections.codeBasedSelections.length === 0) {
 | 
			
		||||
    if (selections?.graphSelections?.length === 0) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    let codeBasedSelections = []
 | 
			
		||||
    for (const selection of selections.codeBasedSelections) {
 | 
			
		||||
    for (const selection of selections.graphSelections) {
 | 
			
		||||
      codeBasedSelections.push(
 | 
			
		||||
        EditorSelection.range(selection.range[0], selection.range[1])
 | 
			
		||||
        EditorSelection.range(
 | 
			
		||||
          selection.codeRef.range[0],
 | 
			
		||||
          selection.codeRef.range[1]
 | 
			
		||||
        )
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    codeBasedSelections.push(
 | 
			
		||||
      EditorSelection.cursor(
 | 
			
		||||
        selections.codeBasedSelections[
 | 
			
		||||
          selections.codeBasedSelections.length - 1
 | 
			
		||||
        ].range[1]
 | 
			
		||||
        selections.graphSelections[selections.graphSelections.length - 1]
 | 
			
		||||
          .codeRef.range[1]
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -311,6 +313,7 @@ export default class EditorManager {
 | 
			
		||||
      codeMirrorRanges: viewUpdate.state.selection.ranges,
 | 
			
		||||
      selectionRanges: this._selectionRanges,
 | 
			
		||||
      isShiftDown: this._isShiftDown,
 | 
			
		||||
      ast: kclManager.ast,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    if (!eventInfo) {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { useEffect } from 'react'
 | 
			
		||||
import { useEffect, useRef } from 'react'
 | 
			
		||||
import {
 | 
			
		||||
  editorManager,
 | 
			
		||||
  engineCommandManager,
 | 
			
		||||
@ -9,11 +9,9 @@ import { useModelingContext } from './useModelingContext'
 | 
			
		||||
import { getEventForSelectWithPoint } from 'lib/selections'
 | 
			
		||||
import {
 | 
			
		||||
  getCapCodeRef,
 | 
			
		||||
  getSweepEdgeCodeRef,
 | 
			
		||||
  getSweepFromSuspectedSweepSurface,
 | 
			
		||||
  getEdgeCuteConsumedCodeRef,
 | 
			
		||||
  getSolid2dCodeRef,
 | 
			
		||||
  getWallCodeRef,
 | 
			
		||||
  getCodeRefsByArtifactId,
 | 
			
		||||
  getArtifactOfTypes,
 | 
			
		||||
  SegmentArtifact,
 | 
			
		||||
} from 'lang/std/artifactGraph'
 | 
			
		||||
@ -25,6 +23,8 @@ import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine'
 | 
			
		||||
 | 
			
		||||
export function useEngineConnectionSubscriptions() {
 | 
			
		||||
  const { send, context, state } = useModelingContext()
 | 
			
		||||
  const stateRef = useRef(state)
 | 
			
		||||
  stateRef.current = state
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!engineCommandManager) return
 | 
			
		||||
@ -34,66 +34,12 @@ export function useEngineConnectionSubscriptions() {
 | 
			
		||||
      event: 'highlight_set_entity',
 | 
			
		||||
      callback: ({ data }) => {
 | 
			
		||||
        if (data?.entity_id) {
 | 
			
		||||
          const artifact = engineCommandManager.artifactGraph.get(
 | 
			
		||||
            data.entity_id
 | 
			
		||||
          )
 | 
			
		||||
          if (artifact?.type === 'solid2D') {
 | 
			
		||||
            const codeRef = getSolid2dCodeRef(
 | 
			
		||||
              artifact,
 | 
			
		||||
              engineCommandManager.artifactGraph
 | 
			
		||||
            )
 | 
			
		||||
            if (err(codeRef)) return
 | 
			
		||||
            editorManager.setHighlightRange([codeRef.range])
 | 
			
		||||
          } else if (artifact?.type === 'cap') {
 | 
			
		||||
            const codeRef = getCapCodeRef(
 | 
			
		||||
              artifact,
 | 
			
		||||
              engineCommandManager.artifactGraph
 | 
			
		||||
            )
 | 
			
		||||
            if (err(codeRef)) return
 | 
			
		||||
            editorManager.setHighlightRange([codeRef.range])
 | 
			
		||||
          } else if (artifact?.type === 'wall') {
 | 
			
		||||
            const extrusion = getSweepFromSuspectedSweepSurface(
 | 
			
		||||
          const codeRefs = getCodeRefsByArtifactId(
 | 
			
		||||
            data.entity_id,
 | 
			
		||||
            engineCommandManager.artifactGraph
 | 
			
		||||
          )
 | 
			
		||||
            const codeRef = getWallCodeRef(
 | 
			
		||||
              artifact,
 | 
			
		||||
              engineCommandManager.artifactGraph
 | 
			
		||||
            )
 | 
			
		||||
            if (err(codeRef)) return
 | 
			
		||||
            editorManager.setHighlightRange(
 | 
			
		||||
              err(extrusion)
 | 
			
		||||
                ? [codeRef.range]
 | 
			
		||||
                : [codeRef.range, extrusion.codeRef.range]
 | 
			
		||||
            )
 | 
			
		||||
          } else if (artifact?.type === 'sweepEdge') {
 | 
			
		||||
            const codeRef = getSweepEdgeCodeRef(
 | 
			
		||||
              artifact,
 | 
			
		||||
              engineCommandManager.artifactGraph
 | 
			
		||||
            )
 | 
			
		||||
            if (err(codeRef)) return
 | 
			
		||||
            editorManager.setHighlightRange([codeRef.range])
 | 
			
		||||
          } else if (artifact?.type === 'segment') {
 | 
			
		||||
            editorManager.setHighlightRange([
 | 
			
		||||
              artifact?.codeRef?.range || [0, 0],
 | 
			
		||||
            ])
 | 
			
		||||
          } else if (artifact?.type === 'edgeCut') {
 | 
			
		||||
            const codeRef = artifact.codeRef
 | 
			
		||||
            const consumedCodeRef = getEdgeCuteConsumedCodeRef(
 | 
			
		||||
              artifact,
 | 
			
		||||
              engineCommandManager.artifactGraph
 | 
			
		||||
            )
 | 
			
		||||
            editorManager.setHighlightRange(
 | 
			
		||||
              err(consumedCodeRef)
 | 
			
		||||
                ? [codeRef.range]
 | 
			
		||||
                : [codeRef.range, consumedCodeRef.range]
 | 
			
		||||
            )
 | 
			
		||||
          } else if (artifact?.type === 'plane') {
 | 
			
		||||
            const codeRef = artifact.codeRef
 | 
			
		||||
            if (err(codeRef)) return
 | 
			
		||||
            editorManager.setHighlightRange([codeRef.range])
 | 
			
		||||
          } else {
 | 
			
		||||
            editorManager.setHighlightRange([[0, 0]])
 | 
			
		||||
          if (codeRefs) {
 | 
			
		||||
            editorManager.setHighlightRange(codeRefs.map(({ range }) => range))
 | 
			
		||||
          }
 | 
			
		||||
        } else if (
 | 
			
		||||
          !editorManager.highlightRange ||
 | 
			
		||||
@ -108,6 +54,7 @@ export function useEngineConnectionSubscriptions() {
 | 
			
		||||
      event: 'select_with_point',
 | 
			
		||||
      callback: (engineEvent) => {
 | 
			
		||||
        ;(async () => {
 | 
			
		||||
          if (stateRef.current.matches('Sketch no face')) return
 | 
			
		||||
          const event = await getEventForSelectWithPoint(engineEvent)
 | 
			
		||||
          event && send(event)
 | 
			
		||||
        })().catch(reportRejection)
 | 
			
		||||
 | 
			
		||||
@ -28,14 +28,16 @@ export function useConvertToVariable(range?: SourceRange) {
 | 
			
		||||
 | 
			
		||||
    const meta = isNodeSafeToReplace(
 | 
			
		||||
      parsed,
 | 
			
		||||
      range || context.selectionRanges.codeBasedSelections?.[0]?.range || []
 | 
			
		||||
      range ||
 | 
			
		||||
        context.selectionRanges.graphSelections?.[0]?.codeRef?.range ||
 | 
			
		||||
        []
 | 
			
		||||
    )
 | 
			
		||||
    if (trap(meta)) return
 | 
			
		||||
 | 
			
		||||
    const { isSafe, value } = meta
 | 
			
		||||
    const canReplace = isSafe && value.type !== 'Identifier'
 | 
			
		||||
    const isOnlyOneSelection =
 | 
			
		||||
      !!range || context.selectionRanges.codeBasedSelections.length === 1
 | 
			
		||||
      !!range || context.selectionRanges.graphSelections.length === 1
 | 
			
		||||
 | 
			
		||||
    setEnabled(canReplace && isOnlyOneSelection)
 | 
			
		||||
  }, [context.selectionRanges])
 | 
			
		||||
@ -52,7 +54,7 @@ export function useConvertToVariable(range?: SourceRange) {
 | 
			
		||||
        moveValueIntoNewVariable(
 | 
			
		||||
          ast,
 | 
			
		||||
          kclManager.programMemory,
 | 
			
		||||
          range || context.selectionRanges.codeBasedSelections[0].range,
 | 
			
		||||
          range || context.selectionRanges.graphSelections[0]?.codeRef?.range,
 | 
			
		||||
          variableName
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -463,7 +463,7 @@ export class KclManager {
 | 
			
		||||
 | 
			
		||||
    if (optionalParams?.focusPath) {
 | 
			
		||||
      returnVal = {
 | 
			
		||||
        codeBasedSelections: [],
 | 
			
		||||
        graphSelections: [],
 | 
			
		||||
        otherSelections: [],
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -485,9 +485,11 @@ export class KclManager {
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
        if (start && end) {
 | 
			
		||||
          returnVal.codeBasedSelections.push({
 | 
			
		||||
            type: 'default',
 | 
			
		||||
          returnVal.graphSelections.push({
 | 
			
		||||
            codeRef: {
 | 
			
		||||
              range: [start, end],
 | 
			
		||||
              pathToNode: path,
 | 
			
		||||
            },
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -81,7 +81,7 @@ export async function executeAst({
 | 
			
		||||
          false
 | 
			
		||||
        ))
 | 
			
		||||
 | 
			
		||||
    await engineCommandManager.waitForAllCommands()
 | 
			
		||||
    await engineCommandManager.waitForAllCommands(useFakeExecutor)
 | 
			
		||||
    return {
 | 
			
		||||
      logs: [],
 | 
			
		||||
      errors: [],
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,7 @@ import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
 | 
			
		||||
import { err } from 'lib/trap'
 | 
			
		||||
import { SimplifiedArgDetails } from './std/stdTypes'
 | 
			
		||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
 | 
			
		||||
import { Artifact, codeRefFromRange } from './std/artifactGraph'
 | 
			
		||||
 | 
			
		||||
beforeAll(async () => {
 | 
			
		||||
  await initPromise
 | 
			
		||||
@ -735,7 +736,7 @@ sketch003 = startSketchOn('XZ')
 | 
			
		||||
  |> close(%)`,
 | 
			
		||||
        codeAfter: `myVar = 5\n`,
 | 
			
		||||
        lineOfInterest: 'line([-2.94, 2.7], %)',
 | 
			
		||||
        type: 'default',
 | 
			
		||||
        type: 'segment',
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    [
 | 
			
		||||
@ -761,7 +762,7 @@ const extrude001 = extrude(10, sketch001)`,
 | 
			
		||||
  |> line([-17.67, 0.85], %)
 | 
			
		||||
  |> close(%)\n`,
 | 
			
		||||
        lineOfInterest: 'line([2.66, 1.17], %)',
 | 
			
		||||
        type: 'extrude-wall',
 | 
			
		||||
        type: 'wall',
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    [
 | 
			
		||||
@ -817,7 +818,7 @@ sketch002 = startSketchOn({
 | 
			
		||||
  |> close(%)
 | 
			
		||||
`,
 | 
			
		||||
        lineOfInterest: 'line([-11.18, -2.15], %)',
 | 
			
		||||
        type: 'extrude-wall',
 | 
			
		||||
        type: 'wall',
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    [
 | 
			
		||||
@ -873,7 +874,7 @@ sketch002 = startSketchOn({
 | 
			
		||||
  |> close(%)
 | 
			
		||||
`,
 | 
			
		||||
        lineOfInterest: 'startProfileAt([4.46, 5.12], %, $tag)',
 | 
			
		||||
        type: 'end-cap',
 | 
			
		||||
        type: 'cap',
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  ] as const
 | 
			
		||||
@ -890,11 +891,12 @@ sketch002 = startSketchOn({
 | 
			
		||||
        codeBefore.indexOf(lineOfInterest),
 | 
			
		||||
        codeBefore.indexOf(lineOfInterest) + lineOfInterest.length,
 | 
			
		||||
      ]
 | 
			
		||||
      const artifact = { type } as Artifact
 | 
			
		||||
      const newAst = await deleteFromSelection(
 | 
			
		||||
        ast,
 | 
			
		||||
        {
 | 
			
		||||
          range,
 | 
			
		||||
          type,
 | 
			
		||||
          codeRef: codeRefFromRange(range, ast),
 | 
			
		||||
          artifact,
 | 
			
		||||
        },
 | 
			
		||||
        execState.memory,
 | 
			
		||||
        async () => {
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import { Selection } from 'lib/selections'
 | 
			
		||||
import { err, reportRejection, trap } from 'lib/trap'
 | 
			
		||||
import { Selection } from 'lib/selections'
 | 
			
		||||
import {
 | 
			
		||||
  Program,
 | 
			
		||||
  CallExpression,
 | 
			
		||||
@ -836,7 +836,7 @@ export function createBinaryExpressionWithUnary([left, right]: [
 | 
			
		||||
 | 
			
		||||
export function giveSketchFnCallTag(
 | 
			
		||||
  ast: Node<Program>,
 | 
			
		||||
  range: Selection['range'],
 | 
			
		||||
  range: SourceRange,
 | 
			
		||||
  tag?: string
 | 
			
		||||
):
 | 
			
		||||
  | {
 | 
			
		||||
@ -910,7 +910,7 @@ export function moveValueIntoNewVariablePath(
 | 
			
		||||
export function moveValueIntoNewVariable(
 | 
			
		||||
  ast: Node<Program>,
 | 
			
		||||
  programMemory: ProgramMemory,
 | 
			
		||||
  sourceRange: Selection['range'],
 | 
			
		||||
  sourceRange: SourceRange,
 | 
			
		||||
  variableName: string
 | 
			
		||||
): {
 | 
			
		||||
  modifiedAst: Node<Program>
 | 
			
		||||
@ -1035,18 +1035,15 @@ export async function deleteFromSelection(
 | 
			
		||||
    ({} as any)
 | 
			
		||||
): Promise<Node<Program> | Error> {
 | 
			
		||||
  const astClone = structuredClone(ast)
 | 
			
		||||
  const range = selection.range
 | 
			
		||||
  const path = getNodePathFromSourceRange(ast, range)
 | 
			
		||||
  const varDec = getNodeFromPath<VariableDeclarator>(
 | 
			
		||||
    ast,
 | 
			
		||||
    path,
 | 
			
		||||
    selection?.codeRef?.pathToNode,
 | 
			
		||||
    'VariableDeclarator'
 | 
			
		||||
  )
 | 
			
		||||
  if (err(varDec)) return varDec
 | 
			
		||||
  if (
 | 
			
		||||
    (selection.type === 'extrude-wall' ||
 | 
			
		||||
      selection.type === 'end-cap' ||
 | 
			
		||||
      selection.type === 'start-cap') &&
 | 
			
		||||
    (selection?.artifact?.type === 'wall' ||
 | 
			
		||||
      selection?.artifact?.type === 'cap') &&
 | 
			
		||||
    varDec.node.init.type === 'PipeExpression'
 | 
			
		||||
  ) {
 | 
			
		||||
    const varDecName = varDec.node.id.name
 | 
			
		||||
@ -1126,7 +1123,6 @@ export async function deleteFromSelection(
 | 
			
		||||
              sketchName
 | 
			
		||||
            )
 | 
			
		||||
            if (err(sketchToPreserve)) return sketchToPreserve
 | 
			
		||||
            console.log('sketchName', sketchName)
 | 
			
		||||
            // Can't kick off multiple requests at once as getFaceDetails
 | 
			
		||||
            // is three engine calls in one and they conflict
 | 
			
		||||
            const faceDetails = await getFaceDetails(sketchToPreserve.on.id)
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ import {
 | 
			
		||||
  getPathToExtrudeForSegmentSelection,
 | 
			
		||||
  hasValidFilletSelection,
 | 
			
		||||
  isTagUsedInFillet,
 | 
			
		||||
  modifyAstCloneWithFilletAndTag,
 | 
			
		||||
  modifyAstWithFilletAndTag,
 | 
			
		||||
} from './addFillet'
 | 
			
		||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
 | 
			
		||||
import { createLiteral } from 'lang/modifyAst'
 | 
			
		||||
@ -22,6 +22,8 @@ import { Selections } from 'lib/selections'
 | 
			
		||||
import { engineCommandManager, kclManager } from 'lib/singletons'
 | 
			
		||||
import { VITE_KC_DEV_TOKEN } from 'env'
 | 
			
		||||
import { KclCommandValue } from 'lib/commandTypes'
 | 
			
		||||
import { isOverlap } from 'lib/utils'
 | 
			
		||||
import { codeRefFromRange } from 'lang/std/artifactGraph'
 | 
			
		||||
 | 
			
		||||
beforeAll(async () => {
 | 
			
		||||
  await initPromise
 | 
			
		||||
@ -114,10 +116,9 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
 | 
			
		||||
    code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
 | 
			
		||||
  ]
 | 
			
		||||
  const selection: Selections = {
 | 
			
		||||
    codeBasedSelections: [
 | 
			
		||||
    graphSelections: [
 | 
			
		||||
      {
 | 
			
		||||
        range: segmentRange,
 | 
			
		||||
        type: 'default',
 | 
			
		||||
        codeRef: codeRefFromRange(segmentRange, ast),
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    otherSelections: [],
 | 
			
		||||
@ -272,13 +273,6 @@ const runModifyAstCloneWithFilletAndTag = async (
 | 
			
		||||
      code.indexOf(selectionSnippet) + selectionSnippet.length,
 | 
			
		||||
    ]
 | 
			
		||||
  )
 | 
			
		||||
  const selection: Selections = {
 | 
			
		||||
    codeBasedSelections: segmentRanges.map((segmentRange) => ({
 | 
			
		||||
      range: segmentRange,
 | 
			
		||||
      type: 'default',
 | 
			
		||||
    })),
 | 
			
		||||
    otherSelections: [],
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // radius
 | 
			
		||||
  const radius: KclCommandValue = {
 | 
			
		||||
@ -289,9 +283,24 @@ const runModifyAstCloneWithFilletAndTag = async (
 | 
			
		||||
 | 
			
		||||
  // executeAst
 | 
			
		||||
  await kclManager.executeAst({ ast })
 | 
			
		||||
  const artifactGraph = engineCommandManager.artifactGraph
 | 
			
		||||
 | 
			
		||||
  const selection: Selections = {
 | 
			
		||||
    graphSelections: segmentRanges.map((segmentRange) => {
 | 
			
		||||
      const maybeArtifact = [...artifactGraph].find(([, a]) => {
 | 
			
		||||
        if (!('codeRef' in a)) return false
 | 
			
		||||
        return isOverlap(a.codeRef.range, segmentRange)
 | 
			
		||||
      })
 | 
			
		||||
      return {
 | 
			
		||||
        codeRef: codeRefFromRange(segmentRange, ast),
 | 
			
		||||
        artifact: maybeArtifact ? maybeArtifact[1] : undefined,
 | 
			
		||||
      }
 | 
			
		||||
    }),
 | 
			
		||||
    otherSelections: [],
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // apply fillet to selection
 | 
			
		||||
  const result = modifyAstCloneWithFilletAndTag(ast, selection, radius)
 | 
			
		||||
  const result = modifyAstWithFilletAndTag(ast, selection, radius)
 | 
			
		||||
  if (err(result)) {
 | 
			
		||||
    return result
 | 
			
		||||
  }
 | 
			
		||||
@ -612,7 +621,6 @@ describe('Testing button states', () => {
 | 
			
		||||
    }
 | 
			
		||||
    const ast = astOrError
 | 
			
		||||
 | 
			
		||||
    // selectionRanges
 | 
			
		||||
    const range: [number, number] = segmentSnippet
 | 
			
		||||
      ? [
 | 
			
		||||
          code.indexOf(segmentSnippet),
 | 
			
		||||
@ -621,10 +629,9 @@ describe('Testing button states', () => {
 | 
			
		||||
      : [ast.end, ast.end] // empty line in the end of the code
 | 
			
		||||
 | 
			
		||||
    const selectionRanges: Selections = {
 | 
			
		||||
      codeBasedSelections: [
 | 
			
		||||
      graphSelections: [
 | 
			
		||||
        {
 | 
			
		||||
          range,
 | 
			
		||||
          type: 'default',
 | 
			
		||||
          codeRef: codeRefFromRange(range, ast),
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
      otherSelections: [],
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,7 @@ import { err, trap } from 'lib/trap'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { KclCommandValue } from 'lib/commandTypes'
 | 
			
		||||
import {
 | 
			
		||||
  Artifact,
 | 
			
		||||
  ArtifactGraph,
 | 
			
		||||
  getSweepFromSuspectedPath,
 | 
			
		||||
} from 'lang/std/artifactGraph'
 | 
			
		||||
@ -51,7 +52,7 @@ export function applyFilletToSelection(
 | 
			
		||||
  radius: KclCommandValue
 | 
			
		||||
): void | Error {
 | 
			
		||||
  // 1. clone and modify with fillet and tag
 | 
			
		||||
  const result = modifyAstCloneWithFilletAndTag(ast, selection, radius)
 | 
			
		||||
  const result = modifyAstWithFilletAndTag(ast, selection, radius)
 | 
			
		||||
  if (err(result)) return result
 | 
			
		||||
  const { modifiedAst, pathToFilletNode } = result
 | 
			
		||||
 | 
			
		||||
@ -60,9 +61,9 @@ export function applyFilletToSelection(
 | 
			
		||||
  updateAstAndFocus(modifiedAst, pathToFilletNode)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function modifyAstCloneWithFilletAndTag(
 | 
			
		||||
export function modifyAstWithFilletAndTag(
 | 
			
		||||
  ast: Node<Program>,
 | 
			
		||||
  selection: Selections,
 | 
			
		||||
  selections: Selections,
 | 
			
		||||
  radius: KclCommandValue
 | 
			
		||||
): { modifiedAst: Node<Program>; pathToFilletNode: Array<PathToNode> } | Error {
 | 
			
		||||
  let clonedAst = structuredClone(ast)
 | 
			
		||||
@ -76,16 +77,15 @@ export function modifyAstCloneWithFilletAndTag(
 | 
			
		||||
  // Step 1: modify ast with tags and group them by extrude nodes (bodies)
 | 
			
		||||
  const extrudeToTagsMap: Map<
 | 
			
		||||
    PathToNode,
 | 
			
		||||
    Array<{ tag: string; selectionType: string }>
 | 
			
		||||
    Array<{ tag: string; artifact: Artifact }>
 | 
			
		||||
  > = new Map()
 | 
			
		||||
  const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
 | 
			
		||||
 | 
			
		||||
  for (const selectionRange of selection.codeBasedSelections) {
 | 
			
		||||
  for (const selection of selections.graphSelections) {
 | 
			
		||||
    const singleSelection = {
 | 
			
		||||
      codeBasedSelections: [selectionRange],
 | 
			
		||||
      graphSelections: [selection],
 | 
			
		||||
      otherSelections: [],
 | 
			
		||||
    }
 | 
			
		||||
    const selectionType = singleSelection.codeBasedSelections[0].type
 | 
			
		||||
 | 
			
		||||
    const result = getPathToExtrudeForSegmentSelection(
 | 
			
		||||
      clonedAstForGetExtrude,
 | 
			
		||||
@ -101,18 +101,21 @@ export function modifyAstCloneWithFilletAndTag(
 | 
			
		||||
    )
 | 
			
		||||
    if (err(tagResult)) return tagResult
 | 
			
		||||
    const { tag } = tagResult
 | 
			
		||||
    const tagInfo = { tag, selectionType }
 | 
			
		||||
 | 
			
		||||
    // Group tags by their corresponding extrude node
 | 
			
		||||
    const extrudeKey = JSON.stringify(pathToExtrudeNode)
 | 
			
		||||
 | 
			
		||||
    if (lookupMap.has(extrudeKey)) {
 | 
			
		||||
    if (lookupMap.has(extrudeKey) && selection.artifact) {
 | 
			
		||||
      const existingPath = lookupMap.get(extrudeKey)
 | 
			
		||||
      if (!existingPath) return new Error('Path to extrude node not found.')
 | 
			
		||||
      extrudeToTagsMap.get(existingPath)?.push(tagInfo)
 | 
			
		||||
    } else {
 | 
			
		||||
      extrudeToTagsMap
 | 
			
		||||
        .get(existingPath)
 | 
			
		||||
        ?.push({ tag, artifact: selection.artifact } as const)
 | 
			
		||||
    } else if (selection.artifact) {
 | 
			
		||||
      lookupMap.set(extrudeKey, pathToExtrudeNode)
 | 
			
		||||
      extrudeToTagsMap.set(pathToExtrudeNode, [tagInfo])
 | 
			
		||||
      extrudeToTagsMap.set(pathToExtrudeNode, [
 | 
			
		||||
        { tag, artifact: selection.artifact } as const,
 | 
			
		||||
      ])
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -123,8 +126,8 @@ export function modifyAstCloneWithFilletAndTag(
 | 
			
		||||
    const radiusValue =
 | 
			
		||||
      'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst
 | 
			
		||||
 | 
			
		||||
    const tagCalls = tagInfos.map(({ tag, selectionType }) => {
 | 
			
		||||
      return getEdgeTagCall(tag, selectionType)
 | 
			
		||||
    const tagCalls = tagInfos.map(({ tag, artifact }) => {
 | 
			
		||||
      return getEdgeTagCall(tag, artifact)
 | 
			
		||||
    })
 | 
			
		||||
    const firstTag = tagCalls[0] // can be Identifier or CallExpression (for opposite and adjacent edges)
 | 
			
		||||
 | 
			
		||||
@ -214,7 +217,7 @@ export function getPathToExtrudeForSegmentSelection(
 | 
			
		||||
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
 | 
			
		||||
  const pathToSegmentNode = getNodePathFromSourceRange(
 | 
			
		||||
    ast,
 | 
			
		||||
    selection.codeBasedSelections[0].range
 | 
			
		||||
    selection.graphSelections[0]?.codeRef?.range
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const varDecNode = getNodeFromPath<VariableDeclaration>(
 | 
			
		||||
@ -292,14 +295,14 @@ function mutateAstWithTagForSketchSegment(
 | 
			
		||||
 | 
			
		||||
function getEdgeTagCall(
 | 
			
		||||
  tag: string,
 | 
			
		||||
  selectionType: string
 | 
			
		||||
  artifact: Artifact
 | 
			
		||||
): Node<Identifier | CallExpression> {
 | 
			
		||||
  let tagCall: Expr = createIdentifier(tag)
 | 
			
		||||
 | 
			
		||||
  // Modify the tag based on selectionType
 | 
			
		||||
  if (selectionType === 'edge') {
 | 
			
		||||
  if (artifact.type === 'sweepEdge' && artifact.subType === 'opposite') {
 | 
			
		||||
    tagCall = createCallExpressionStdLib('getOppositeEdge', [tagCall])
 | 
			
		||||
  } else if (selectionType === 'adjacent-edge') {
 | 
			
		||||
  } else if (artifact.type === 'sweepEdge' && artifact.subType === 'adjacent') {
 | 
			
		||||
    tagCall = createCallExpressionStdLib('getNextAdjacentEdge', [tagCall])
 | 
			
		||||
  }
 | 
			
		||||
  return tagCall
 | 
			
		||||
@ -442,22 +445,21 @@ export const hasValidFilletSelection = ({
 | 
			
		||||
  if (!extrudeExists) return false
 | 
			
		||||
 | 
			
		||||
  // check if nothing is selected
 | 
			
		||||
  if (selectionRanges.codeBasedSelections.length === 0) {
 | 
			
		||||
  if (selectionRanges.graphSelections.length === 0) {
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // check if selection is last string in code
 | 
			
		||||
  if (selectionRanges.codeBasedSelections[0].range[0] === code.length) {
 | 
			
		||||
  if (selectionRanges.graphSelections[0]?.codeRef?.range[0] === code.length) {
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // selection exists:
 | 
			
		||||
  for (const selection of selectionRanges.codeBasedSelections) {
 | 
			
		||||
  for (const selection of selectionRanges.graphSelections) {
 | 
			
		||||
    // check if all selections are in sketchLineHelperMap
 | 
			
		||||
    const path = getNodePathFromSourceRange(ast, selection.range)
 | 
			
		||||
    const segmentNode = getNodeFromPath<Node<CallExpression>>(
 | 
			
		||||
      ast,
 | 
			
		||||
      path,
 | 
			
		||||
      selection.codeRef.pathToNode,
 | 
			
		||||
      'CallExpression'
 | 
			
		||||
    )
 | 
			
		||||
    if (err(segmentNode)) return false
 | 
			
		||||
@ -493,9 +495,9 @@ export const hasValidFilletSelection = ({
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    // check if tag is used in fillet
 | 
			
		||||
    if (tagExists) {
 | 
			
		||||
    if (tagExists && selection.artifact) {
 | 
			
		||||
      // create tag call
 | 
			
		||||
      let tagCall: Expr = getEdgeTagCall(tag, selection.type)
 | 
			
		||||
      let tagCall: Expr = getEdgeTagCall(tag, selection.artifact)
 | 
			
		||||
 | 
			
		||||
      // check if tag is used in fillet
 | 
			
		||||
      let inFillet = false
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ import {
 | 
			
		||||
  createPipeSubstitution,
 | 
			
		||||
} from './modifyAst'
 | 
			
		||||
import { err } from 'lib/trap'
 | 
			
		||||
import { codeRefFromRange } from './std/artifactGraph'
 | 
			
		||||
 | 
			
		||||
beforeAll(async () => {
 | 
			
		||||
  await initPromise
 | 
			
		||||
@ -365,7 +366,9 @@ part001 = startSketchAt([-1.41, 3.46])
 | 
			
		||||
    const result = doesPipeHaveCallExp({
 | 
			
		||||
      calleeName: 'close',
 | 
			
		||||
      ast,
 | 
			
		||||
      selection: { type: 'default', range: [100, 101] },
 | 
			
		||||
      selection: {
 | 
			
		||||
        codeRef: codeRefFromRange([100, 101], ast),
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
    expect(result).toEqual(true)
 | 
			
		||||
  })
 | 
			
		||||
@ -385,7 +388,9 @@ part001 = startSketchAt([-1.41, 3.46])
 | 
			
		||||
    const result = doesPipeHaveCallExp({
 | 
			
		||||
      calleeName: 'extrude',
 | 
			
		||||
      ast,
 | 
			
		||||
      selection: { type: 'default', range: [100, 101] },
 | 
			
		||||
      selection: {
 | 
			
		||||
        codeRef: codeRefFromRange([100, 101], ast),
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
    expect(result).toEqual(true)
 | 
			
		||||
  })
 | 
			
		||||
@ -403,7 +408,9 @@ part001 = startSketchAt([-1.41, 3.46])
 | 
			
		||||
    const result = doesPipeHaveCallExp({
 | 
			
		||||
      calleeName: 'close',
 | 
			
		||||
      ast,
 | 
			
		||||
      selection: { type: 'default', range: [100, 101] },
 | 
			
		||||
      selection: {
 | 
			
		||||
        codeRef: codeRefFromRange([100, 101], ast),
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
    expect(result).toEqual(false)
 | 
			
		||||
  })
 | 
			
		||||
@ -415,7 +422,9 @@ part001 = startSketchAt([-1.41, 3.46])
 | 
			
		||||
    const result = doesPipeHaveCallExp({
 | 
			
		||||
      calleeName: 'close',
 | 
			
		||||
      ast,
 | 
			
		||||
      selection: { type: 'default', range: [9, 10] },
 | 
			
		||||
      selection: {
 | 
			
		||||
        codeRef: codeRefFromRange([9, 10], ast),
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
    expect(result).toEqual(false)
 | 
			
		||||
  })
 | 
			
		||||
@ -435,7 +444,9 @@ part001 = startSketchAt([-1.41, 3.46])
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const result = hasExtrudeSketch({
 | 
			
		||||
      ast,
 | 
			
		||||
      selection: { type: 'default', range: [100, 101] },
 | 
			
		||||
      selection: {
 | 
			
		||||
        codeRef: codeRefFromRange([100, 101], ast),
 | 
			
		||||
      },
 | 
			
		||||
      programMemory: execState.memory,
 | 
			
		||||
    })
 | 
			
		||||
    expect(result).toEqual(true)
 | 
			
		||||
@ -454,7 +465,9 @@ part001 = startSketchAt([-1.41, 3.46])
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const result = hasExtrudeSketch({
 | 
			
		||||
      ast,
 | 
			
		||||
      selection: { type: 'default', range: [100, 101] },
 | 
			
		||||
      selection: {
 | 
			
		||||
        codeRef: codeRefFromRange([100, 101], ast),
 | 
			
		||||
      },
 | 
			
		||||
      programMemory: execState.memory,
 | 
			
		||||
    })
 | 
			
		||||
    expect(result).toEqual(true)
 | 
			
		||||
@ -467,7 +480,9 @@ part001 = startSketchAt([-1.41, 3.46])
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const result = hasExtrudeSketch({
 | 
			
		||||
      ast,
 | 
			
		||||
      selection: { type: 'default', range: [10, 11] },
 | 
			
		||||
      selection: {
 | 
			
		||||
        codeRef: codeRefFromRange([10, 11], ast),
 | 
			
		||||
      },
 | 
			
		||||
      programMemory: execState.memory,
 | 
			
		||||
    })
 | 
			
		||||
    expect(result).toEqual(false)
 | 
			
		||||
@ -556,8 +571,7 @@ sketch003 = startSketchOn(extrude001, 'END')
 | 
			
		||||
      exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
 | 
			
		||||
    const extruded = hasSketchPipeBeenExtruded(
 | 
			
		||||
      {
 | 
			
		||||
        range: [characterIndex, characterIndex],
 | 
			
		||||
        type: 'default',
 | 
			
		||||
        codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
 | 
			
		||||
      },
 | 
			
		||||
      ast
 | 
			
		||||
    )
 | 
			
		||||
@ -571,8 +585,7 @@ sketch003 = startSketchOn(extrude001, 'END')
 | 
			
		||||
      exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
 | 
			
		||||
    const extruded = hasSketchPipeBeenExtruded(
 | 
			
		||||
      {
 | 
			
		||||
        range: [characterIndex, characterIndex],
 | 
			
		||||
        type: 'default',
 | 
			
		||||
        codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
 | 
			
		||||
      },
 | 
			
		||||
      ast
 | 
			
		||||
    )
 | 
			
		||||
@ -586,8 +599,7 @@ sketch003 = startSketchOn(extrude001, 'END')
 | 
			
		||||
      exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
 | 
			
		||||
    const extruded = hasSketchPipeBeenExtruded(
 | 
			
		||||
      {
 | 
			
		||||
        range: [characterIndex, characterIndex],
 | 
			
		||||
        type: 'default',
 | 
			
		||||
        codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
 | 
			
		||||
      },
 | 
			
		||||
      ast
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,7 @@ import {
 | 
			
		||||
import { err, Reason } from 'lib/trap'
 | 
			
		||||
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
 | 
			
		||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
 | 
			
		||||
import { ArtifactGraph, codeRefFromRange } from './std/artifactGraph'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
 | 
			
		||||
@ -130,7 +131,7 @@ function moreNodePathFromSourceRange(
 | 
			
		||||
    | VariableDeclaration
 | 
			
		||||
    | ReturnStatement
 | 
			
		||||
  >,
 | 
			
		||||
  sourceRange: Selection['range'],
 | 
			
		||||
  sourceRange: SourceRange,
 | 
			
		||||
  previousPath: PathToNode = [['body', '']]
 | 
			
		||||
): PathToNode {
 | 
			
		||||
  const [start, end] = sourceRange
 | 
			
		||||
@ -381,7 +382,7 @@ function moreNodePathFromSourceRange(
 | 
			
		||||
 | 
			
		||||
export function getNodePathFromSourceRange(
 | 
			
		||||
  node: Program,
 | 
			
		||||
  sourceRange: Selection['range'],
 | 
			
		||||
  sourceRange: SourceRange,
 | 
			
		||||
  previousPath: PathToNode = [['body', '']]
 | 
			
		||||
): PathToNode {
 | 
			
		||||
  const [start, end] = sourceRange || []
 | 
			
		||||
@ -560,7 +561,7 @@ export function findAllPreviousVariablesPath(
 | 
			
		||||
export function findAllPreviousVariables(
 | 
			
		||||
  ast: Program,
 | 
			
		||||
  programMemory: ProgramMemory,
 | 
			
		||||
  sourceRange: Selection['range'],
 | 
			
		||||
  sourceRange: SourceRange,
 | 
			
		||||
  type: 'number' | 'string' = 'number'
 | 
			
		||||
): {
 | 
			
		||||
  variables: PrevVariable<typeof type extends 'number' ? number : string>[]
 | 
			
		||||
@ -705,19 +706,26 @@ export function isValueZero(val?: Expr): boolean {
 | 
			
		||||
 | 
			
		||||
export function isLinesParallelAndConstrained(
 | 
			
		||||
  ast: Program,
 | 
			
		||||
  artifactGraph: ArtifactGraph,
 | 
			
		||||
  programMemory: ProgramMemory,
 | 
			
		||||
  primaryLine: Selection,
 | 
			
		||||
  secondaryLine: Selection
 | 
			
		||||
):
 | 
			
		||||
  | {
 | 
			
		||||
      isParallelAndConstrained: boolean
 | 
			
		||||
      sourceRange: SourceRange
 | 
			
		||||
      selection: Selection | null
 | 
			
		||||
    }
 | 
			
		||||
  | Error {
 | 
			
		||||
  try {
 | 
			
		||||
    const EPSILON = 0.005
 | 
			
		||||
    const primaryPath = getNodePathFromSourceRange(ast, primaryLine.range)
 | 
			
		||||
    const secondaryPath = getNodePathFromSourceRange(ast, secondaryLine.range)
 | 
			
		||||
    const primaryPath = getNodePathFromSourceRange(
 | 
			
		||||
      ast,
 | 
			
		||||
      primaryLine?.codeRef?.range
 | 
			
		||||
    )
 | 
			
		||||
    const secondaryPath = getNodePathFromSourceRange(
 | 
			
		||||
      ast,
 | 
			
		||||
      secondaryLine?.codeRef?.range
 | 
			
		||||
    )
 | 
			
		||||
    const _secondaryNode = getNodeFromPath<CallExpression>(
 | 
			
		||||
      ast,
 | 
			
		||||
      secondaryPath,
 | 
			
		||||
@ -733,12 +741,15 @@ export function isLinesParallelAndConstrained(
 | 
			
		||||
    if (err(sg)) return sg
 | 
			
		||||
    const _primarySegment = getSketchSegmentFromSourceRange(
 | 
			
		||||
      sg,
 | 
			
		||||
      primaryLine.range
 | 
			
		||||
      primaryLine?.codeRef?.range
 | 
			
		||||
    )
 | 
			
		||||
    if (err(_primarySegment)) return _primarySegment
 | 
			
		||||
    const primarySegment = _primarySegment.segment
 | 
			
		||||
 | 
			
		||||
    const _segment = getSketchSegmentFromSourceRange(sg, secondaryLine.range)
 | 
			
		||||
    const _segment = getSketchSegmentFromSourceRange(
 | 
			
		||||
      sg,
 | 
			
		||||
      secondaryLine?.codeRef?.range
 | 
			
		||||
    )
 | 
			
		||||
    if (err(_segment)) return _segment
 | 
			
		||||
    const { segment: secondarySegment, index: secondaryIndex } = _segment
 | 
			
		||||
    const primaryAngle = getAngle(primarySegment.from, primarySegment.to)
 | 
			
		||||
@ -751,7 +762,7 @@ export function isLinesParallelAndConstrained(
 | 
			
		||||
      Math.abs(primaryAngle - secondaryAngle) < EPSILON ||
 | 
			
		||||
      Math.abs(primaryAngle - secondaryAngleAlt) < EPSILON
 | 
			
		||||
 | 
			
		||||
    // is secordary line fully constrain, or has constrain type of 'angle'
 | 
			
		||||
    // is secondary line fully constrain, or has constrain type of 'angle'
 | 
			
		||||
    const secondaryFirstArg = getFirstArg(secondaryNode)
 | 
			
		||||
    if (err(secondaryFirstArg)) return secondaryFirstArg
 | 
			
		||||
 | 
			
		||||
@ -761,14 +772,14 @@ export function isLinesParallelAndConstrained(
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    const constraintLevelMeta = getConstraintLevelFromSourceRange(
 | 
			
		||||
      secondaryLine.range,
 | 
			
		||||
      secondaryLine?.codeRef.range,
 | 
			
		||||
      ast
 | 
			
		||||
    )
 | 
			
		||||
    if (err(constraintLevelMeta)) {
 | 
			
		||||
      console.error(constraintLevelMeta)
 | 
			
		||||
      return {
 | 
			
		||||
        isParallelAndConstrained: false,
 | 
			
		||||
        sourceRange: [0, 0],
 | 
			
		||||
        selection: null,
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    const constraintLevel = constraintLevelMeta.level
 | 
			
		||||
@ -785,12 +796,15 @@ export function isLinesParallelAndConstrained(
 | 
			
		||||
 | 
			
		||||
    return {
 | 
			
		||||
      isParallelAndConstrained,
 | 
			
		||||
      sourceRange: prevSourceRange,
 | 
			
		||||
      selection: {
 | 
			
		||||
        codeRef: codeRefFromRange(prevSourceRange, ast),
 | 
			
		||||
        artifact: artifactGraph.get(prevSegment.__geoMeta.id),
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    return {
 | 
			
		||||
      isParallelAndConstrained: false,
 | 
			
		||||
      sourceRange: [0, 0],
 | 
			
		||||
      selection: null,
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -804,10 +818,9 @@ export function doesPipeHaveCallExp({
 | 
			
		||||
  ast: Program
 | 
			
		||||
  selection: Selection
 | 
			
		||||
}): boolean {
 | 
			
		||||
  const pathToNode = getNodePathFromSourceRange(ast, selection.range)
 | 
			
		||||
  const pipeExpressionMeta = getNodeFromPath<PipeExpression>(
 | 
			
		||||
    ast,
 | 
			
		||||
    pathToNode,
 | 
			
		||||
    selection?.codeRef?.pathToNode,
 | 
			
		||||
    'PipeExpression'
 | 
			
		||||
  )
 | 
			
		||||
  if (err(pipeExpressionMeta)) {
 | 
			
		||||
@ -832,10 +845,9 @@ export function hasExtrudeSketch({
 | 
			
		||||
  selection: Selection
 | 
			
		||||
  programMemory: ProgramMemory
 | 
			
		||||
}): boolean {
 | 
			
		||||
  const pathToNode = getNodePathFromSourceRange(ast, selection.range)
 | 
			
		||||
  const varDecMeta = getNodeFromPath<VariableDeclaration>(
 | 
			
		||||
    ast,
 | 
			
		||||
    pathToNode,
 | 
			
		||||
    selection?.codeRef?.pathToNode,
 | 
			
		||||
    'VariableDeclaration'
 | 
			
		||||
  )
 | 
			
		||||
  if (err(varDecMeta)) {
 | 
			
		||||
@ -856,9 +868,9 @@ export function isSingleCursorInPipe(
 | 
			
		||||
  selectionRanges: Selections,
 | 
			
		||||
  ast: Program
 | 
			
		||||
) {
 | 
			
		||||
  if (selectionRanges.codeBasedSelections.length !== 1) return false
 | 
			
		||||
  const selection = selectionRanges.codeBasedSelections[0]
 | 
			
		||||
  const pathToNode = getNodePathFromSourceRange(ast, selection.range)
 | 
			
		||||
  if (selectionRanges.graphSelections.length !== 1) return false
 | 
			
		||||
  const selection = selectionRanges.graphSelections[0]
 | 
			
		||||
  const pathToNode = getNodePathFromSourceRange(ast, selection?.codeRef?.range)
 | 
			
		||||
  const nodeTypes = pathToNode.map(([, type]) => type)
 | 
			
		||||
  if (nodeTypes.includes('FunctionExpression')) return false
 | 
			
		||||
  if (!nodeTypes.includes('VariableDeclaration')) return false
 | 
			
		||||
@ -928,10 +940,9 @@ export function findUsesOfTagInPipe(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
 | 
			
		||||
  const path = getNodePathFromSourceRange(ast, selection.range)
 | 
			
		||||
  const _node = getNodeFromPath<Node<PipeExpression>>(
 | 
			
		||||
    ast,
 | 
			
		||||
    path,
 | 
			
		||||
    selection.codeRef.pathToNode,
 | 
			
		||||
    'PipeExpression'
 | 
			
		||||
  )
 | 
			
		||||
  if (err(_node)) return false
 | 
			
		||||
@ -939,7 +950,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
 | 
			
		||||
  if (pipeExpression.type !== 'PipeExpression') return false
 | 
			
		||||
  const _varDec = getNodeFromPath<VariableDeclarator>(
 | 
			
		||||
    ast,
 | 
			
		||||
    path,
 | 
			
		||||
    selection.codeRef.pathToNode,
 | 
			
		||||
    'VariableDeclarator'
 | 
			
		||||
  )
 | 
			
		||||
  if (err(_varDec)) return false
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ Map {
 | 
			
		||||
        0,
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [
 | 
			
		||||
      "UUID",
 | 
			
		||||
    ],
 | 
			
		||||
@ -35,6 +36,7 @@ Map {
 | 
			
		||||
        0,
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "planeId": "UUID",
 | 
			
		||||
    "segIds": [
 | 
			
		||||
      "UUID",
 | 
			
		||||
@ -65,6 +67,7 @@ Map {
 | 
			
		||||
      "UUID",
 | 
			
		||||
      "UUID",
 | 
			
		||||
    ],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "surfaceId": "UUID",
 | 
			
		||||
    "type": "segment",
 | 
			
		||||
@ -88,6 +91,7 @@ Map {
 | 
			
		||||
      "UUID",
 | 
			
		||||
      "UUID",
 | 
			
		||||
    ],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "surfaceId": "UUID",
 | 
			
		||||
    "type": "segment",
 | 
			
		||||
@ -110,6 +114,7 @@ Map {
 | 
			
		||||
      "UUID",
 | 
			
		||||
      "UUID",
 | 
			
		||||
    ],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "surfaceId": "UUID",
 | 
			
		||||
    "type": "segment",
 | 
			
		||||
@ -132,6 +137,7 @@ Map {
 | 
			
		||||
      "UUID",
 | 
			
		||||
      "UUID",
 | 
			
		||||
    ],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "surfaceId": "UUID",
 | 
			
		||||
    "type": "segment",
 | 
			
		||||
@ -151,10 +157,12 @@ Map {
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    "edgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "type": "segment",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-7" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "type": "solid2D",
 | 
			
		||||
  },
 | 
			
		||||
@ -182,6 +190,7 @@ Map {
 | 
			
		||||
      "UUID",
 | 
			
		||||
      "UUID",
 | 
			
		||||
    ],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "subType": "extrusion",
 | 
			
		||||
    "surfaceIds": [
 | 
			
		||||
@ -196,6 +205,7 @@ Map {
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-9" => {
 | 
			
		||||
    "edgeCutEdgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [],
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
@ -203,6 +213,7 @@ Map {
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-10" => {
 | 
			
		||||
    "edgeCutEdgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [
 | 
			
		||||
      "UUID",
 | 
			
		||||
    ],
 | 
			
		||||
@ -212,6 +223,7 @@ Map {
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-11" => {
 | 
			
		||||
    "edgeCutEdgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [],
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
@ -219,6 +231,7 @@ Map {
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-12" => {
 | 
			
		||||
    "edgeCutEdgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [],
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
@ -226,6 +239,7 @@ Map {
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-13" => {
 | 
			
		||||
    "edgeCutEdgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [],
 | 
			
		||||
    "subType": "start",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
@ -233,54 +247,63 @@ Map {
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-14" => {
 | 
			
		||||
    "edgeCutEdgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [],
 | 
			
		||||
    "subType": "end",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "cap",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-15" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "opposite",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-16" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "adjacent",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-17" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "opposite",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-18" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "adjacent",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-19" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "opposite",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-20" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "adjacent",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-21" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "opposite",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-22" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "adjacent",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
@ -302,6 +325,7 @@ Map {
 | 
			
		||||
    },
 | 
			
		||||
    "consumedEdgeId": "UUID",
 | 
			
		||||
    "edgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "subType": "fillet",
 | 
			
		||||
    "type": "edgeCut",
 | 
			
		||||
  },
 | 
			
		||||
@ -319,6 +343,7 @@ Map {
 | 
			
		||||
        0,
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "planeId": "UUID",
 | 
			
		||||
    "segIds": [
 | 
			
		||||
      "UUID",
 | 
			
		||||
@ -348,6 +373,7 @@ Map {
 | 
			
		||||
      "UUID",
 | 
			
		||||
      "UUID",
 | 
			
		||||
    ],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "surfaceId": "UUID",
 | 
			
		||||
    "type": "segment",
 | 
			
		||||
@ -370,6 +396,7 @@ Map {
 | 
			
		||||
      "UUID",
 | 
			
		||||
      "UUID",
 | 
			
		||||
    ],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "surfaceId": "UUID",
 | 
			
		||||
    "type": "segment",
 | 
			
		||||
@ -392,6 +419,7 @@ Map {
 | 
			
		||||
      "UUID",
 | 
			
		||||
      "UUID",
 | 
			
		||||
    ],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "surfaceId": "UUID",
 | 
			
		||||
    "type": "segment",
 | 
			
		||||
@ -411,10 +439,12 @@ Map {
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
    "edgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "type": "segment",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-29" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "type": "solid2D",
 | 
			
		||||
  },
 | 
			
		||||
@ -440,6 +470,7 @@ Map {
 | 
			
		||||
      "UUID",
 | 
			
		||||
      "UUID",
 | 
			
		||||
    ],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathId": "UUID",
 | 
			
		||||
    "subType": "extrusion",
 | 
			
		||||
    "surfaceIds": [
 | 
			
		||||
@ -453,6 +484,7 @@ Map {
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-31" => {
 | 
			
		||||
    "edgeCutEdgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [],
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
@ -460,6 +492,7 @@ Map {
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-32" => {
 | 
			
		||||
    "edgeCutEdgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [],
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
@ -467,6 +500,7 @@ Map {
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-33" => {
 | 
			
		||||
    "edgeCutEdgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [],
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
@ -474,6 +508,7 @@ Map {
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-34" => {
 | 
			
		||||
    "edgeCutEdgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [],
 | 
			
		||||
    "subType": "start",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
@ -481,42 +516,49 @@ Map {
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-35" => {
 | 
			
		||||
    "edgeCutEdgeIds": [],
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "pathIds": [],
 | 
			
		||||
    "subType": "end",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "cap",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-36" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "opposite",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-37" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "adjacent",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-38" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "opposite",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-39" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "adjacent",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-40" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "opposite",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
    "type": "sweepEdge",
 | 
			
		||||
  },
 | 
			
		||||
  "UUID-41" => {
 | 
			
		||||
    "id": "UUID",
 | 
			
		||||
    "segId": "UUID",
 | 
			
		||||
    "subType": "adjacent",
 | 
			
		||||
    "sweepId": "UUID",
 | 
			
		||||
 | 
			
		||||
@ -438,7 +438,8 @@ async function GraphTheGraph(
 | 
			
		||||
      if (
 | 
			
		||||
        propName === 'type' ||
 | 
			
		||||
        propName === 'codeRef' ||
 | 
			
		||||
        propName === 'subType'
 | 
			
		||||
        propName === 'subType' ||
 | 
			
		||||
        propName === 'id'
 | 
			
		||||
      )
 | 
			
		||||
        return
 | 
			
		||||
      if (Array.isArray(value))
 | 
			
		||||
@ -662,6 +663,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      {
 | 
			
		||||
        type: 'path',
 | 
			
		||||
        segIds: [],
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        planeId: 'UUID-1',
 | 
			
		||||
        sweepId: '',
 | 
			
		||||
        codeRef: {
 | 
			
		||||
@ -675,6 +677,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
        type: 'sweep',
 | 
			
		||||
        subType: 'extrusion',
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        surfaceIds: [],
 | 
			
		||||
        edgeIds: [],
 | 
			
		||||
        codeRef: {
 | 
			
		||||
@ -684,6 +687,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: 'path',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        segIds: expect.any(Array),
 | 
			
		||||
        planeId: expect.any(String),
 | 
			
		||||
        sweepId: expect.any(String),
 | 
			
		||||
@ -697,6 +701,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
    expect(getUpdateObjects('extend_path')).toEqual([
 | 
			
		||||
      {
 | 
			
		||||
        type: 'segment',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceId: '',
 | 
			
		||||
        edgeIds: [],
 | 
			
		||||
@ -707,6 +712,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: 'path',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        segIds: expect.any(Array),
 | 
			
		||||
        planeId: expect.any(String),
 | 
			
		||||
        sweepId: expect.any(String),
 | 
			
		||||
@ -721,6 +727,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      {
 | 
			
		||||
        type: 'edgeCut',
 | 
			
		||||
        subType: 'fillet',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        consumedEdgeId: expect.any(String),
 | 
			
		||||
        edgeIds: [],
 | 
			
		||||
        surfaceId: '',
 | 
			
		||||
@ -731,6 +738,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: 'segment',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceId: expect.any(String),
 | 
			
		||||
        edgeIds: expect.any(Array),
 | 
			
		||||
@ -744,6 +752,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
    expect(getUpdateObjects('solid3d_get_extrusion_face_info')).toEqual([
 | 
			
		||||
      {
 | 
			
		||||
        type: 'wall',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        segId: expect.any(String),
 | 
			
		||||
        edgeCutEdgeIds: [],
 | 
			
		||||
        sweepId: expect.any(String),
 | 
			
		||||
@ -751,6 +760,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: 'segment',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceId: expect.any(String),
 | 
			
		||||
        edgeIds: expect.any(Array),
 | 
			
		||||
@ -762,6 +772,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      {
 | 
			
		||||
        type: 'sweep',
 | 
			
		||||
        subType: 'extrusion',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceIds: expect.any(Array),
 | 
			
		||||
        edgeIds: expect.any(Array),
 | 
			
		||||
@ -772,6 +783,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: 'wall',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        segId: expect.any(String),
 | 
			
		||||
        edgeCutEdgeIds: [],
 | 
			
		||||
        sweepId: expect.any(String),
 | 
			
		||||
@ -779,6 +791,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: 'segment',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceId: expect.any(String),
 | 
			
		||||
        edgeIds: expect.any(Array),
 | 
			
		||||
@ -790,6 +803,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      {
 | 
			
		||||
        type: 'sweep',
 | 
			
		||||
        subType: 'extrusion',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceIds: expect.any(Array),
 | 
			
		||||
        edgeIds: expect.any(Array),
 | 
			
		||||
@ -800,6 +814,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: 'wall',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        segId: expect.any(String),
 | 
			
		||||
        edgeCutEdgeIds: [],
 | 
			
		||||
        sweepId: expect.any(String),
 | 
			
		||||
@ -807,6 +822,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: 'segment',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceId: expect.any(String),
 | 
			
		||||
        edgeIds: expect.any(Array),
 | 
			
		||||
@ -819,6 +835,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      {
 | 
			
		||||
        type: 'sweep',
 | 
			
		||||
        subType: 'extrusion',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceIds: expect.any(Array),
 | 
			
		||||
        edgeIds: expect.any(Array),
 | 
			
		||||
@ -829,6 +846,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: 'wall',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        segId: expect.any(String),
 | 
			
		||||
        edgeCutEdgeIds: [],
 | 
			
		||||
        sweepId: expect.any(String),
 | 
			
		||||
@ -836,6 +854,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        type: 'segment',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceId: expect.any(String),
 | 
			
		||||
        edgeIds: expect.any(Array),
 | 
			
		||||
@ -847,6 +866,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      {
 | 
			
		||||
        type: 'sweep',
 | 
			
		||||
        subType: 'extrusion',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceIds: expect.any(Array),
 | 
			
		||||
        edgeIds: expect.any(Array),
 | 
			
		||||
@ -858,6 +878,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      {
 | 
			
		||||
        type: 'cap',
 | 
			
		||||
        subType: 'start',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        edgeCutEdgeIds: [],
 | 
			
		||||
        sweepId: expect.any(String),
 | 
			
		||||
        pathIds: [],
 | 
			
		||||
@ -865,6 +886,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      {
 | 
			
		||||
        type: 'sweep',
 | 
			
		||||
        subType: 'extrusion',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceIds: expect.any(Array),
 | 
			
		||||
        edgeIds: expect.any(Array),
 | 
			
		||||
@ -876,6 +898,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      {
 | 
			
		||||
        type: 'cap',
 | 
			
		||||
        subType: 'end',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        edgeCutEdgeIds: [],
 | 
			
		||||
        sweepId: expect.any(String),
 | 
			
		||||
        pathIds: [],
 | 
			
		||||
@ -883,6 +906,7 @@ describe('testing getArtifactsToUpdate', () => {
 | 
			
		||||
      {
 | 
			
		||||
        type: 'sweep',
 | 
			
		||||
        subType: 'extrusion',
 | 
			
		||||
        id: expect.any(String),
 | 
			
		||||
        pathId: expect.any(String),
 | 
			
		||||
        surfaceIds: expect.any(Array),
 | 
			
		||||
        edgeIds: expect.any(Array),
 | 
			
		||||
 | 
			
		||||
@ -5,36 +5,40 @@ import { err } from 'lib/trap'
 | 
			
		||||
 | 
			
		||||
export type ArtifactId = string
 | 
			
		||||
 | 
			
		||||
interface CommonCommandProperties {
 | 
			
		||||
interface BaseArtifact {
 | 
			
		||||
  id: ArtifactId
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface CodeRef {
 | 
			
		||||
  range: SourceRange
 | 
			
		||||
  pathToNode: PathToNode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PlaneArtifact {
 | 
			
		||||
export interface PlaneArtifact extends BaseArtifact {
 | 
			
		||||
  type: 'plane'
 | 
			
		||||
  pathIds: Array<ArtifactId>
 | 
			
		||||
  codeRef: CommonCommandProperties
 | 
			
		||||
  codeRef: CodeRef
 | 
			
		||||
}
 | 
			
		||||
export interface PlaneArtifactRich {
 | 
			
		||||
export interface PlaneArtifactRich extends BaseArtifact {
 | 
			
		||||
  type: 'plane'
 | 
			
		||||
  paths: Array<PathArtifact>
 | 
			
		||||
  codeRef: CommonCommandProperties
 | 
			
		||||
  codeRef: CodeRef
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface PathArtifact {
 | 
			
		||||
export interface PathArtifact extends BaseArtifact {
 | 
			
		||||
  type: 'path'
 | 
			
		||||
  planeId: ArtifactId
 | 
			
		||||
  segIds: Array<ArtifactId>
 | 
			
		||||
  sweepId: ArtifactId
 | 
			
		||||
  solid2dId?: ArtifactId
 | 
			
		||||
  codeRef: CommonCommandProperties
 | 
			
		||||
  codeRef: CodeRef
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface solid2D {
 | 
			
		||||
interface solid2D extends BaseArtifact {
 | 
			
		||||
  type: 'solid2D'
 | 
			
		||||
  pathId: ArtifactId
 | 
			
		||||
}
 | 
			
		||||
export interface PathArtifactRich {
 | 
			
		||||
export interface PathArtifactRich extends BaseArtifact {
 | 
			
		||||
  type: 'path'
 | 
			
		||||
  /** A path must always lie on a plane */
 | 
			
		||||
  plane: PlaneArtifact | WallArtifact
 | 
			
		||||
@ -42,52 +46,52 @@ export interface PathArtifactRich {
 | 
			
		||||
  segments: Array<SegmentArtifact>
 | 
			
		||||
  /** A path may not result in a sweep artifact */
 | 
			
		||||
  sweep?: SweepArtifact
 | 
			
		||||
  codeRef: CommonCommandProperties
 | 
			
		||||
  codeRef: CodeRef
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface SegmentArtifact {
 | 
			
		||||
export interface SegmentArtifact extends BaseArtifact {
 | 
			
		||||
  type: 'segment'
 | 
			
		||||
  pathId: ArtifactId
 | 
			
		||||
  surfaceId: ArtifactId
 | 
			
		||||
  edgeIds: Array<ArtifactId>
 | 
			
		||||
  edgeCutId?: ArtifactId
 | 
			
		||||
  codeRef: CommonCommandProperties
 | 
			
		||||
  codeRef: CodeRef
 | 
			
		||||
}
 | 
			
		||||
interface SegmentArtifactRich {
 | 
			
		||||
interface SegmentArtifactRich extends BaseArtifact {
 | 
			
		||||
  type: 'segment'
 | 
			
		||||
  path: PathArtifact
 | 
			
		||||
  surf: WallArtifact
 | 
			
		||||
  edges: Array<SweepEdge>
 | 
			
		||||
  edgeCut?: EdgeCut
 | 
			
		||||
  codeRef: CommonCommandProperties
 | 
			
		||||
  codeRef: CodeRef
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
 | 
			
		||||
interface SweepArtifact {
 | 
			
		||||
interface SweepArtifact extends BaseArtifact {
 | 
			
		||||
  type: 'sweep'
 | 
			
		||||
  subType: 'extrusion' | 'revolve'
 | 
			
		||||
  pathId: string
 | 
			
		||||
  surfaceIds: Array<string>
 | 
			
		||||
  edgeIds: Array<string>
 | 
			
		||||
  codeRef: CommonCommandProperties
 | 
			
		||||
  codeRef: CodeRef
 | 
			
		||||
}
 | 
			
		||||
interface SweepArtifactRich {
 | 
			
		||||
interface SweepArtifactRich extends BaseArtifact {
 | 
			
		||||
  type: 'sweep'
 | 
			
		||||
  subType: 'extrusion' | 'revolve'
 | 
			
		||||
  path: PathArtifact
 | 
			
		||||
  surfaces: Array<WallArtifact | CapArtifact>
 | 
			
		||||
  edges: Array<SweepEdge>
 | 
			
		||||
  codeRef: CommonCommandProperties
 | 
			
		||||
  codeRef: CodeRef
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface WallArtifact {
 | 
			
		||||
interface WallArtifact extends BaseArtifact {
 | 
			
		||||
  type: 'wall'
 | 
			
		||||
  segId: ArtifactId
 | 
			
		||||
  edgeCutEdgeIds: Array<ArtifactId>
 | 
			
		||||
  sweepId: ArtifactId
 | 
			
		||||
  pathIds: Array<ArtifactId>
 | 
			
		||||
}
 | 
			
		||||
interface CapArtifact {
 | 
			
		||||
interface CapArtifact extends BaseArtifact {
 | 
			
		||||
  type: 'cap'
 | 
			
		||||
  subType: 'start' | 'end'
 | 
			
		||||
  edgeCutEdgeIds: Array<ArtifactId>
 | 
			
		||||
@ -95,7 +99,7 @@ interface CapArtifact {
 | 
			
		||||
  pathIds: Array<ArtifactId>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface SweepEdge {
 | 
			
		||||
interface SweepEdge extends BaseArtifact {
 | 
			
		||||
  type: 'sweepEdge'
 | 
			
		||||
  segId: ArtifactId
 | 
			
		||||
  sweepId: ArtifactId
 | 
			
		||||
@ -103,16 +107,16 @@ interface SweepEdge {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** A edgeCut is a more generic term for both fillet or chamfer */
 | 
			
		||||
interface EdgeCut {
 | 
			
		||||
interface EdgeCut extends BaseArtifact {
 | 
			
		||||
  type: 'edgeCut'
 | 
			
		||||
  subType: 'fillet' | 'chamfer'
 | 
			
		||||
  consumedEdgeId: ArtifactId
 | 
			
		||||
  edgeIds: Array<ArtifactId>
 | 
			
		||||
  surfaceId: ArtifactId
 | 
			
		||||
  codeRef: CommonCommandProperties
 | 
			
		||||
  codeRef: CodeRef
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface EdgeCutEdge {
 | 
			
		||||
interface EdgeCutEdge extends BaseArtifact {
 | 
			
		||||
  type: 'edgeCutEdge'
 | 
			
		||||
  edgeCutId: ArtifactId
 | 
			
		||||
  surfaceId: ArtifactId
 | 
			
		||||
@ -257,6 +261,7 @@ export function getArtifactsToUpdate({
 | 
			
		||||
        id,
 | 
			
		||||
        artifact: {
 | 
			
		||||
          type: 'plane',
 | 
			
		||||
          id,
 | 
			
		||||
          pathIds: [],
 | 
			
		||||
          codeRef: { range, pathToNode },
 | 
			
		||||
        },
 | 
			
		||||
@ -274,6 +279,7 @@ export function getArtifactsToUpdate({
 | 
			
		||||
          id: currentPlaneId,
 | 
			
		||||
          artifact: {
 | 
			
		||||
            type: 'wall',
 | 
			
		||||
            id: currentPlaneId,
 | 
			
		||||
            segId: existingPlane.segId,
 | 
			
		||||
            edgeCutEdgeIds: existingPlane.edgeCutEdgeIds,
 | 
			
		||||
            sweepId: existingPlane.sweepId,
 | 
			
		||||
@ -283,7 +289,10 @@ export function getArtifactsToUpdate({
 | 
			
		||||
      ]
 | 
			
		||||
    } else {
 | 
			
		||||
      return [
 | 
			
		||||
        { id: currentPlaneId, artifact: { type: 'plane', pathIds, codeRef } },
 | 
			
		||||
        {
 | 
			
		||||
          id: currentPlaneId,
 | 
			
		||||
          artifact: { type: 'plane', id: currentPlaneId, pathIds, codeRef },
 | 
			
		||||
        },
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  } else if (cmd.type === 'start_path') {
 | 
			
		||||
@ -291,6 +300,7 @@ export function getArtifactsToUpdate({
 | 
			
		||||
      id,
 | 
			
		||||
      artifact: {
 | 
			
		||||
        type: 'path',
 | 
			
		||||
        id,
 | 
			
		||||
        segIds: [],
 | 
			
		||||
        planeId: currentPlaneId,
 | 
			
		||||
        sweepId: '',
 | 
			
		||||
@ -303,7 +313,7 @@ export function getArtifactsToUpdate({
 | 
			
		||||
    if (plane?.type === 'plane') {
 | 
			
		||||
      returnArr.push({
 | 
			
		||||
        id: currentPlaneId,
 | 
			
		||||
        artifact: { type: 'plane', pathIds: [id], codeRef },
 | 
			
		||||
        artifact: { type: 'plane', id: currentPlaneId, pathIds: [id], codeRef },
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    if (plane?.type === 'wall') {
 | 
			
		||||
@ -311,6 +321,7 @@ export function getArtifactsToUpdate({
 | 
			
		||||
        id: currentPlaneId,
 | 
			
		||||
        artifact: {
 | 
			
		||||
          type: 'wall',
 | 
			
		||||
          id: currentPlaneId,
 | 
			
		||||
          segId: plane.segId,
 | 
			
		||||
          edgeCutEdgeIds: plane.edgeCutEdgeIds,
 | 
			
		||||
          sweepId: plane.sweepId,
 | 
			
		||||
@ -325,6 +336,7 @@ export function getArtifactsToUpdate({
 | 
			
		||||
      id,
 | 
			
		||||
      artifact: {
 | 
			
		||||
        type: 'segment',
 | 
			
		||||
        id,
 | 
			
		||||
        pathId,
 | 
			
		||||
        surfaceId: '',
 | 
			
		||||
        edgeIds: [],
 | 
			
		||||
@ -343,7 +355,11 @@ export function getArtifactsToUpdate({
 | 
			
		||||
    ) {
 | 
			
		||||
      returnArr.push({
 | 
			
		||||
        id: response.data.modeling_response.data.face_id,
 | 
			
		||||
        artifact: { type: 'solid2D', pathId },
 | 
			
		||||
        artifact: {
 | 
			
		||||
          type: 'solid2D',
 | 
			
		||||
          id: response.data.modeling_response.data.face_id,
 | 
			
		||||
          pathId,
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
      const path = getArtifact(pathId)
 | 
			
		||||
      if (path?.type === 'path')
 | 
			
		||||
@ -363,6 +379,7 @@ export function getArtifactsToUpdate({
 | 
			
		||||
      artifact: {
 | 
			
		||||
        type: 'sweep',
 | 
			
		||||
        subType: subType,
 | 
			
		||||
        id,
 | 
			
		||||
        pathId: cmd.target,
 | 
			
		||||
        surfaceIds: [],
 | 
			
		||||
        edgeIds: [],
 | 
			
		||||
@ -394,6 +411,7 @@ export function getArtifactsToUpdate({
 | 
			
		||||
              id: face_id,
 | 
			
		||||
              artifact: {
 | 
			
		||||
                type: 'wall',
 | 
			
		||||
                id: face_id,
 | 
			
		||||
                segId: curve_id,
 | 
			
		||||
                edgeCutEdgeIds: [],
 | 
			
		||||
                sweepId: path.sweepId,
 | 
			
		||||
@ -426,6 +444,7 @@ export function getArtifactsToUpdate({
 | 
			
		||||
            id: face_id,
 | 
			
		||||
            artifact: {
 | 
			
		||||
              type: 'cap',
 | 
			
		||||
              id: face_id,
 | 
			
		||||
              subType: cap === 'bottom' ? 'start' : 'end',
 | 
			
		||||
              edgeCutEdgeIds: [],
 | 
			
		||||
              sweepId: path.sweepId,
 | 
			
		||||
@ -472,6 +491,7 @@ export function getArtifactsToUpdate({
 | 
			
		||||
        id: response.data.modeling_response.data.edge,
 | 
			
		||||
        artifact: {
 | 
			
		||||
          type: 'sweepEdge',
 | 
			
		||||
          id: response.data.modeling_response.data.edge,
 | 
			
		||||
          subType:
 | 
			
		||||
            cmd.type === 'solid3d_get_next_adjacent_edge'
 | 
			
		||||
              ? 'adjacent'
 | 
			
		||||
@ -500,6 +520,7 @@ export function getArtifactsToUpdate({
 | 
			
		||||
      id,
 | 
			
		||||
      artifact: {
 | 
			
		||||
        type: 'edgeCut',
 | 
			
		||||
        id,
 | 
			
		||||
        subType: cmd.cut_type,
 | 
			
		||||
        consumedEdgeId: cmd.edge_id,
 | 
			
		||||
        edgeIds: [],
 | 
			
		||||
@ -590,6 +611,7 @@ export function expandPlane(
 | 
			
		||||
  )
 | 
			
		||||
  return {
 | 
			
		||||
    type: 'plane',
 | 
			
		||||
    id: plane.id,
 | 
			
		||||
    paths: Array.from(paths.values()),
 | 
			
		||||
    codeRef: plane.codeRef,
 | 
			
		||||
  }
 | 
			
		||||
@ -620,6 +642,7 @@ export function expandPath(
 | 
			
		||||
  if (err(plane)) return plane
 | 
			
		||||
  return {
 | 
			
		||||
    type: 'path',
 | 
			
		||||
    id: path.id,
 | 
			
		||||
    segments: Array.from(segs.values()),
 | 
			
		||||
    sweep,
 | 
			
		||||
    plane,
 | 
			
		||||
@ -647,6 +670,7 @@ export function expandSweep(
 | 
			
		||||
  return {
 | 
			
		||||
    type: 'sweep',
 | 
			
		||||
    subType: sweep.subType,
 | 
			
		||||
    id: sweep.id,
 | 
			
		||||
    surfaces: Array.from(surfs.values()),
 | 
			
		||||
    edges: Array.from(edges.values()),
 | 
			
		||||
    path,
 | 
			
		||||
@ -682,6 +706,7 @@ export function expandSegment(
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    type: 'segment',
 | 
			
		||||
    id: segment.id,
 | 
			
		||||
    path,
 | 
			
		||||
    surf,
 | 
			
		||||
    edges: Array.from(edges.values()),
 | 
			
		||||
@ -693,7 +718,7 @@ export function expandSegment(
 | 
			
		||||
export function getCapCodeRef(
 | 
			
		||||
  cap: CapArtifact,
 | 
			
		||||
  artifactGraph: ArtifactGraph
 | 
			
		||||
): CommonCommandProperties | Error {
 | 
			
		||||
): CodeRef | Error {
 | 
			
		||||
  const sweep = getArtifactOfTypes(
 | 
			
		||||
    { key: cap.sweepId, types: ['sweep'] },
 | 
			
		||||
    artifactGraph
 | 
			
		||||
@ -710,7 +735,7 @@ export function getCapCodeRef(
 | 
			
		||||
export function getSolid2dCodeRef(
 | 
			
		||||
  solid2D: solid2D,
 | 
			
		||||
  artifactGraph: ArtifactGraph
 | 
			
		||||
): CommonCommandProperties | Error {
 | 
			
		||||
): CodeRef | Error {
 | 
			
		||||
  const path = getArtifactOfTypes(
 | 
			
		||||
    { key: solid2D.pathId, types: ['path'] },
 | 
			
		||||
    artifactGraph
 | 
			
		||||
@ -722,7 +747,7 @@ export function getSolid2dCodeRef(
 | 
			
		||||
export function getWallCodeRef(
 | 
			
		||||
  wall: WallArtifact,
 | 
			
		||||
  artifactGraph: ArtifactGraph
 | 
			
		||||
): CommonCommandProperties | Error {
 | 
			
		||||
): CodeRef | Error {
 | 
			
		||||
  const seg = getArtifactOfTypes(
 | 
			
		||||
    { key: wall.segId, types: ['segment'] },
 | 
			
		||||
    artifactGraph
 | 
			
		||||
@ -734,7 +759,7 @@ export function getWallCodeRef(
 | 
			
		||||
export function getSweepEdgeCodeRef(
 | 
			
		||||
  edge: SweepEdge,
 | 
			
		||||
  artifactGraph: ArtifactGraph
 | 
			
		||||
): CommonCommandProperties | Error {
 | 
			
		||||
): CodeRef | Error {
 | 
			
		||||
  const seg = getArtifactOfTypes(
 | 
			
		||||
    { key: edge.segId, types: ['segment'] },
 | 
			
		||||
    artifactGraph
 | 
			
		||||
@ -742,10 +767,10 @@ export function getSweepEdgeCodeRef(
 | 
			
		||||
  if (err(seg)) return seg
 | 
			
		||||
  return seg.codeRef
 | 
			
		||||
}
 | 
			
		||||
export function getEdgeCuteConsumedCodeRef(
 | 
			
		||||
export function getEdgeCutConsumedCodeRef(
 | 
			
		||||
  edge: EdgeCut,
 | 
			
		||||
  artifactGraph: ArtifactGraph
 | 
			
		||||
): CommonCommandProperties | Error {
 | 
			
		||||
): CodeRef | Error {
 | 
			
		||||
  const seg = getArtifactOfTypes(
 | 
			
		||||
    { key: edge.consumedEdgeId, types: ['segment', 'sweepEdge'] },
 | 
			
		||||
    artifactGraph
 | 
			
		||||
@ -803,3 +828,46 @@ export function getSweepFromSuspectedPath(
 | 
			
		||||
    artifactGraph
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getCodeRefsByArtifactId(
 | 
			
		||||
  id: string,
 | 
			
		||||
  artifactGraph: ArtifactGraph
 | 
			
		||||
): Array<CodeRef> | null {
 | 
			
		||||
  const artifact = artifactGraph.get(id)
 | 
			
		||||
  if (artifact?.type === 'solid2D') {
 | 
			
		||||
    const codeRef = getSolid2dCodeRef(artifact, artifactGraph)
 | 
			
		||||
    if (err(codeRef)) return null
 | 
			
		||||
    return [codeRef]
 | 
			
		||||
  } else if (artifact?.type === 'cap') {
 | 
			
		||||
    const codeRef = getCapCodeRef(artifact, artifactGraph)
 | 
			
		||||
    if (err(codeRef)) return null
 | 
			
		||||
    return [codeRef]
 | 
			
		||||
  } else if (artifact?.type === 'wall') {
 | 
			
		||||
    const extrusion = getSweepFromSuspectedSweepSurface(id, artifactGraph)
 | 
			
		||||
    const codeRef = getWallCodeRef(artifact, artifactGraph)
 | 
			
		||||
    if (err(codeRef)) return null
 | 
			
		||||
    return err(extrusion) ? [codeRef] : [codeRef, extrusion.codeRef]
 | 
			
		||||
  } else if (artifact?.type === 'sweepEdge') {
 | 
			
		||||
    const codeRef = getSweepEdgeCodeRef(artifact, artifactGraph)
 | 
			
		||||
    if (err(codeRef)) return null
 | 
			
		||||
    return [codeRef]
 | 
			
		||||
  } else if (artifact?.type === 'segment') {
 | 
			
		||||
    return [artifact.codeRef]
 | 
			
		||||
  } else if (artifact?.type === 'edgeCut') {
 | 
			
		||||
    const codeRef = artifact.codeRef
 | 
			
		||||
    const consumedCodeRef = getEdgeCutConsumedCodeRef(artifact, artifactGraph)
 | 
			
		||||
    if (err(consumedCodeRef)) return [codeRef]
 | 
			
		||||
    return [codeRef, consumedCodeRef]
 | 
			
		||||
  } else if (artifact && 'codeRef' in artifact) {
 | 
			
		||||
    return [artifact.codeRef]
 | 
			
		||||
  } else {
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
 | 
			
		||||
  return {
 | 
			
		||||
    range,
 | 
			
		||||
    pathToNode: getNodePathFromSourceRange(ast, range),
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { Program, SourceRange } from 'lang/wasm'
 | 
			
		||||
import { SourceRange } from 'lang/wasm'
 | 
			
		||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
 | 
			
		||||
import { Models } from '@kittycad/lib'
 | 
			
		||||
import { exportSave } from 'lib/exportSave'
 | 
			
		||||
@ -1398,11 +1398,6 @@ export class EngineCommandManager extends EventTarget {
 | 
			
		||||
    this._camControlsCameraChange = cb
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private getAst: () => Program = () =>
 | 
			
		||||
    ({ start: 0, end: 0, body: [], nonCodeMeta: {} } as any)
 | 
			
		||||
  set getAstCb(cb: () => Program) {
 | 
			
		||||
    this.getAst = cb
 | 
			
		||||
  }
 | 
			
		||||
  private makeDefaultPlanes: () => Promise<DefaultPlanes> | null = () => null
 | 
			
		||||
  private modifyGrid: (hidden: boolean) => Promise<void> | null = () => null
 | 
			
		||||
 | 
			
		||||
@ -2125,18 +2120,30 @@ export class EngineCommandManager extends EventTarget {
 | 
			
		||||
   * 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.
 | 
			
		||||
   */
 | 
			
		||||
  async waitForAllCommands() {
 | 
			
		||||
  async waitForAllCommands(useFakeExecutor = false) {
 | 
			
		||||
    await Promise.all(Object.values(this.pendingCommands).map((a) => a.promise))
 | 
			
		||||
    setTimeout(() => {
 | 
			
		||||
      // the ast is wrong without this one tick timeout.
 | 
			
		||||
      // an example is `Solids should be select and deletable` e2e test will fail
 | 
			
		||||
      // because the out of date ast messes with selections
 | 
			
		||||
      // TODO: race condition
 | 
			
		||||
      if (!this?.kclManager) return
 | 
			
		||||
      this.artifactGraph = createArtifactGraph({
 | 
			
		||||
        orderedCommands: this.orderedCommands,
 | 
			
		||||
        responseMap: this.responseMap,
 | 
			
		||||
      ast: this.getAst(),
 | 
			
		||||
        ast: this.kclManager.ast,
 | 
			
		||||
      })
 | 
			
		||||
      if (useFakeExecutor) {
 | 
			
		||||
        // mock executions don't produce an artifactGraph, so this will always be empty
 | 
			
		||||
        // skipping the below logic to wait for the next real execution
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      if (this.artifactGraph.size) {
 | 
			
		||||
        this.deferredArtifactEmptied(null)
 | 
			
		||||
      } else {
 | 
			
		||||
        this.deferredArtifactPopulated(null)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,20 @@
 | 
			
		||||
import { parse, Sketch, recast, initPromise, sketchFromKclValue } from '../wasm'
 | 
			
		||||
import {
 | 
			
		||||
  parse,
 | 
			
		||||
  Sketch,
 | 
			
		||||
  recast,
 | 
			
		||||
  initPromise,
 | 
			
		||||
  sketchFromKclValue,
 | 
			
		||||
  SourceRange,
 | 
			
		||||
} from '../wasm'
 | 
			
		||||
import {
 | 
			
		||||
  ConstraintType,
 | 
			
		||||
  getTransformInfos,
 | 
			
		||||
  transformAstSketchLines,
 | 
			
		||||
} from './sketchcombos'
 | 
			
		||||
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
 | 
			
		||||
import { Selection } from 'lib/selections'
 | 
			
		||||
import { enginelessExecutor } from '../../lib/testHelpers'
 | 
			
		||||
import { err } from 'lib/trap'
 | 
			
		||||
import { codeRefFromRange } from './artifactGraph'
 | 
			
		||||
 | 
			
		||||
beforeAll(async () => {
 | 
			
		||||
  await initPromise
 | 
			
		||||
@ -27,16 +34,17 @@ async function testingSwapSketchFnCall({
 | 
			
		||||
  originalRange: [number, number]
 | 
			
		||||
}> {
 | 
			
		||||
  const startIndex = inputCode.indexOf(callToSwap)
 | 
			
		||||
  const range: Selection = {
 | 
			
		||||
    type: 'default',
 | 
			
		||||
    range: [startIndex, startIndex + callToSwap.length],
 | 
			
		||||
  }
 | 
			
		||||
  const range: SourceRange = [startIndex, startIndex + callToSwap.length]
 | 
			
		||||
  const ast = parse(inputCode)
 | 
			
		||||
  if (err(ast)) return Promise.reject(ast)
 | 
			
		||||
 | 
			
		||||
  const execState = await enginelessExecutor(ast)
 | 
			
		||||
  const selections = {
 | 
			
		||||
    codeBasedSelections: [range],
 | 
			
		||||
    graphSelections: [
 | 
			
		||||
      {
 | 
			
		||||
        codeRef: codeRefFromRange(range, ast),
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
    otherSelections: [],
 | 
			
		||||
  }
 | 
			
		||||
  const transformInfos = getTransformInfos(selections, ast, constraintType)
 | 
			
		||||
@ -57,7 +65,7 @@ async function testingSwapSketchFnCall({
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    newCode,
 | 
			
		||||
    originalRange: range.range,
 | 
			
		||||
    originalRange: range,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
import { parse, Expr, recast, initPromise } from '../wasm'
 | 
			
		||||
import { parse, Expr, recast, initPromise, Program } from '../wasm'
 | 
			
		||||
import {
 | 
			
		||||
  getConstraintType,
 | 
			
		||||
  getTransformInfos,
 | 
			
		||||
@ -9,9 +9,10 @@ import {
 | 
			
		||||
  getConstraintLevelFromSourceRange,
 | 
			
		||||
} from './sketchcombos'
 | 
			
		||||
import { ToolTip } from 'lang/langHelpers'
 | 
			
		||||
import { Selection, Selections } from 'lib/selections'
 | 
			
		||||
import { Selections, Selection } from 'lib/selections'
 | 
			
		||||
import { err } from 'lib/trap'
 | 
			
		||||
import { enginelessExecutor } from '../../lib/testHelpers'
 | 
			
		||||
import { codeRefFromRange } from './artifactGraph'
 | 
			
		||||
 | 
			
		||||
beforeAll(async () => {
 | 
			
		||||
  await initPromise
 | 
			
		||||
@ -87,10 +88,10 @@ function getConstraintTypeFromSourceHelper2(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function makeSelections(
 | 
			
		||||
  codeBaseSelections: Selections['codeBasedSelections']
 | 
			
		||||
  graphSelections: Selections['graphSelections']
 | 
			
		||||
): Selections {
 | 
			
		||||
  return {
 | 
			
		||||
    codeBasedSelections: codeBaseSelections,
 | 
			
		||||
    graphSelections: graphSelections,
 | 
			
		||||
    otherSelections: [],
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -112,7 +113,11 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
 | 
			
		||||
  |> close(%)
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
    const selectLine = (script: string, lineNumber: number): Selection => {
 | 
			
		||||
    const selectLine = (
 | 
			
		||||
      script: string,
 | 
			
		||||
      lineNumber: number,
 | 
			
		||||
      ast: Program
 | 
			
		||||
    ): Selection => {
 | 
			
		||||
      const lines = script.split('\n')
 | 
			
		||||
      const codeBeforeLine = lines.slice(0, lineNumber).join('\n').length
 | 
			
		||||
      const line = lines.find((_, i) => i === lineNumber)
 | 
			
		||||
@ -124,14 +129,13 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
 | 
			
		||||
      const start = codeBeforeLine + line.indexOf('|> ' + 5)
 | 
			
		||||
      const range: [number, number] = [start, start]
 | 
			
		||||
      return {
 | 
			
		||||
        type: 'default',
 | 
			
		||||
        range,
 | 
			
		||||
        codeRef: codeRefFromRange(range, ast),
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async function applyTransformation(
 | 
			
		||||
      inputCode: string,
 | 
			
		||||
      selectionRanges: Selections['codeBasedSelections']
 | 
			
		||||
      selectionRanges: Selections['graphSelections']
 | 
			
		||||
    ) {
 | 
			
		||||
      const ast = parse(inputCode)
 | 
			
		||||
      if (err(ast)) return Promise.reject(ast)
 | 
			
		||||
@ -157,9 +161,11 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    it(`Should reorder when user selects first-to-last`, async () => {
 | 
			
		||||
      const selectionRanges: Selections['codeBasedSelections'] = [
 | 
			
		||||
        selectLine(inputScript, 3),
 | 
			
		||||
        selectLine(inputScript, 4),
 | 
			
		||||
      const ast = parse(inputScript)
 | 
			
		||||
      if (err(ast)) return Promise.reject(ast)
 | 
			
		||||
      const selectionRanges: Selections['graphSelections'] = [
 | 
			
		||||
        selectLine(inputScript, 3, ast),
 | 
			
		||||
        selectLine(inputScript, 4, ast),
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
      const newCode = await applyTransformation(inputScript, selectionRanges)
 | 
			
		||||
@ -167,9 +173,11 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    it(`Should reorder when user selects last-to-first`, async () => {
 | 
			
		||||
      const selectionRanges: Selections['codeBasedSelections'] = [
 | 
			
		||||
        selectLine(inputScript, 4),
 | 
			
		||||
        selectLine(inputScript, 3),
 | 
			
		||||
      const ast = parse(inputScript)
 | 
			
		||||
      if (err(ast)) return Promise.reject(ast)
 | 
			
		||||
      const selectionRanges: Selections['graphSelections'] = [
 | 
			
		||||
        selectLine(inputScript, 4, ast),
 | 
			
		||||
        selectLine(inputScript, 3, ast),
 | 
			
		||||
      ]
 | 
			
		||||
 | 
			
		||||
      const newCode = await applyTransformation(inputScript, selectionRanges)
 | 
			
		||||
@ -288,15 +296,14 @@ part001 = startSketchOn('XY')
 | 
			
		||||
    const ast = parse(inputScript)
 | 
			
		||||
    if (err(ast)) return Promise.reject(ast)
 | 
			
		||||
 | 
			
		||||
    const selectionRanges: Selections['codeBasedSelections'] = inputScript
 | 
			
		||||
    const selectionRanges: Selections['graphSelections'] = inputScript
 | 
			
		||||
      .split('\n')
 | 
			
		||||
      .filter((ln) => ln.includes('//'))
 | 
			
		||||
      .map((ln) => {
 | 
			
		||||
        const comment = ln.split('//')[1]
 | 
			
		||||
        const start = inputScript.indexOf('//' + comment) - 7
 | 
			
		||||
        return {
 | 
			
		||||
          type: 'default',
 | 
			
		||||
          range: [start, start],
 | 
			
		||||
          codeRef: codeRefFromRange([start, start], ast),
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
@ -379,15 +386,14 @@ part001 = startSketchOn('XY')
 | 
			
		||||
    const ast = parse(inputScript)
 | 
			
		||||
    if (err(ast)) return Promise.reject(ast)
 | 
			
		||||
 | 
			
		||||
    const selectionRanges: Selections['codeBasedSelections'] = inputScript
 | 
			
		||||
    const selectionRanges: Selections['graphSelections'] = inputScript
 | 
			
		||||
      .split('\n')
 | 
			
		||||
      .filter((ln) => ln.includes('// select for horizontal constraint'))
 | 
			
		||||
      .map((ln) => {
 | 
			
		||||
        const comment = ln.split('//')[1]
 | 
			
		||||
        const start = inputScript.indexOf('//' + comment) - 7
 | 
			
		||||
        return {
 | 
			
		||||
          type: 'default',
 | 
			
		||||
          range: [start, start],
 | 
			
		||||
          codeRef: codeRefFromRange([start, start], ast),
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
@ -441,15 +447,14 @@ part001 = startSketchOn('XY')
 | 
			
		||||
    const ast = parse(inputScript)
 | 
			
		||||
    if (err(ast)) return Promise.reject(ast)
 | 
			
		||||
 | 
			
		||||
    const selectionRanges: Selections['codeBasedSelections'] = inputScript
 | 
			
		||||
    const selectionRanges: Selections['graphSelections'] = inputScript
 | 
			
		||||
      .split('\n')
 | 
			
		||||
      .filter((ln) => ln.includes('// select for vertical constraint'))
 | 
			
		||||
      .map((ln) => {
 | 
			
		||||
        const comment = ln.split('//')[1]
 | 
			
		||||
        const start = inputScript.indexOf('//' + comment) - 7
 | 
			
		||||
        return {
 | 
			
		||||
          type: 'default',
 | 
			
		||||
          range: [start, start],
 | 
			
		||||
          codeRef: codeRefFromRange([start, start], ast),
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
@ -536,7 +541,7 @@ async function helperThing(
 | 
			
		||||
  const ast = parse(inputScript)
 | 
			
		||||
  if (err(ast)) return Promise.reject(ast)
 | 
			
		||||
 | 
			
		||||
  const selectionRanges: Selections['codeBasedSelections'] = inputScript
 | 
			
		||||
  const selectionRanges: Selections['graphSelections'] = inputScript
 | 
			
		||||
    .split('\n')
 | 
			
		||||
    .filter((ln) =>
 | 
			
		||||
      linesOfInterest.some((lineOfInterest) => ln.includes(lineOfInterest))
 | 
			
		||||
@ -545,8 +550,7 @@ async function helperThing(
 | 
			
		||||
      const comment = ln.split('//')[1]
 | 
			
		||||
      const start = inputScript.indexOf('//' + comment) - 7
 | 
			
		||||
      return {
 | 
			
		||||
        type: 'default',
 | 
			
		||||
        range: [start, start],
 | 
			
		||||
        codeRef: codeRefFromRange([start, start], ast),
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
@ -605,7 +609,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
    const ast = parse(code)
 | 
			
		||||
    const constraintLevels: ConstraintLevel[] = ['full', 'partial', 'free']
 | 
			
		||||
    constraintLevels.forEach((constraintLevel) => {
 | 
			
		||||
      const recursivelySeachCommentsAndCheckConstraintLevel = (
 | 
			
		||||
      const recursivelySearchCommentsAndCheckConstraintLevel = (
 | 
			
		||||
        str: string,
 | 
			
		||||
        offset: number = 0
 | 
			
		||||
      ): null => {
 | 
			
		||||
@ -622,12 +626,12 @@ part001 = startSketchOn('XY')
 | 
			
		||||
          throw expectedConstraintLevel
 | 
			
		||||
        }
 | 
			
		||||
        expect(expectedConstraintLevel.level).toBe(constraintLevel)
 | 
			
		||||
        return recursivelySeachCommentsAndCheckConstraintLevel(
 | 
			
		||||
        return recursivelySearchCommentsAndCheckConstraintLevel(
 | 
			
		||||
          str,
 | 
			
		||||
          index + constraintLevel.length
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      recursivelySeachCommentsAndCheckConstraintLevel(code)
 | 
			
		||||
      recursivelySearchCommentsAndCheckConstraintLevel(code)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ import {
 | 
			
		||||
  TransformInfo,
 | 
			
		||||
} from './stdTypes'
 | 
			
		||||
import { ToolTip, toolTips } from 'lang/langHelpers'
 | 
			
		||||
import { Selections, Selection } from 'lib/selections'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { cleanErrs, err } from 'lib/trap'
 | 
			
		||||
import {
 | 
			
		||||
  CallExpression,
 | 
			
		||||
@ -19,6 +19,7 @@ import {
 | 
			
		||||
  ProgramMemory,
 | 
			
		||||
  sketchFromKclValue,
 | 
			
		||||
  Literal,
 | 
			
		||||
  SourceRange,
 | 
			
		||||
} from '../wasm'
 | 
			
		||||
import {
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
@ -1483,11 +1484,8 @@ export function getTransformInfos(
 | 
			
		||||
  ast: Program,
 | 
			
		||||
  constraintType: ConstraintType
 | 
			
		||||
): TransformInfo[] {
 | 
			
		||||
  const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
 | 
			
		||||
    getNodePathFromSourceRange(ast, range)
 | 
			
		||||
  )
 | 
			
		||||
  const nodes = paths.map((pathToNode) =>
 | 
			
		||||
    getNodeFromPath<Expr>(ast, pathToNode, 'CallExpression')
 | 
			
		||||
  const nodes = selectionRanges.graphSelections.map(({ codeRef }) =>
 | 
			
		||||
    getNodeFromPath<Expr>(ast, codeRef.pathToNode, 'CallExpression')
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
@ -1515,12 +1513,8 @@ export function getRemoveConstraintsTransforms(
 | 
			
		||||
  ast: Program,
 | 
			
		||||
  constraintType: ConstraintType
 | 
			
		||||
): TransformInfo[] | Error {
 | 
			
		||||
  // return ()
 | 
			
		||||
  const paths = selectionRanges.codeBasedSelections.map((selectionRange) =>
 | 
			
		||||
    getNodePathFromSourceRange(ast, selectionRange.range)
 | 
			
		||||
  )
 | 
			
		||||
  const nodes = paths.map((pathToNode) =>
 | 
			
		||||
    getNodeFromPath<Expr>(ast, pathToNode)
 | 
			
		||||
  const nodes = selectionRanges.graphSelections.map(({ codeRef }) =>
 | 
			
		||||
    getNodeFromPath<Expr>(ast, codeRef.pathToNode)
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const theTransforms = nodes.map((nodeMeta) => {
 | 
			
		||||
@ -1571,11 +1565,10 @@ export function transformSecondarySketchLinesTagFirst({
 | 
			
		||||
 | 
			
		||||
  // We need to sort the selections by their start position
 | 
			
		||||
  // so that we can process them in dependency order and not write invalid KCL.
 | 
			
		||||
  const sortedCodeBasedSelections =
 | 
			
		||||
    selectionRanges.codeBasedSelections.toSorted(
 | 
			
		||||
      (a, b) => a.range[0] - b.range[0]
 | 
			
		||||
  const sortedCodeBasedSelections = selectionRanges.graphSelections.toSorted(
 | 
			
		||||
    (a, b) => a?.codeRef?.range[0] - b?.codeRef?.range[0]
 | 
			
		||||
  )
 | 
			
		||||
  const primarySelection = sortedCodeBasedSelections[0].range
 | 
			
		||||
  const primarySelection = sortedCodeBasedSelections[0]?.codeRef?.range
 | 
			
		||||
  const secondarySelections = sortedCodeBasedSelections.slice(1)
 | 
			
		||||
 | 
			
		||||
  const _tag = giveSketchFnCallTag(ast, primarySelection, forceSegName)
 | 
			
		||||
@ -1586,7 +1579,7 @@ export function transformSecondarySketchLinesTagFirst({
 | 
			
		||||
    ast: modifiedAst,
 | 
			
		||||
    selectionRanges: {
 | 
			
		||||
      ...selectionRanges,
 | 
			
		||||
      codeBasedSelections: secondarySelections,
 | 
			
		||||
      graphSelections: secondarySelections,
 | 
			
		||||
    },
 | 
			
		||||
    referencedSegmentRange: primarySelection,
 | 
			
		||||
    transformInfos,
 | 
			
		||||
@ -1634,8 +1627,8 @@ export function transformAstSketchLines({
 | 
			
		||||
  transformInfos: TransformInfo[]
 | 
			
		||||
  programMemory: ProgramMemory
 | 
			
		||||
  referenceSegName: string
 | 
			
		||||
  referencedSegmentRange?: SourceRange
 | 
			
		||||
  forceValueUsedInTransform?: BinaryPart
 | 
			
		||||
  referencedSegmentRange?: Selection['range']
 | 
			
		||||
}):
 | 
			
		||||
  | {
 | 
			
		||||
      modifiedAst: Node<Program>
 | 
			
		||||
@ -1786,11 +1779,11 @@ export function transformAstSketchLines({
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if ('codeBasedSelections' in selectionRanges) {
 | 
			
		||||
  if ('graphSelections' in selectionRanges) {
 | 
			
		||||
    // If the processing of any of the selections failed, return the first error
 | 
			
		||||
    const maybeProcessErrors = selectionRanges.codeBasedSelections
 | 
			
		||||
      .map(({ range }, index) =>
 | 
			
		||||
        processSelection(getNodePathFromSourceRange(node, range), index)
 | 
			
		||||
    const maybeProcessErrors = selectionRanges.graphSelections
 | 
			
		||||
      .map(({ codeRef }, index) =>
 | 
			
		||||
        processSelection(getNodePathFromSourceRange(node, codeRef.range), index)
 | 
			
		||||
      )
 | 
			
		||||
      .filter(err)
 | 
			
		||||
 | 
			
		||||
@ -1838,7 +1831,7 @@ function getArgLiteralVal(arg: Literal): number | Error {
 | 
			
		||||
export type ConstraintLevel = 'free' | 'partial' | 'full'
 | 
			
		||||
 | 
			
		||||
export function getConstraintLevelFromSourceRange(
 | 
			
		||||
  cursorRange: Selection['range'],
 | 
			
		||||
  cursorRange: SourceRange,
 | 
			
		||||
  ast: Program | Error
 | 
			
		||||
): Error | { range: [number, number]; level: ConstraintLevel } {
 | 
			
		||||
  if (err(ast)) return ast
 | 
			
		||||
 | 
			
		||||
@ -1,52 +1,13 @@
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import {
 | 
			
		||||
  Program,
 | 
			
		||||
  PathToNode,
 | 
			
		||||
  CallExpression,
 | 
			
		||||
  Literal,
 | 
			
		||||
  ArrayExpression,
 | 
			
		||||
  BinaryExpression,
 | 
			
		||||
} from './wasm'
 | 
			
		||||
import { getNodeFromPath } from './queryAst'
 | 
			
		||||
import { ArtifactGraph, filterArtifacts } from 'lang/std/artifactGraph'
 | 
			
		||||
import { isOverlap } from 'lib/utils'
 | 
			
		||||
import { err } from 'lib/trap'
 | 
			
		||||
 | 
			
		||||
export function pathMapToSelections(
 | 
			
		||||
  ast: Program,
 | 
			
		||||
  prevSelections: Selections,
 | 
			
		||||
  pathToNodeMap: { [key: number]: PathToNode }
 | 
			
		||||
): Selections {
 | 
			
		||||
  const newSelections: Selections = {
 | 
			
		||||
    ...prevSelections,
 | 
			
		||||
    codeBasedSelections: [],
 | 
			
		||||
  }
 | 
			
		||||
  Object.entries(pathToNodeMap).forEach(([index, path]) => {
 | 
			
		||||
    const nodeMeta = getNodeFromPath<any>(ast, path)
 | 
			
		||||
    if (err(nodeMeta)) return
 | 
			
		||||
    const node = nodeMeta.node as any
 | 
			
		||||
    const selection = prevSelections.codeBasedSelections[Number(index)]
 | 
			
		||||
    if (node) {
 | 
			
		||||
      if (
 | 
			
		||||
        selection.type === 'base-edgeCut' ||
 | 
			
		||||
        selection.type === 'adjacent-edgeCut' ||
 | 
			
		||||
        selection.type === 'opposite-edgeCut'
 | 
			
		||||
      ) {
 | 
			
		||||
        newSelections.codeBasedSelections.push({
 | 
			
		||||
          range: [node.start, node.end],
 | 
			
		||||
          type: selection.type,
 | 
			
		||||
          secondaryRange: selection.secondaryRange,
 | 
			
		||||
        })
 | 
			
		||||
      } else {
 | 
			
		||||
        newSelections.codeBasedSelections.push({
 | 
			
		||||
          range: [node.start, node.end],
 | 
			
		||||
          type: selection.type,
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  return newSelections
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function updatePathToNodeFromMap(
 | 
			
		||||
  oldPath: PathToNode,
 | 
			
		||||
@ -72,11 +33,11 @@ export function isCursorInSketchCommandRange(
 | 
			
		||||
    {
 | 
			
		||||
      types: ['segment', 'path'],
 | 
			
		||||
      predicate: (artifact) => {
 | 
			
		||||
        return selectionRanges.codeBasedSelections.some(
 | 
			
		||||
        return selectionRanges.graphSelections.some(
 | 
			
		||||
          (selection) =>
 | 
			
		||||
            Array.isArray(selection?.range) &&
 | 
			
		||||
            Array.isArray(selection?.codeRef?.range) &&
 | 
			
		||||
            Array.isArray(artifact?.codeRef?.range) &&
 | 
			
		||||
            isOverlap(selection.range, artifact.codeRef.range)
 | 
			
		||||
            isOverlap(selection?.codeRef?.range, artifact.codeRef.range)
 | 
			
		||||
        )
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -233,8 +233,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
 | 
			
		||||
    args: {
 | 
			
		||||
      selection: {
 | 
			
		||||
        inputType: 'selection',
 | 
			
		||||
        // TODO: These are products of an extrude
 | 
			
		||||
        selectionTypes: ['extrude-wall', 'start-cap', 'end-cap'],
 | 
			
		||||
        selectionTypes: ['solid2D', 'segment'],
 | 
			
		||||
        multiple: false, // TODO: multiple selection
 | 
			
		||||
        required: true,
 | 
			
		||||
        skip: true,
 | 
			
		||||
@ -265,7 +264,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
 | 
			
		||||
    args: {
 | 
			
		||||
      selection: {
 | 
			
		||||
        inputType: 'selection',
 | 
			
		||||
        selectionTypes: ['extrude-wall', 'start-cap', 'end-cap'],
 | 
			
		||||
        selectionTypes: ['solid2D', 'segment'],
 | 
			
		||||
        multiple: false, // TODO: multiple selection
 | 
			
		||||
        required: true,
 | 
			
		||||
        skip: true,
 | 
			
		||||
@ -284,20 +283,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
 | 
			
		||||
    args: {
 | 
			
		||||
      selection: {
 | 
			
		||||
        inputType: 'selection',
 | 
			
		||||
        selectionTypes: [
 | 
			
		||||
          'default',
 | 
			
		||||
          'line-end',
 | 
			
		||||
          'line-mid',
 | 
			
		||||
          'extrude-wall',
 | 
			
		||||
          'solid2D',
 | 
			
		||||
          'start-cap',
 | 
			
		||||
          'end-cap',
 | 
			
		||||
          'point',
 | 
			
		||||
          'edge',
 | 
			
		||||
          'line',
 | 
			
		||||
          'arc',
 | 
			
		||||
          'all',
 | 
			
		||||
        ],
 | 
			
		||||
        selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
 | 
			
		||||
        multiple: true,
 | 
			
		||||
        required: true,
 | 
			
		||||
        skip: false,
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,12 @@
 | 
			
		||||
import { CustomIconName } from 'components/CustomIcon'
 | 
			
		||||
import { AllMachines } from 'hooks/useStateMachineCommands'
 | 
			
		||||
import { Actor, AnyStateMachine, ContextFrom, EventFrom } from 'xstate'
 | 
			
		||||
import { Selection } from './selections'
 | 
			
		||||
import { Identifier, Expr, VariableDeclaration } from 'lang/wasm'
 | 
			
		||||
import { commandBarMachine } from 'machines/commandBarMachine'
 | 
			
		||||
import { ReactNode } from 'react'
 | 
			
		||||
import { MachineManager } from 'components/MachineManagerProvider'
 | 
			
		||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
 | 
			
		||||
import { Artifact } from 'lang/std/artifactGraph'
 | 
			
		||||
 | 
			
		||||
type Icon = CustomIconName
 | 
			
		||||
const PLATFORMS = ['both', 'web', 'desktop'] as const
 | 
			
		||||
@ -144,7 +144,7 @@ export type CommandArgumentConfig<
 | 
			
		||||
    }
 | 
			
		||||
  | {
 | 
			
		||||
      inputType: 'selection'
 | 
			
		||||
      selectionTypes: Selection['type'][]
 | 
			
		||||
      selectionTypes: Artifact['type'][]
 | 
			
		||||
      multiple: boolean
 | 
			
		||||
    }
 | 
			
		||||
  | { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default values
 | 
			
		||||
@ -218,7 +218,7 @@ export type CommandArgument<
 | 
			
		||||
    }
 | 
			
		||||
  | {
 | 
			
		||||
      inputType: 'selection'
 | 
			
		||||
      selectionTypes: Selection['type'][]
 | 
			
		||||
      selectionTypes: Artifact['type'][]
 | 
			
		||||
      multiple: boolean
 | 
			
		||||
    }
 | 
			
		||||
  | { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default value
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ import {
 | 
			
		||||
  kclManager,
 | 
			
		||||
  sceneEntitiesManager,
 | 
			
		||||
} from 'lib/singletons'
 | 
			
		||||
import { CallExpression, SourceRange, Expr, parse } from 'lang/wasm'
 | 
			
		||||
import { CallExpression, SourceRange, Expr } from 'lang/wasm'
 | 
			
		||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
 | 
			
		||||
import { isNonNullable, uuidv4 } from 'lib/utils'
 | 
			
		||||
import { EditorSelection, SelectionRange } from '@codemirror/state'
 | 
			
		||||
@ -15,6 +15,7 @@ import { Program } from 'lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  doesPipeHaveCallExp,
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
  getNodePathFromSourceRange,
 | 
			
		||||
  hasSketchPipeBeenExtruded,
 | 
			
		||||
  isSingleCursorInPipe,
 | 
			
		||||
} from 'lang/queryAst'
 | 
			
		||||
@ -28,12 +29,15 @@ import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
 | 
			
		||||
import { PathToNodeMap } from 'lang/std/sketchcombos'
 | 
			
		||||
import { err } from 'lib/trap'
 | 
			
		||||
import {
 | 
			
		||||
  Artifact,
 | 
			
		||||
  getArtifactOfTypes,
 | 
			
		||||
  getArtifactsOfTypes,
 | 
			
		||||
  getCapCodeRef,
 | 
			
		||||
  getSweepEdgeCodeRef,
 | 
			
		||||
  getSolid2dCodeRef,
 | 
			
		||||
  getWallCodeRef,
 | 
			
		||||
  CodeRef,
 | 
			
		||||
  getCodeRefsByArtifactId,
 | 
			
		||||
  ArtifactId,
 | 
			
		||||
} from 'lang/std/artifactGraph'
 | 
			
		||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
 | 
			
		||||
@ -43,7 +47,8 @@ export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
 | 
			
		||||
 | 
			
		||||
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
 | 
			
		||||
 | 
			
		||||
export type Selection =
 | 
			
		||||
/** @deprecated Use {@link Artifact} instead. */
 | 
			
		||||
type Selection__old =
 | 
			
		||||
  | {
 | 
			
		||||
      type:
 | 
			
		||||
        | 'default'
 | 
			
		||||
@ -67,9 +72,88 @@ export type Selection =
 | 
			
		||||
      // TODO this is a temporary measure that well be made redundant with: https://github.com/KittyCAD/modeling-app/pull/3836
 | 
			
		||||
      secondaryRange: SourceRange
 | 
			
		||||
    }
 | 
			
		||||
export type Selections = {
 | 
			
		||||
/** @deprecated Use {@link Selection} instead. */
 | 
			
		||||
export type Selections__old = {
 | 
			
		||||
  otherSelections: Axis[]
 | 
			
		||||
  codeBasedSelections: Selection[]
 | 
			
		||||
  codeBasedSelections: Selection__old[]
 | 
			
		||||
}
 | 
			
		||||
export interface Selection {
 | 
			
		||||
  artifact?: Artifact
 | 
			
		||||
  codeRef: CodeRef
 | 
			
		||||
}
 | 
			
		||||
export type Selections = {
 | 
			
		||||
  otherSelections: Array<Axis>
 | 
			
		||||
  graphSelections: Array<Selection>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** @deprecated If you're writing a new function, it should use {@link Selection} and not {@link Selection__old}
 | 
			
		||||
 * this function should only be used for backwards compatibility with old functions.
 | 
			
		||||
 */
 | 
			
		||||
function convertSelectionToOld(selection: Selection): Selection__old | null {
 | 
			
		||||
  // return {} as Selection__old
 | 
			
		||||
  // TODO implementation
 | 
			
		||||
  const _artifact = selection.artifact
 | 
			
		||||
  if (_artifact?.type === 'solid2D') {
 | 
			
		||||
    const codeRef = getSolid2dCodeRef(
 | 
			
		||||
      _artifact,
 | 
			
		||||
      engineCommandManager.artifactGraph
 | 
			
		||||
    )
 | 
			
		||||
    if (err(codeRef)) return null
 | 
			
		||||
    return { range: codeRef.range, type: 'solid2D' }
 | 
			
		||||
  }
 | 
			
		||||
  if (_artifact?.type === 'cap') {
 | 
			
		||||
    const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
 | 
			
		||||
    if (err(codeRef)) return null
 | 
			
		||||
    return {
 | 
			
		||||
      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 { range: codeRef.range, type: 'extrude-wall' }
 | 
			
		||||
  }
 | 
			
		||||
  if (_artifact?.type === 'segment' || _artifact?.type === 'path') {
 | 
			
		||||
    return { range: _artifact.codeRef.range, type: 'default' }
 | 
			
		||||
  }
 | 
			
		||||
  if (_artifact?.type === 'sweepEdge') {
 | 
			
		||||
    const codeRef = getSweepEdgeCodeRef(
 | 
			
		||||
      _artifact,
 | 
			
		||||
      engineCommandManager.artifactGraph
 | 
			
		||||
    )
 | 
			
		||||
    if (err(codeRef)) return null
 | 
			
		||||
    if (_artifact?.subType === 'adjacent') {
 | 
			
		||||
      return { range: codeRef.range, type: 'adjacent-edge' }
 | 
			
		||||
    }
 | 
			
		||||
    return { range: codeRef.range, type: 'edge' }
 | 
			
		||||
  }
 | 
			
		||||
  if (_artifact?.type === 'edgeCut') {
 | 
			
		||||
    const codeRef = _artifact.codeRef
 | 
			
		||||
    return { range: codeRef.range, type: 'default' }
 | 
			
		||||
  }
 | 
			
		||||
  if (selection?.codeRef?.range) {
 | 
			
		||||
    return { range: selection.codeRef.range, type: 'default' }
 | 
			
		||||
  }
 | 
			
		||||
  return null
 | 
			
		||||
}
 | 
			
		||||
/** @deprecated If you're writing a new function, it should use {@link Selection} and not {@link Selection__old}
 | 
			
		||||
 * this function should only be used for backwards compatibility with old functions.
 | 
			
		||||
 */
 | 
			
		||||
export function convertSelectionsToOld(selection: Selections): Selections__old {
 | 
			
		||||
  const selections: Selection__old[] = []
 | 
			
		||||
  for (const artifact of selection.graphSelections) {
 | 
			
		||||
    const converted = convertSelectionToOld(artifact)
 | 
			
		||||
    if (converted) selections.push(converted)
 | 
			
		||||
  }
 | 
			
		||||
  const selectionsOld: Selections__old = {
 | 
			
		||||
    otherSelections: selection.otherSelections,
 | 
			
		||||
    codeBasedSelections: selections,
 | 
			
		||||
  }
 | 
			
		||||
  return selectionsOld
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getEventForSelectWithPoint({
 | 
			
		||||
@ -94,127 +178,18 @@ export async function getEventForSelectWithPoint({
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  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,
 | 
			
		||||
  const codeRefs = getCodeRefsByArtifactId(
 | 
			
		||||
    data.entity_id,
 | 
			
		||||
    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
 | 
			
		||||
  if (_artifact && codeRefs) {
 | 
			
		||||
    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' },
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (_artifact.type === 'sweepEdge') {
 | 
			
		||||
    const codeRef = getSweepEdgeCodeRef(
 | 
			
		||||
      _artifact,
 | 
			
		||||
      engineCommandManager.artifactGraph
 | 
			
		||||
    )
 | 
			
		||||
    if (err(codeRef)) return null
 | 
			
		||||
    if (_artifact?.subType === 'adjacent') {
 | 
			
		||||
      return {
 | 
			
		||||
        type: 'Set selection',
 | 
			
		||||
        data: {
 | 
			
		||||
          selectionType: 'singleCodeCursor',
 | 
			
		||||
          selection: { range: codeRef.range, type: 'adjacent-edge' },
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return {
 | 
			
		||||
      type: 'Set selection',
 | 
			
		||||
      data: {
 | 
			
		||||
        selectionType: 'singleCodeCursor',
 | 
			
		||||
        selection: { range: codeRef.range, type: 'edge' },
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (_artifact.type === 'edgeCut') {
 | 
			
		||||
    const consumedEdge = getArtifactOfTypes(
 | 
			
		||||
      { key: _artifact.consumedEdgeId, types: ['segment', 'sweepEdge'] },
 | 
			
		||||
      engineCommandManager.artifactGraph
 | 
			
		||||
    )
 | 
			
		||||
    if (err(consumedEdge))
 | 
			
		||||
      return {
 | 
			
		||||
        type: 'Set selection',
 | 
			
		||||
        data: {
 | 
			
		||||
          selectionType: 'singleCodeCursor',
 | 
			
		||||
          selection: { range: _artifact.codeRef.range, type: 'default' },
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    if (consumedEdge.type === 'segment') {
 | 
			
		||||
      return {
 | 
			
		||||
        type: 'Set selection',
 | 
			
		||||
        data: {
 | 
			
		||||
          selectionType: 'singleCodeCursor',
 | 
			
		||||
          selection: {
 | 
			
		||||
            range: _artifact.codeRef.range,
 | 
			
		||||
            type: 'base-edgeCut',
 | 
			
		||||
            secondaryRange: consumedEdge.codeRef.range,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    const segment = getArtifactOfTypes(
 | 
			
		||||
      { key: consumedEdge.segId, types: ['segment'] },
 | 
			
		||||
      engineCommandManager.artifactGraph
 | 
			
		||||
    )
 | 
			
		||||
    if (err(segment)) return null
 | 
			
		||||
    return {
 | 
			
		||||
      type: 'Set selection',
 | 
			
		||||
      data: {
 | 
			
		||||
        selectionType: 'singleCodeCursor',
 | 
			
		||||
        selection: {
 | 
			
		||||
          range: _artifact.codeRef.range,
 | 
			
		||||
          type:
 | 
			
		||||
            consumedEdge.subType === 'adjacent'
 | 
			
		||||
              ? 'adjacent-edgeCut'
 | 
			
		||||
              : 'opposite-edgeCut',
 | 
			
		||||
          secondaryRange: segment.codeRef.range,
 | 
			
		||||
          artifact: _artifact,
 | 
			
		||||
          codeRef: codeRefs[0],
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
@ -237,28 +212,55 @@ export function getEventForSegmentSelection(
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  const pathToNode = group?.userData?.pathToNode
 | 
			
		||||
  if (!pathToNode) return null
 | 
			
		||||
  // previous drags don't update ast for efficiency reasons
 | 
			
		||||
  // So we want to make sure we have and updated ast with
 | 
			
		||||
  // accurate source ranges
 | 
			
		||||
  const updatedAst = parse(codeManager.code)
 | 
			
		||||
  if (err(updatedAst)) return null
 | 
			
		||||
 | 
			
		||||
  const nodeMeta = getNodeFromPath<Node<CallExpression>>(
 | 
			
		||||
    updatedAst,
 | 
			
		||||
    pathToNode,
 | 
			
		||||
    'CallExpression'
 | 
			
		||||
  // 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)
 | 
			
		||||
    )
 | 
			
		||||
  if (err(nodeMeta)) return null
 | 
			
		||||
  })?.[0]
 | 
			
		||||
 | 
			
		||||
  const node = nodeMeta.node
 | 
			
		||||
  const range: SourceRange = [node.start, node.end]
 | 
			
		||||
  const id = segWithMatchingPathToNode__Id
 | 
			
		||||
 | 
			
		||||
  if (!id && group) {
 | 
			
		||||
    const node = getNodeFromPath<Expr>(
 | 
			
		||||
      kclManager.ast,
 | 
			
		||||
      group.userData.pathToNode
 | 
			
		||||
    )
 | 
			
		||||
    if (err(node)) return null
 | 
			
		||||
    return {
 | 
			
		||||
      type: 'Set selection',
 | 
			
		||||
      data: {
 | 
			
		||||
        selectionType: 'singleCodeCursor',
 | 
			
		||||
      selection: { range, type: 'default' },
 | 
			
		||||
        selection: {
 | 
			
		||||
          codeRef: {
 | 
			
		||||
            range: [node.node.start, node.node.end],
 | 
			
		||||
            pathToNode: group.userData.pathToNode,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (!id || !group) return null
 | 
			
		||||
  const artifact = engineCommandManager.artifactGraph.get(id)
 | 
			
		||||
  const codeRefs = getCodeRefsByArtifactId(
 | 
			
		||||
    id,
 | 
			
		||||
    engineCommandManager.artifactGraph
 | 
			
		||||
  )
 | 
			
		||||
  if (!artifact || !codeRefs) return null
 | 
			
		||||
  return {
 | 
			
		||||
    type: 'Set selection',
 | 
			
		||||
    data: {
 | 
			
		||||
      selectionType: 'singleCodeCursor',
 | 
			
		||||
      selection: {
 | 
			
		||||
        artifact,
 | 
			
		||||
        codeRef: codeRefs[0],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -274,13 +276,24 @@ export function handleSelectionBatch({
 | 
			
		||||
  updateSceneObjectColors: () => void
 | 
			
		||||
} {
 | 
			
		||||
  const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
 | 
			
		||||
  const selectionToEngine: SelectionToEngine[] = []
 | 
			
		||||
 | 
			
		||||
  selections.graphSelections.forEach(({ artifact }) => {
 | 
			
		||||
    artifact?.id &&
 | 
			
		||||
      selectionToEngine.push({
 | 
			
		||||
        type: 'default',
 | 
			
		||||
        id: artifact?.id,
 | 
			
		||||
        range: getCodeRefsByArtifactId(
 | 
			
		||||
          artifact.id,
 | 
			
		||||
          engineCommandManager.artifactGraph
 | 
			
		||||
        )?.[0].range || [0, 0],
 | 
			
		||||
      })
 | 
			
		||||
  })
 | 
			
		||||
  const engineEvents: Models['WebSocketRequest_type'][] =
 | 
			
		||||
    resetAndSetEngineEntitySelectionCmds(
 | 
			
		||||
      codeToIdSelections(selections.codeBasedSelections)
 | 
			
		||||
    )
 | 
			
		||||
  selections.codeBasedSelections.forEach(({ range, type }) => {
 | 
			
		||||
    if (range?.[1]) {
 | 
			
		||||
      ranges.push(EditorSelection.cursor(range[1]))
 | 
			
		||||
    resetAndSetEngineEntitySelectionCmds(selectionToEngine)
 | 
			
		||||
  selections.graphSelections.forEach(({ codeRef }) => {
 | 
			
		||||
    if (codeRef.range?.[1]) {
 | 
			
		||||
      ranges.push(EditorSelection.cursor(codeRef.range[1]))
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
  if (ranges.length)
 | 
			
		||||
@ -288,11 +301,11 @@ export function handleSelectionBatch({
 | 
			
		||||
      engineEvents,
 | 
			
		||||
      codeMirrorSelection: EditorSelection.create(
 | 
			
		||||
        ranges,
 | 
			
		||||
        selections.codeBasedSelections.length - 1
 | 
			
		||||
        selections.graphSelections.length - 1
 | 
			
		||||
      ),
 | 
			
		||||
      otherSelections: selections.otherSelections,
 | 
			
		||||
      updateSceneObjectColors: () =>
 | 
			
		||||
        updateSceneObjectColors(selections.codeBasedSelections),
 | 
			
		||||
        updateSceneObjectColors(selections.graphSelections),
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
@ -303,43 +316,75 @@ export function handleSelectionBatch({
 | 
			
		||||
    engineEvents,
 | 
			
		||||
    otherSelections: selections.otherSelections,
 | 
			
		||||
    updateSceneObjectColors: () =>
 | 
			
		||||
      updateSceneObjectColors(selections.codeBasedSelections),
 | 
			
		||||
      updateSceneObjectColors(selections.graphSelections),
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SelectionToEngine = { type: Selection['type']; id: string }
 | 
			
		||||
type SelectionToEngine = {
 | 
			
		||||
  type: Selection__old['type']
 | 
			
		||||
  id?: string
 | 
			
		||||
  range: SourceRange
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function processCodeMirrorRanges({
 | 
			
		||||
  codeMirrorRanges,
 | 
			
		||||
  selectionRanges,
 | 
			
		||||
  isShiftDown,
 | 
			
		||||
  ast,
 | 
			
		||||
}: {
 | 
			
		||||
  codeMirrorRanges: readonly SelectionRange[]
 | 
			
		||||
  selectionRanges: Selections
 | 
			
		||||
  isShiftDown: boolean
 | 
			
		||||
  ast: Program
 | 
			
		||||
}): null | {
 | 
			
		||||
  modelingEvent: ModelingMachineEvent
 | 
			
		||||
  engineEvents: Models['WebSocketRequest_type'][]
 | 
			
		||||
} {
 | 
			
		||||
  const isChange =
 | 
			
		||||
    codeMirrorRanges.length !== selectionRanges.codeBasedSelections.length ||
 | 
			
		||||
    codeMirrorRanges.length !== selectionRanges?.graphSelections?.length ||
 | 
			
		||||
    codeMirrorRanges.some(({ from, to }, i) => {
 | 
			
		||||
      return (
 | 
			
		||||
        from !== selectionRanges.codeBasedSelections[i].range[0] ||
 | 
			
		||||
        to !== selectionRanges.codeBasedSelections[i].range[1]
 | 
			
		||||
        from !== selectionRanges.graphSelections[i]?.codeRef?.range[0] ||
 | 
			
		||||
        to !== selectionRanges.graphSelections[i]?.codeRef?.range[1]
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
  if (!isChange) return null
 | 
			
		||||
  const codeBasedSelections: Selections['codeBasedSelections'] =
 | 
			
		||||
  const codeBasedSelections: Selections['graphSelections'] =
 | 
			
		||||
    codeMirrorRanges.map(({ from, to }) => {
 | 
			
		||||
      const pathToNode = getNodePathFromSourceRange(ast, [from, to])
 | 
			
		||||
      return {
 | 
			
		||||
        type: 'default',
 | 
			
		||||
        codeRef: {
 | 
			
		||||
          range: [from, to],
 | 
			
		||||
          pathToNode,
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  const idBasedSelections: SelectionToEngine[] =
 | 
			
		||||
    codeToIdSelections(codeBasedSelections)
 | 
			
		||||
  const selections: Selection[] = []
 | 
			
		||||
  for (const { id, range } of idBasedSelections) {
 | 
			
		||||
    if (!id) {
 | 
			
		||||
      const pathToNode = getNodePathFromSourceRange(ast, range)
 | 
			
		||||
      selections.push({
 | 
			
		||||
        codeRef: {
 | 
			
		||||
          range,
 | 
			
		||||
          pathToNode,
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
      continue
 | 
			
		||||
    }
 | 
			
		||||
    const artifact = engineCommandManager.artifactGraph.get(id)
 | 
			
		||||
    const codeRefs = getCodeRefsByArtifactId(
 | 
			
		||||
      id,
 | 
			
		||||
      engineCommandManager.artifactGraph
 | 
			
		||||
    )
 | 
			
		||||
    if (artifact && codeRefs) {
 | 
			
		||||
      selections.push({ artifact, codeRef: codeRefs[0] })
 | 
			
		||||
    } else if (codeRefs) {
 | 
			
		||||
      selections.push({ codeRef: codeRefs[0] })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (!selectionRanges) return null
 | 
			
		||||
  updateSceneObjectColors(codeBasedSelections)
 | 
			
		||||
@ -350,11 +395,13 @@ export function processCodeMirrorRanges({
 | 
			
		||||
        selectionType: 'mirrorCodeMirrorSelections',
 | 
			
		||||
        selection: {
 | 
			
		||||
          otherSelections: isShiftDown ? selectionRanges.otherSelections : [],
 | 
			
		||||
          codeBasedSelections,
 | 
			
		||||
          graphSelections: selections,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    engineEvents: resetAndSetEngineEntitySelectionCmds(idBasedSelections),
 | 
			
		||||
    engineEvents: resetAndSetEngineEntitySelectionCmds(
 | 
			
		||||
      idBasedSelections.filter(({ id }) => !!id)
 | 
			
		||||
    ),
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -371,7 +418,7 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
 | 
			
		||||
    if (err(nodeMeta)) return
 | 
			
		||||
    const node = nodeMeta.node
 | 
			
		||||
    const groupHasCursor = codeBasedSelections.some((selection) => {
 | 
			
		||||
      return isOverlap(selection.range, [node.start, node.end])
 | 
			
		||||
      return isOverlap(selection?.codeRef?.range, [node.start, node.end])
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    const color = groupHasCursor
 | 
			
		||||
@ -406,7 +453,7 @@ function resetAndSetEngineEntitySelectionCmds(
 | 
			
		||||
      type: 'modeling_cmd_req',
 | 
			
		||||
      cmd: {
 | 
			
		||||
        type: 'select_add',
 | 
			
		||||
        entities: selections.map(({ id }) => id),
 | 
			
		||||
        entities: selections.map(({ id }) => id).filter(isNonNullable),
 | 
			
		||||
      },
 | 
			
		||||
      cmd_id: uuidv4(),
 | 
			
		||||
    },
 | 
			
		||||
@ -426,14 +473,14 @@ export function isSelectionLastLine(
 | 
			
		||||
  code: string,
 | 
			
		||||
  i = 0
 | 
			
		||||
) {
 | 
			
		||||
  return selectionRanges.codeBasedSelections[i].range[1] === code.length
 | 
			
		||||
  return selectionRanges.graphSelections[i]?.codeRef?.range[1] === code.length
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function isRangeBetweenCharacters(selectionRanges: Selections) {
 | 
			
		||||
  return (
 | 
			
		||||
    selectionRanges.codeBasedSelections.length === 1 &&
 | 
			
		||||
    selectionRanges.codeBasedSelections[0].range[0] === 0 &&
 | 
			
		||||
    selectionRanges.codeBasedSelections[0].range[1] === 0
 | 
			
		||||
    selectionRanges.graphSelections.length === 1 &&
 | 
			
		||||
    selectionRanges.graphSelections[0]?.codeRef?.range[0] === 0 &&
 | 
			
		||||
    selectionRanges.graphSelections[0]?.codeRef?.range[1] === 0
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -444,7 +491,7 @@ export type CommonASTNode = {
 | 
			
		||||
 | 
			
		||||
function buildCommonNodeFromSelection(selectionRanges: Selections, i: number) {
 | 
			
		||||
  return {
 | 
			
		||||
    selection: selectionRanges.codeBasedSelections[i],
 | 
			
		||||
    selection: selectionRanges.graphSelections[i],
 | 
			
		||||
    ast: kclManager.ast,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -476,7 +523,7 @@ function nodeHasCircle(node: CommonASTNode) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function canSweepSelection(selection: Selections) {
 | 
			
		||||
  const commonNodes = selection.codeBasedSelections.map((_, i) =>
 | 
			
		||||
  const commonNodes = selection.graphSelections.map((_, i) =>
 | 
			
		||||
    buildCommonNodeFromSelection(selection, i)
 | 
			
		||||
  )
 | 
			
		||||
  return (
 | 
			
		||||
@ -488,33 +535,8 @@ export function canSweepSelection(selection: Selections) {
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function canFilletSelection(selection: Selections) {
 | 
			
		||||
  const commonNodes = selection.codeBasedSelections.map((_, i) =>
 | 
			
		||||
    buildCommonNodeFromSelection(selection, i)
 | 
			
		||||
  ) // TODO FILLET DUMMY PLACEHOLDER
 | 
			
		||||
  return (
 | 
			
		||||
    !!isSketchPipe(selection) &&
 | 
			
		||||
    commonNodes.every((n) => nodeHasClose(n)) &&
 | 
			
		||||
    commonNodes.every((n) => !nodeHasExtrude(n))
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function canExtrudeSelectionItem(selection: Selections, i: number) {
 | 
			
		||||
  const isolatedSelection = {
 | 
			
		||||
    ...selection,
 | 
			
		||||
    codeBasedSelections: [selection.codeBasedSelections[i]],
 | 
			
		||||
  }
 | 
			
		||||
  const commonNode = buildCommonNodeFromSelection(selection, i)
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    !!isSketchPipe(isolatedSelection) &&
 | 
			
		||||
    (nodeHasClose(commonNode) || nodeHasCircle(commonNode)) &&
 | 
			
		||||
    !nodeHasExtrude(commonNode)
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This accounts for non-geometry selections under "other"
 | 
			
		||||
export type ResolvedSelectionType = [Selection['type'] | 'other', number]
 | 
			
		||||
export type ResolvedSelectionType = [Artifact['type'] | 'other', number]
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * In the future, I'd like this function to properly return the type of each selected entity based on
 | 
			
		||||
@ -527,17 +549,16 @@ export function getSelectionType(
 | 
			
		||||
  selection?: Selections
 | 
			
		||||
): ResolvedSelectionType[] {
 | 
			
		||||
  if (!selection) return []
 | 
			
		||||
  const extrudableCount = selection.codeBasedSelections.filter((_, i) => {
 | 
			
		||||
    const singleSelection = {
 | 
			
		||||
      ...selection,
 | 
			
		||||
      codeBasedSelections: [selection.codeBasedSelections[i]],
 | 
			
		||||
    }
 | 
			
		||||
    return canExtrudeSelectionItem(singleSelection, 0)
 | 
			
		||||
  }).length
 | 
			
		||||
 | 
			
		||||
  return extrudableCount === selection.codeBasedSelections.length
 | 
			
		||||
    ? [['extrude-wall', extrudableCount]]
 | 
			
		||||
    : [['other', selection.codeBasedSelections.length]]
 | 
			
		||||
  const selectionsWithArtifacts = selection.graphSelections.filter(
 | 
			
		||||
    (s) => !!s.artifact
 | 
			
		||||
  )
 | 
			
		||||
  const firstSelection = selectionsWithArtifacts[0]
 | 
			
		||||
  const firstSelectionType = firstSelection?.artifact?.type
 | 
			
		||||
  if (!firstSelectionType) return []
 | 
			
		||||
  const selectionsWithSameType = selectionsWithArtifacts.filter(
 | 
			
		||||
    (s) => s.artifact?.type === firstSelection.artifact?.type
 | 
			
		||||
  )
 | 
			
		||||
  return [[firstSelectionType, selectionsWithSameType.length]]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getSelectionTypeDisplayText(
 | 
			
		||||
@ -549,9 +570,10 @@ export function getSelectionTypeDisplayText(
 | 
			
		||||
    .map(
 | 
			
		||||
      // Hack for showing "face" instead of "extrude-wall" in command bar text
 | 
			
		||||
      ([type, count]) =>
 | 
			
		||||
        `${count} ${type.replace('extrude-wall', 'face')}${
 | 
			
		||||
          count > 1 ? 's' : ''
 | 
			
		||||
        }`
 | 
			
		||||
        `${count} ${type
 | 
			
		||||
          .replace('wall', 'face')
 | 
			
		||||
          .replace('solid2D', 'face')
 | 
			
		||||
          .replace('segment', 'face')}${count > 1 ? 's' : ''}`
 | 
			
		||||
    )
 | 
			
		||||
    .join(', ')
 | 
			
		||||
}
 | 
			
		||||
@ -572,10 +594,14 @@ export function canSubmitSelectionArg(
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function codeToIdSelections(
 | 
			
		||||
  codeBasedSelections: Selection[]
 | 
			
		||||
export function codeToIdSelections(
 | 
			
		||||
  selections: Selection[]
 | 
			
		||||
): SelectionToEngine[] {
 | 
			
		||||
  return codeBasedSelections
 | 
			
		||||
  const selectionsOld = convertSelectionsToOld({
 | 
			
		||||
    graphSelections: selections,
 | 
			
		||||
    otherSelections: [],
 | 
			
		||||
  }).codeBasedSelections
 | 
			
		||||
  return selectionsOld
 | 
			
		||||
    .flatMap((selection): null | SelectionToEngine[] => {
 | 
			
		||||
      const { type } = selection
 | 
			
		||||
      // TODO #868: loops over all artifacts will become inefficient at a large scale
 | 
			
		||||
@ -607,19 +633,26 @@ function codeToIdSelections(
 | 
			
		||||
        | {
 | 
			
		||||
            id: ArtifactId
 | 
			
		||||
            artifact: unknown
 | 
			
		||||
            selection: Selection
 | 
			
		||||
            selection: Selection__old
 | 
			
		||||
          }
 | 
			
		||||
        | undefined
 | 
			
		||||
      overlappingEntries.forEach((entry) => {
 | 
			
		||||
        // TODO probably need to remove much of the `type === 'xyz'` below
 | 
			
		||||
        if (type === 'default' && entry.artifact.type === 'segment') {
 | 
			
		||||
          bestCandidate = entry
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        if (type === 'solid2D' && entry.artifact.type === 'path') {
 | 
			
		||||
          const solid = engineCommandManager.artifactGraph.get(
 | 
			
		||||
        if (entry.artifact.type === 'path') {
 | 
			
		||||
          const artifact = engineCommandManager.artifactGraph.get(
 | 
			
		||||
            entry.artifact.solid2dId || ''
 | 
			
		||||
          )
 | 
			
		||||
          if (solid?.type !== 'solid2D') return
 | 
			
		||||
          if (artifact?.type !== 'solid2D') {
 | 
			
		||||
            bestCandidate = {
 | 
			
		||||
              artifact: entry.artifact,
 | 
			
		||||
              selection,
 | 
			
		||||
              id: entry.id,
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          if (!entry.artifact.solid2dId) {
 | 
			
		||||
            console.error(
 | 
			
		||||
              'Expected PathArtifact to have solid2dId, but none found'
 | 
			
		||||
@ -627,7 +660,7 @@ function codeToIdSelections(
 | 
			
		||||
            return
 | 
			
		||||
          }
 | 
			
		||||
          bestCandidate = {
 | 
			
		||||
            artifact: solid,
 | 
			
		||||
            artifact: artifact,
 | 
			
		||||
            selection,
 | 
			
		||||
            id: entry.artifact.solid2dId,
 | 
			
		||||
          }
 | 
			
		||||
@ -752,10 +785,11 @@ function codeToIdSelections(
 | 
			
		||||
          {
 | 
			
		||||
            type,
 | 
			
		||||
            id: bestCandidate.id,
 | 
			
		||||
            range: bestCandidate.selection.range,
 | 
			
		||||
          },
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
      return null
 | 
			
		||||
      return [selection]
 | 
			
		||||
    })
 | 
			
		||||
    .filter(isNonNullable)
 | 
			
		||||
}
 | 
			
		||||
@ -798,32 +832,58 @@ export function updateSelections(
 | 
			
		||||
 | 
			
		||||
  const newSelections = Object.entries(pathToNodeMap)
 | 
			
		||||
    .map(([index, pathToNode]): Selection | undefined => {
 | 
			
		||||
      const previousSelection =
 | 
			
		||||
        prevSelectionRanges.graphSelections[Number(index)]
 | 
			
		||||
      const nodeMeta = getNodeFromPath<Expr>(ast, pathToNode)
 | 
			
		||||
      if (err(nodeMeta)) return undefined
 | 
			
		||||
      const node = nodeMeta.node
 | 
			
		||||
      const selection = prevSelectionRanges.codeBasedSelections[Number(index)]
 | 
			
		||||
      if (
 | 
			
		||||
        selection?.type === 'base-edgeCut' ||
 | 
			
		||||
        selection?.type === 'adjacent-edgeCut' ||
 | 
			
		||||
        selection?.type === 'opposite-edgeCut'
 | 
			
		||||
      let artifact: Artifact | null = null
 | 
			
		||||
      for (const [id, a] of engineCommandManager.artifactGraph) {
 | 
			
		||||
        if (previousSelection?.artifact?.type === a.type) {
 | 
			
		||||
          const codeRefs = getCodeRefsByArtifactId(
 | 
			
		||||
            id,
 | 
			
		||||
            engineCommandManager.artifactGraph
 | 
			
		||||
          )
 | 
			
		||||
        return {
 | 
			
		||||
          range: [node.start, node.end],
 | 
			
		||||
          type: selection?.type,
 | 
			
		||||
          secondaryRange: selection?.secondaryRange,
 | 
			
		||||
          if (!codeRefs) continue
 | 
			
		||||
          if (
 | 
			
		||||
            JSON.stringify(codeRefs[0].pathToNode) ===
 | 
			
		||||
            JSON.stringify(pathToNode)
 | 
			
		||||
          ) {
 | 
			
		||||
            artifact = a
 | 
			
		||||
            console.log('found artifact', a)
 | 
			
		||||
            break
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (!artifact) return undefined
 | 
			
		||||
      return {
 | 
			
		||||
        artifact: artifact,
 | 
			
		||||
        codeRef: {
 | 
			
		||||
          range: [node.start, node.end],
 | 
			
		||||
        type: selection?.type,
 | 
			
		||||
          pathToNode: pathToNode,
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    .filter((x?: Selection) => x !== undefined) as Selection[]
 | 
			
		||||
 | 
			
		||||
  // for when there is no artifact (sketch mode since mock execute does not update artifactGraph)
 | 
			
		||||
  const pathToNodeBasedSelections: Selections['graphSelections'] = []
 | 
			
		||||
  for (const pathToNode of Object.values(pathToNodeMap)) {
 | 
			
		||||
    const node = getNodeFromPath<Expr>(ast, pathToNode)
 | 
			
		||||
    if (err(node)) return node
 | 
			
		||||
    pathToNodeBasedSelections.push({
 | 
			
		||||
      codeRef: {
 | 
			
		||||
        range: [node.node.start, node.node.end],
 | 
			
		||||
        pathToNode: pathToNode,
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    codeBasedSelections:
 | 
			
		||||
      newSelections.length > 0
 | 
			
		||||
    graphSelections:
 | 
			
		||||
      newSelections.length >= pathToNodeBasedSelections.length
 | 
			
		||||
        ? newSelections
 | 
			
		||||
        : prevSelectionRanges.codeBasedSelections,
 | 
			
		||||
        : pathToNodeBasedSelections,
 | 
			
		||||
    otherSelections: prevSelectionRanges.otherSelections,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,8 +18,6 @@ window.tearDown = engineCommandManager.tearDown
 | 
			
		||||
export const kclManager = new KclManager(engineCommandManager)
 | 
			
		||||
engineCommandManager.kclManager = kclManager
 | 
			
		||||
 | 
			
		||||
engineCommandManager.getAstCb = () => kclManager.ast
 | 
			
		||||
 | 
			
		||||
export const sceneInfra = new SceneInfra(engineCommandManager)
 | 
			
		||||
engineCommandManager.camControlsCameraChange = sceneInfra.onCameraChange
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -35,8 +35,8 @@ export function useCalculateKclExpression({
 | 
			
		||||
  const { programMemory, code } = useKclContext()
 | 
			
		||||
  const { context } = useModelingContext()
 | 
			
		||||
  const selectionRange:
 | 
			
		||||
    | (typeof context.selectionRanges.codeBasedSelections)[number]['range']
 | 
			
		||||
    | undefined = context.selectionRanges.codeBasedSelections[0]?.range
 | 
			
		||||
    | (typeof context)['selectionRanges']['graphSelections'][number]['codeRef']['range']
 | 
			
		||||
    | undefined = context.selectionRanges.graphSelections[0]?.codeRef?.range
 | 
			
		||||
  const inputRef = useRef<HTMLInputElement>(null)
 | 
			
		||||
  const [availableVarInfo, setAvailableVarInfo] = useState<
 | 
			
		||||
    ReturnType<typeof findAllPreviousVariables>
 | 
			
		||||
@ -102,6 +102,7 @@ export function useCalculateKclExpression({
 | 
			
		||||
        engineCommandManager,
 | 
			
		||||
        useFakeExecutor: true,
 | 
			
		||||
        programMemoryOverride: kclManager.programMemory.clone(),
 | 
			
		||||
        idGenerator: kclManager.execState.idGenerator,
 | 
			
		||||
      })
 | 
			
		||||
      const resultDeclaration = ast.body.find(
 | 
			
		||||
        (a) =>
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@ import { useEffect, useState } from 'react'
 | 
			
		||||
export function usePreviousVariables() {
 | 
			
		||||
  const { programMemory, code } = useKclContext()
 | 
			
		||||
  const { context } = useModelingContext()
 | 
			
		||||
  const selectionRange = context.selectionRanges.codeBasedSelections[0]
 | 
			
		||||
  const selectionRange = context.selectionRanges.graphSelections[0]?.codeRef
 | 
			
		||||
    ?.range || [code.length, code.length]
 | 
			
		||||
  const [previousVariablesInfo, setPreviousVariablesInfo] = useState<
 | 
			
		||||
    ReturnType<typeof findAllPreviousVariables>
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,7 @@ import {
 | 
			
		||||
  CommandArgumentWithName,
 | 
			
		||||
  KclCommandValue,
 | 
			
		||||
} from 'lib/commandTypes'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { Selections__old } from 'lib/selections'
 | 
			
		||||
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
 | 
			
		||||
import { MachineManager } from 'components/MachineManagerProvider'
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ export type CommandBarContext = {
 | 
			
		||||
  commands: Command[]
 | 
			
		||||
  selectedCommand?: Command
 | 
			
		||||
  currentArgument?: CommandArgument<unknown> & { name: string }
 | 
			
		||||
  selectionRanges: Selections
 | 
			
		||||
  selectionRanges: Selections__old
 | 
			
		||||
  argumentsToSubmit: { [x: string]: unknown }
 | 
			
		||||
  machineManager: MachineManager
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||