Disable extrude button if there is no extrudable geometry (#2730)
Disable extrude button if there is no extrudeable geometry
This commit is contained in:
		@ -63,6 +63,7 @@ import {
 | 
			
		||||
import {
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
  getNodePathFromSourceRange,
 | 
			
		||||
  hasExtrudableGeometry,
 | 
			
		||||
  isSingleCursorInPipe,
 | 
			
		||||
} from 'lang/queryAst'
 | 
			
		||||
import { TEST } from 'env'
 | 
			
		||||
@ -447,8 +448,13 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
          if (
 | 
			
		||||
            selectionRanges.codeBasedSelections.length === 0 ||
 | 
			
		||||
            isSelectionLastLine(selectionRanges, codeManager.code)
 | 
			
		||||
          )
 | 
			
		||||
            return true
 | 
			
		||||
          ) {
 | 
			
		||||
            // they have no selection, we should enable the button
 | 
			
		||||
            // so they can select the face through the cmdbar
 | 
			
		||||
            // BUT only if there's extrudable geometry
 | 
			
		||||
            if (hasExtrudableGeometry(kclManager.ast)) return true
 | 
			
		||||
            return false
 | 
			
		||||
          }
 | 
			
		||||
          if (!isPipe) return false
 | 
			
		||||
 | 
			
		||||
          return canExtrudeSelection(selectionRanges)
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,8 @@ import {
 | 
			
		||||
  doesPipeHaveCallExp,
 | 
			
		||||
  hasExtrudeSketchGroup,
 | 
			
		||||
  findUsesOfTagInPipe,
 | 
			
		||||
  hasSketchPipeBeenExtruded,
 | 
			
		||||
  hasExtrudableGeometry,
 | 
			
		||||
} from './queryAst'
 | 
			
		||||
import { enginelessExecutor } from '../lib/testHelpers'
 | 
			
		||||
import {
 | 
			
		||||
@ -396,3 +398,90 @@ describe('Testing findUsesOfTagInPipe', () => {
 | 
			
		||||
    expect(result).toHaveLength(0)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
describe('Testing hasSketchPipeBeenExtruded', () => {
 | 
			
		||||
  const exampleCode = `const sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([3.29, 7.86], %)
 | 
			
		||||
  |> line([2.48, 2.44], %)
 | 
			
		||||
  |> line([2.66, 1.17], %)
 | 
			
		||||
  |> line([3.75, 0.46], %)
 | 
			
		||||
  |> line([4.99, -0.46], %, 'seg01')
 | 
			
		||||
  |> line([3.3, -2.12], %)
 | 
			
		||||
  |> line([2.16, -3.33], %)
 | 
			
		||||
  |> line([0.85, -3.08], %)
 | 
			
		||||
  |> line([-0.18, -3.36], %)
 | 
			
		||||
  |> line([-3.86, -2.73], %)
 | 
			
		||||
  |> line([-17.67, 0.85], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
const extrude001 = extrude(10, sketch001)
 | 
			
		||||
const sketch002 = startSketchOn(extrude001, 'seg01')
 | 
			
		||||
  |> startProfileAt([-12.94, 6.6], %)
 | 
			
		||||
  |> line([2.45, -0.2], %)
 | 
			
		||||
  |> line([-2, -1.25], %)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
`
 | 
			
		||||
  it('finds sketch001 pipe to be extruded', async () => {
 | 
			
		||||
    const ast = parse(exampleCode)
 | 
			
		||||
    const lineOfInterest = `line([4.99, -0.46], %, 'seg01')`
 | 
			
		||||
    const characterIndex =
 | 
			
		||||
      exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
 | 
			
		||||
    const extruded = hasSketchPipeBeenExtruded(
 | 
			
		||||
      {
 | 
			
		||||
        range: [characterIndex, characterIndex],
 | 
			
		||||
        type: 'default',
 | 
			
		||||
      },
 | 
			
		||||
      ast
 | 
			
		||||
    )
 | 
			
		||||
    expect(extruded).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
  it('find sketch002 NOT pipe to be extruded', async () => {
 | 
			
		||||
    const ast = parse(exampleCode)
 | 
			
		||||
    const lineOfInterest = `line([2.45, -0.2], %)`
 | 
			
		||||
    const characterIndex =
 | 
			
		||||
      exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
 | 
			
		||||
    const extruded = hasSketchPipeBeenExtruded(
 | 
			
		||||
      {
 | 
			
		||||
        range: [characterIndex, characterIndex],
 | 
			
		||||
        type: 'default',
 | 
			
		||||
      },
 | 
			
		||||
      ast
 | 
			
		||||
    )
 | 
			
		||||
    expect(extruded).toBeFalsy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
describe('Testing hasExtrudableGeometry', () => {
 | 
			
		||||
  it('finds sketch001 pipe to be extruded', async () => {
 | 
			
		||||
    const exampleCode = `const sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([3.29, 7.86], %)
 | 
			
		||||
  |> line([2.48, 2.44], %)
 | 
			
		||||
  |> line([-3.86, -2.73], %)
 | 
			
		||||
  |> line([-17.67, 0.85], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
const extrude001 = extrude(10, sketch001)
 | 
			
		||||
const sketch002 = startSketchOn(extrude001, 'seg01')
 | 
			
		||||
  |> startProfileAt([-12.94, 6.6], %)
 | 
			
		||||
  |> line([2.45, -0.2], %)
 | 
			
		||||
  |> line([-2, -1.25], %)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
`
 | 
			
		||||
    const ast = parse(exampleCode)
 | 
			
		||||
    const extrudable = hasExtrudableGeometry(ast)
 | 
			
		||||
    expect(extrudable).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
  it('find sketch002 NOT pipe to be extruded', async () => {
 | 
			
		||||
    const exampleCode = `const sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([3.29, 7.86], %)
 | 
			
		||||
  |> line([2.48, 2.44], %)
 | 
			
		||||
  |> line([-3.86, -2.73], %)
 | 
			
		||||
  |> line([-17.67, 0.85], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
const extrude001 = extrude(10, sketch001)
 | 
			
		||||
`
 | 
			
		||||
    const ast = parse(exampleCode)
 | 
			
		||||
    const extrudable = hasExtrudableGeometry(ast)
 | 
			
		||||
    expect(extrudable).toBeFalsy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -720,3 +720,78 @@ export function findUsesOfTagInPipe(
 | 
			
		||||
  })
 | 
			
		||||
  return dependentRanges
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
 | 
			
		||||
  const path = getNodePathFromSourceRange(ast, selection.range)
 | 
			
		||||
  const { node: pipeExpression } = getNodeFromPath<PipeExpression>(
 | 
			
		||||
    ast,
 | 
			
		||||
    path,
 | 
			
		||||
    'PipeExpression'
 | 
			
		||||
  )
 | 
			
		||||
  if (pipeExpression.type !== 'PipeExpression') return false
 | 
			
		||||
  const varDec = getNodeFromPath<VariableDeclarator>(
 | 
			
		||||
    ast,
 | 
			
		||||
    path,
 | 
			
		||||
    'VariableDeclarator'
 | 
			
		||||
  ).node
 | 
			
		||||
  let extruded = false
 | 
			
		||||
  traverse(ast as any, {
 | 
			
		||||
    enter(node) {
 | 
			
		||||
      if (
 | 
			
		||||
        node.type === 'CallExpression' &&
 | 
			
		||||
        node.callee.type === 'Identifier' &&
 | 
			
		||||
        node.callee.name === 'extrude' &&
 | 
			
		||||
        node.arguments?.[1]?.type === 'Identifier' &&
 | 
			
		||||
        node.arguments[1].name === varDec.id.name
 | 
			
		||||
      ) {
 | 
			
		||||
        extruded = true
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
  return extruded
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** File must contain at least one sketch that has not been extruded already */
 | 
			
		||||
export function hasExtrudableGeometry(ast: Program) {
 | 
			
		||||
  const theMap: any = {}
 | 
			
		||||
  traverse(ast as any, {
 | 
			
		||||
    enter(node) {
 | 
			
		||||
      if (
 | 
			
		||||
        node.type === 'VariableDeclarator' &&
 | 
			
		||||
        node.init?.type === 'PipeExpression'
 | 
			
		||||
      ) {
 | 
			
		||||
        let hasStartProfileAt = false
 | 
			
		||||
        let hasStartSketchOn = false
 | 
			
		||||
        let hasClose = false
 | 
			
		||||
        for (const pipe of node.init.body) {
 | 
			
		||||
          if (
 | 
			
		||||
            pipe.type === 'CallExpression' &&
 | 
			
		||||
            pipe.callee.name === 'startProfileAt'
 | 
			
		||||
          ) {
 | 
			
		||||
            hasStartProfileAt = true
 | 
			
		||||
          }
 | 
			
		||||
          if (
 | 
			
		||||
            pipe.type === 'CallExpression' &&
 | 
			
		||||
            pipe.callee.name === 'startSketchOn'
 | 
			
		||||
          ) {
 | 
			
		||||
            hasStartSketchOn = true
 | 
			
		||||
          }
 | 
			
		||||
          if (pipe.type === 'CallExpression' && pipe.callee.name === 'close') {
 | 
			
		||||
            hasClose = true
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (hasStartProfileAt && hasStartSketchOn && hasClose) {
 | 
			
		||||
          theMap[node.id.name] = true
 | 
			
		||||
        }
 | 
			
		||||
      } else if (
 | 
			
		||||
        node.type === 'CallExpression' &&
 | 
			
		||||
        node.callee.name === 'extrude' &&
 | 
			
		||||
        node.arguments[1]?.type === 'Identifier' &&
 | 
			
		||||
        theMap?.[node?.arguments?.[1]?.name]
 | 
			
		||||
      ) {
 | 
			
		||||
        delete theMap[node.arguments[1].name]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
  })
 | 
			
		||||
  return Object.keys(theMap).length > 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ import { Program } from 'lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  doesPipeHaveCallExp,
 | 
			
		||||
  getNodeFromPath,
 | 
			
		||||
  hasSketchPipeBeenExtruded,
 | 
			
		||||
  isSingleCursorInPipe,
 | 
			
		||||
} from 'lang/queryAst'
 | 
			
		||||
import { CommandArgument } from './commandTypes'
 | 
			
		||||
@ -387,6 +388,7 @@ export function canExtrudeSelection(selection: Selections) {
 | 
			
		||||
  )
 | 
			
		||||
  return (
 | 
			
		||||
    !!isSketchPipe(selection) &&
 | 
			
		||||
    commonNodes.every((n) => !hasSketchPipeBeenExtruded(n.selection, n.ast)) &&
 | 
			
		||||
    commonNodes.every((n) => nodeHasClose(n)) &&
 | 
			
		||||
    commonNodes.every((n) => !nodeHasExtrude(n))
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user