fix no profile errors (#5877)
* fix no profile errors * add test and tweak a couple things * quick fix * fix animation * add another test * Use actor.getSnapshot in the debug function So we don't have to rebuild that listener every time that the state changes. * try fix tests --------- Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
This commit is contained in:
		@ -152,9 +152,15 @@ export class EditorFixture {
 | 
			
		||||
  }
 | 
			
		||||
  replaceCode = async (findCode: string, replaceCode: string) => {
 | 
			
		||||
    const lines = await this.page.locator('.cm-line').all()
 | 
			
		||||
 | 
			
		||||
    let code = (await Promise.all(lines.map((c) => c.textContent()))).join('\n')
 | 
			
		||||
    if (!lines) return
 | 
			
		||||
    code = code.replace(findCode, replaceCode)
 | 
			
		||||
    if (!findCode) {
 | 
			
		||||
      // nuke everything
 | 
			
		||||
      code = replaceCode
 | 
			
		||||
    } else {
 | 
			
		||||
      if (!lines) return
 | 
			
		||||
      code = code.replace(findCode, replaceCode)
 | 
			
		||||
    }
 | 
			
		||||
    await this.codeContent.fill(code)
 | 
			
		||||
  }
 | 
			
		||||
  checkIfPaneIsOpen() {
 | 
			
		||||
 | 
			
		||||
@ -1662,6 +1662,96 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
  test('can enter sketch mode for sketch with no profiles', async ({
 | 
			
		||||
    scene,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    editor,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
  }) => {
 | 
			
		||||
    await page.addInitScript(async () => {
 | 
			
		||||
      localStorage.setItem(
 | 
			
		||||
        'persistCode',
 | 
			
		||||
        `sketch001 = startSketchOn('XY')
 | 
			
		||||
`
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.connectionEstablished()
 | 
			
		||||
    await scene.settled(cmdBar)
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByRole('button', { name: 'Start Sketch' })
 | 
			
		||||
    ).not.toBeDisabled()
 | 
			
		||||
 | 
			
		||||
    // open feature tree and double click the first sketch
 | 
			
		||||
    await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick()
 | 
			
		||||
    await page.waitForTimeout(600)
 | 
			
		||||
 | 
			
		||||
    // click in the scene twice to add a segment
 | 
			
		||||
    const [startProfile1] = scene.makeMouseHelpers(658, 140)
 | 
			
		||||
    const [segment1Clk] = scene.makeMouseHelpers(701, 200)
 | 
			
		||||
 | 
			
		||||
    // wait for line to be aria pressed
 | 
			
		||||
    await expect
 | 
			
		||||
      .poll(async () => toolbar.lineBtn.getAttribute('aria-pressed'))
 | 
			
		||||
      .toBe('true')
 | 
			
		||||
 | 
			
		||||
    await startProfile1()
 | 
			
		||||
    await editor.expectEditor.toContain(`profile001 = startProfileAt`)
 | 
			
		||||
    await segment1Clk()
 | 
			
		||||
    await editor.expectEditor.toContain(`|> line(end`)
 | 
			
		||||
  })
 | 
			
		||||
  test('can delete all profiles in sketch mode and user can still equip a tool and draw something', async ({
 | 
			
		||||
    scene,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    editor,
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
  }) => {
 | 
			
		||||
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.connectionEstablished()
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByRole('button', { name: 'Start Sketch' })
 | 
			
		||||
    ).not.toBeDisabled()
 | 
			
		||||
 | 
			
		||||
    const [selectXZPlane] = scene.makeMouseHelpers(650, 150)
 | 
			
		||||
 | 
			
		||||
    await toolbar.startSketchPlaneSelection()
 | 
			
		||||
    await selectXZPlane()
 | 
			
		||||
    // timeout wait for engine animation is unavoidable
 | 
			
		||||
    await page.waitForTimeout(600)
 | 
			
		||||
    await editor.expectEditor.toContain(`sketch001 = startSketchOn('XZ')`)
 | 
			
		||||
 | 
			
		||||
    const [startProfile1] = scene.makeMouseHelpers(568, 70)
 | 
			
		||||
    const [segment1Clk] = scene.makeMouseHelpers(701, 78)
 | 
			
		||||
    const [segment2Clk] = scene.makeMouseHelpers(745, 189)
 | 
			
		||||
 | 
			
		||||
    await test.step('add two segments', async () => {
 | 
			
		||||
      await startProfile1()
 | 
			
		||||
      await editor.expectEditor.toContain(
 | 
			
		||||
        `profile001 = startProfileAt([4.61, 12.21], sketch001)`
 | 
			
		||||
      )
 | 
			
		||||
      await segment1Clk()
 | 
			
		||||
      await editor.expectEditor.toContain(`|> line(end`)
 | 
			
		||||
      await segment2Clk()
 | 
			
		||||
      await editor.expectEditor.toContain(`|> line(end = [2.98, -7.52])`)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('delete all profiles', async () => {
 | 
			
		||||
      await editor.replaceCode('', "sketch001 = startSketchOn('XZ')\n")
 | 
			
		||||
      await page.waitForTimeout(600) // wait for deferred execution
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('equip circle and draw it', async () => {
 | 
			
		||||
      await toolbar.circleBtn.click()
 | 
			
		||||
      await page.mouse.click(700, 200)
 | 
			
		||||
      await page.mouse.click(750, 200)
 | 
			
		||||
      await editor.expectEditor.toContain('circle(sketch001, center = [')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
  test('Can add multiple profiles to a sketch (all tool types)', async ({
 | 
			
		||||
    scene,
 | 
			
		||||
    toolbar,
 | 
			
		||||
 | 
			
		||||
@ -353,6 +353,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
 | 
			
		||||
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.connectionEstablished()
 | 
			
		||||
    await scene.settled(cmdBar)
 | 
			
		||||
 | 
			
		||||
    const camPosition1 = async () => {
 | 
			
		||||
 | 
			
		||||
@ -580,8 +580,7 @@ export class SceneEntities {
 | 
			
		||||
        if (interaction !== 'none') return
 | 
			
		||||
        if (args.mouseEvent.which !== 1) return
 | 
			
		||||
        const { intersectionPoint } = args
 | 
			
		||||
        if (!intersectionPoint?.twoD || !sketchDetails?.sketchEntryNodePath)
 | 
			
		||||
          return
 | 
			
		||||
        if (!intersectionPoint?.twoD) return
 | 
			
		||||
 | 
			
		||||
        const parent = getParentGroup(
 | 
			
		||||
          args?.intersects?.[0]?.object,
 | 
			
		||||
@ -616,7 +615,7 @@ export class SceneEntities {
 | 
			
		||||
 | 
			
		||||
        const inserted = insertNewStartProfileAt(
 | 
			
		||||
          kclManager.ast,
 | 
			
		||||
          sketchDetails.sketchEntryNodePath,
 | 
			
		||||
          sketchDetails.sketchEntryNodePath || [],
 | 
			
		||||
          sketchDetails.sketchNodePaths,
 | 
			
		||||
          sketchDetails.planeNodePath,
 | 
			
		||||
          [snappedClickPoint.x, snappedClickPoint.y],
 | 
			
		||||
 | 
			
		||||
@ -114,10 +114,11 @@ import { useToken } from 'machines/appMachine'
 | 
			
		||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
 | 
			
		||||
import { useSettings } from 'machines/appMachine'
 | 
			
		||||
import { IndexLoaderData } from 'lib/types'
 | 
			
		||||
import { OutputFormat3d } from '@rust/kcl-lib/bindings/ModelingCmd'
 | 
			
		||||
import { OutputFormat3d, Point3d } from '@rust/kcl-lib/bindings/ModelingCmd'
 | 
			
		||||
import { EXPORT_TOAST_MESSAGES, MAKE_TOAST_MESSAGES } from 'lib/constants'
 | 
			
		||||
import { exportMake } from 'lib/exportMake'
 | 
			
		||||
import { exportSave } from 'lib/exportSave'
 | 
			
		||||
import { Plane } from '@rust/kcl-lib/bindings/Plane'
 | 
			
		||||
 | 
			
		||||
export const ModelingMachineContext = createContext(
 | 
			
		||||
  {} as {
 | 
			
		||||
@ -573,8 +574,9 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
              kclManager.ast,
 | 
			
		||||
              selectionRanges.graphSelections[0]
 | 
			
		||||
            )
 | 
			
		||||
          )
 | 
			
		||||
          ) {
 | 
			
		||||
            return false
 | 
			
		||||
          }
 | 
			
		||||
          return !!isCursorInSketchCommandRange(
 | 
			
		||||
            engineCommandManager.artifactGraph,
 | 
			
		||||
            selectionRanges
 | 
			
		||||
@ -602,7 +604,6 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let fileName = file?.name?.replace('.kcl', `.${input.type}`) || ''
 | 
			
		||||
            console.log('fileName', fileName)
 | 
			
		||||
            // Ensure the file has an extension.
 | 
			
		||||
            if (!fileName.includes('.')) {
 | 
			
		||||
              fileName += `.${input.type}`
 | 
			
		||||
@ -852,6 +853,7 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
                ? artifact?.pathId
 | 
			
		||||
                : plane?.pathIds[0]
 | 
			
		||||
            let sketch: KclValue | null = null
 | 
			
		||||
            let planeVar: Plane | null = null
 | 
			
		||||
            for (const variable of Object.values(
 | 
			
		||||
              kclManager.execState.variables
 | 
			
		||||
            )) {
 | 
			
		||||
@ -875,13 +877,43 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
                }
 | 
			
		||||
                break
 | 
			
		||||
              }
 | 
			
		||||
              if (
 | 
			
		||||
                variable?.type === 'Plane' &&
 | 
			
		||||
                plane.id === variable.value.id
 | 
			
		||||
              ) {
 | 
			
		||||
                planeVar = variable.value
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
            if (!sketch || sketch.type !== 'Sketch')
 | 
			
		||||
              return Promise.reject(new Error('No sketch'))
 | 
			
		||||
            if (!sketch || sketch.type !== 'Sketch')
 | 
			
		||||
            if (!sketch || sketch.type !== 'Sketch') {
 | 
			
		||||
              if (artifact?.type !== 'plane')
 | 
			
		||||
                return Promise.reject(new Error('No sketch'))
 | 
			
		||||
              const planeCodeRef = getFaceCodeRef(artifact)
 | 
			
		||||
              if (planeVar && planeCodeRef) {
 | 
			
		||||
                const toTuple = (point: Point3d): [number, number, number] => [
 | 
			
		||||
                  point.x,
 | 
			
		||||
                  point.y,
 | 
			
		||||
                  point.z,
 | 
			
		||||
                ]
 | 
			
		||||
                const planPath = getNodePathFromSourceRange(
 | 
			
		||||
                  kclManager.ast,
 | 
			
		||||
                  planeCodeRef.range
 | 
			
		||||
                )
 | 
			
		||||
                await letEngineAnimateAndSyncCamAfter(
 | 
			
		||||
                  engineCommandManager,
 | 
			
		||||
                  artifact.id
 | 
			
		||||
                )
 | 
			
		||||
                return {
 | 
			
		||||
                  sketchEntryNodePath: [],
 | 
			
		||||
                  planeNodePath: planPath,
 | 
			
		||||
                  sketchNodePaths: [],
 | 
			
		||||
                  zAxis: toTuple(planeVar.zAxis),
 | 
			
		||||
                  yAxis: toTuple(planeVar.yAxis),
 | 
			
		||||
                  origin: toTuple(planeVar.origin),
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
              return Promise.reject(new Error('No sketch'))
 | 
			
		||||
            }
 | 
			
		||||
            const info = await getSketchOrientationDetails(sketch.value)
 | 
			
		||||
 | 
			
		||||
            await letEngineAnimateAndSyncCamAfter(
 | 
			
		||||
              engineCommandManager,
 | 
			
		||||
              info?.sketchDetails?.faceId || ''
 | 
			
		||||
@ -1576,7 +1608,7 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
        'setup-client-side-sketch-segments': fromPromise(
 | 
			
		||||
          async ({ input: { sketchDetails, selectionRanges } }) => {
 | 
			
		||||
            if (!sketchDetails) return
 | 
			
		||||
            if (!sketchDetails.sketchEntryNodePath.length) return
 | 
			
		||||
            if (!sketchDetails.sketchEntryNodePath?.length) return
 | 
			
		||||
            if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
 | 
			
		||||
              sceneEntitiesManager.tearDownSketch({ removeAxis: false })
 | 
			
		||||
            }
 | 
			
		||||
@ -1617,6 +1649,9 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
              updatedPlaneNodePath: sketchDetails.planeNodePath,
 | 
			
		||||
              expressionIndexToDelete: -1,
 | 
			
		||||
            } as const
 | 
			
		||||
            if (!sketchDetails?.sketchEntryNodePath?.length) {
 | 
			
		||||
              return existingSketchInfoNoOp
 | 
			
		||||
            }
 | 
			
		||||
            if (
 | 
			
		||||
              !sketchDetails.sketchNodePaths.length &&
 | 
			
		||||
              sketchDetails.planeNodePath.length
 | 
			
		||||
@ -1727,6 +1762,18 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  // Add debug function to window object
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    // @ts-ignore - we're intentionally adding this to window
 | 
			
		||||
    window.getModelingState = () => {
 | 
			
		||||
      const modelingState = modelingActor.getSnapshot()
 | 
			
		||||
      return {
 | 
			
		||||
        modelingState,
 | 
			
		||||
        id: modelingState._nodes[modelingState._nodes.length - 1].id,
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, [modelingActor])
 | 
			
		||||
 | 
			
		||||
  useSetupEngineManager(
 | 
			
		||||
    streamRef,
 | 
			
		||||
    modelingSend,
 | 
			
		||||
 | 
			
		||||
@ -61,6 +61,7 @@ export function getNodeFromPath<T>(
 | 
			
		||||
  path: PathToNode,
 | 
			
		||||
  stopAt?: SyntaxType | SyntaxType[],
 | 
			
		||||
  returnEarly = false,
 | 
			
		||||
  suppressNoise = false,
 | 
			
		||||
  replacement?: any
 | 
			
		||||
):
 | 
			
		||||
  | {
 | 
			
		||||
@ -105,9 +106,11 @@ export function getNodeFromPath<T>(
 | 
			
		||||
          .filter((a) => a)
 | 
			
		||||
          .join(' > ')}`
 | 
			
		||||
      )
 | 
			
		||||
      console.error(tree)
 | 
			
		||||
      console.error(sourceCode)
 | 
			
		||||
      console.error(error.stack)
 | 
			
		||||
      if (!suppressNoise) {
 | 
			
		||||
        console.error(tree)
 | 
			
		||||
        console.error(sourceCode)
 | 
			
		||||
        console.error(error.stack)
 | 
			
		||||
      }
 | 
			
		||||
      return error
 | 
			
		||||
    }
 | 
			
		||||
    parent = currentNode
 | 
			
		||||
@ -967,3 +970,11 @@ export function getSettingsAnnotation(
 | 
			
		||||
 | 
			
		||||
  return settings
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function pathToNodeKeys(pathToNode: PathToNode): (string | number)[] {
 | 
			
		||||
  return pathToNode.map(([key]) => key)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function stringifyPathToNode(pathToNode: PathToNode): string {
 | 
			
		||||
  return JSON.stringify(pathToNodeKeys(pathToNode))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -748,6 +748,9 @@ export function getPathsFromPlaneArtifact(
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  if (nodePaths.length === 0) {
 | 
			
		||||
    return []
 | 
			
		||||
  }
 | 
			
		||||
  return onlyConsecutivePaths(nodePaths, nodePaths[0], ast)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3541,7 +3541,7 @@ function addTagKw(): addTagFn {
 | 
			
		||||
    // If we changed the node, we must replace the old node with the new node in the AST.
 | 
			
		||||
    const mustReplaceNode = primaryCallExp.type !== callExpr.node.type
 | 
			
		||||
    if (mustReplaceNode) {
 | 
			
		||||
      getNodeFromPath(_node, pathToNode, ['CallExpression'], false, {
 | 
			
		||||
      getNodeFromPath(_node, pathToNode, ['CallExpression'], false, false, {
 | 
			
		||||
        ...primaryCallExp,
 | 
			
		||||
        start: callExpr.node.start,
 | 
			
		||||
        end: callExpr.node.end,
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user