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) => {
|
replaceCode = async (findCode: string, replaceCode: string) => {
|
||||||
const lines = await this.page.locator('.cm-line').all()
|
const lines = await this.page.locator('.cm-line').all()
|
||||||
|
|
||||||
let code = (await Promise.all(lines.map((c) => c.textContent()))).join('\n')
|
let code = (await Promise.all(lines.map((c) => c.textContent()))).join('\n')
|
||||||
if (!lines) return
|
if (!findCode) {
|
||||||
code = code.replace(findCode, replaceCode)
|
// nuke everything
|
||||||
|
code = replaceCode
|
||||||
|
} else {
|
||||||
|
if (!lines) return
|
||||||
|
code = code.replace(findCode, replaceCode)
|
||||||
|
}
|
||||||
await this.codeContent.fill(code)
|
await this.codeContent.fill(code)
|
||||||
}
|
}
|
||||||
checkIfPaneIsOpen() {
|
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 ({
|
test('Can add multiple profiles to a sketch (all tool types)', async ({
|
||||||
scene,
|
scene,
|
||||||
toolbar,
|
toolbar,
|
||||||
|
@ -353,6 +353,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await scene.connectionEstablished()
|
||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
const camPosition1 = async () => {
|
const camPosition1 = async () => {
|
||||||
|
@ -580,8 +580,7 @@ export class SceneEntities {
|
|||||||
if (interaction !== 'none') return
|
if (interaction !== 'none') return
|
||||||
if (args.mouseEvent.which !== 1) return
|
if (args.mouseEvent.which !== 1) return
|
||||||
const { intersectionPoint } = args
|
const { intersectionPoint } = args
|
||||||
if (!intersectionPoint?.twoD || !sketchDetails?.sketchEntryNodePath)
|
if (!intersectionPoint?.twoD) return
|
||||||
return
|
|
||||||
|
|
||||||
const parent = getParentGroup(
|
const parent = getParentGroup(
|
||||||
args?.intersects?.[0]?.object,
|
args?.intersects?.[0]?.object,
|
||||||
@ -616,7 +615,7 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const inserted = insertNewStartProfileAt(
|
const inserted = insertNewStartProfileAt(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
sketchDetails.sketchEntryNodePath,
|
sketchDetails.sketchEntryNodePath || [],
|
||||||
sketchDetails.sketchNodePaths,
|
sketchDetails.sketchNodePaths,
|
||||||
sketchDetails.planeNodePath,
|
sketchDetails.planeNodePath,
|
||||||
[snappedClickPoint.x, snappedClickPoint.y],
|
[snappedClickPoint.x, snappedClickPoint.y],
|
||||||
|
@ -114,10 +114,11 @@ import { useToken } from 'machines/appMachine'
|
|||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { useSettings } from 'machines/appMachine'
|
import { useSettings } from 'machines/appMachine'
|
||||||
import { IndexLoaderData } from 'lib/types'
|
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 { EXPORT_TOAST_MESSAGES, MAKE_TOAST_MESSAGES } from 'lib/constants'
|
||||||
import { exportMake } from 'lib/exportMake'
|
import { exportMake } from 'lib/exportMake'
|
||||||
import { exportSave } from 'lib/exportSave'
|
import { exportSave } from 'lib/exportSave'
|
||||||
|
import { Plane } from '@rust/kcl-lib/bindings/Plane'
|
||||||
|
|
||||||
export const ModelingMachineContext = createContext(
|
export const ModelingMachineContext = createContext(
|
||||||
{} as {
|
{} as {
|
||||||
@ -573,8 +574,9 @@ export const ModelingMachineProvider = ({
|
|||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
selectionRanges.graphSelections[0]
|
selectionRanges.graphSelections[0]
|
||||||
)
|
)
|
||||||
)
|
) {
|
||||||
return false
|
return false
|
||||||
|
}
|
||||||
return !!isCursorInSketchCommandRange(
|
return !!isCursorInSketchCommandRange(
|
||||||
engineCommandManager.artifactGraph,
|
engineCommandManager.artifactGraph,
|
||||||
selectionRanges
|
selectionRanges
|
||||||
@ -602,7 +604,6 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let fileName = file?.name?.replace('.kcl', `.${input.type}`) || ''
|
let fileName = file?.name?.replace('.kcl', `.${input.type}`) || ''
|
||||||
console.log('fileName', fileName)
|
|
||||||
// Ensure the file has an extension.
|
// Ensure the file has an extension.
|
||||||
if (!fileName.includes('.')) {
|
if (!fileName.includes('.')) {
|
||||||
fileName += `.${input.type}`
|
fileName += `.${input.type}`
|
||||||
@ -852,6 +853,7 @@ export const ModelingMachineProvider = ({
|
|||||||
? artifact?.pathId
|
? artifact?.pathId
|
||||||
: plane?.pathIds[0]
|
: plane?.pathIds[0]
|
||||||
let sketch: KclValue | null = null
|
let sketch: KclValue | null = null
|
||||||
|
let planeVar: Plane | null = null
|
||||||
for (const variable of Object.values(
|
for (const variable of Object.values(
|
||||||
kclManager.execState.variables
|
kclManager.execState.variables
|
||||||
)) {
|
)) {
|
||||||
@ -875,13 +877,43 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
variable?.type === 'Plane' &&
|
||||||
|
plane.id === variable.value.id
|
||||||
|
) {
|
||||||
|
planeVar = variable.value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!sketch || sketch.type !== 'Sketch')
|
if (!sketch || sketch.type !== 'Sketch') {
|
||||||
return Promise.reject(new Error('No sketch'))
|
if (artifact?.type !== 'plane')
|
||||||
if (!sketch || sketch.type !== 'Sketch')
|
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'))
|
return Promise.reject(new Error('No sketch'))
|
||||||
|
}
|
||||||
const info = await getSketchOrientationDetails(sketch.value)
|
const info = await getSketchOrientationDetails(sketch.value)
|
||||||
|
|
||||||
await letEngineAnimateAndSyncCamAfter(
|
await letEngineAnimateAndSyncCamAfter(
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
info?.sketchDetails?.faceId || ''
|
info?.sketchDetails?.faceId || ''
|
||||||
@ -1576,7 +1608,7 @@ export const ModelingMachineProvider = ({
|
|||||||
'setup-client-side-sketch-segments': fromPromise(
|
'setup-client-side-sketch-segments': fromPromise(
|
||||||
async ({ input: { sketchDetails, selectionRanges } }) => {
|
async ({ input: { sketchDetails, selectionRanges } }) => {
|
||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
if (!sketchDetails.sketchEntryNodePath.length) return
|
if (!sketchDetails.sketchEntryNodePath?.length) return
|
||||||
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
|
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
|
||||||
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||||
}
|
}
|
||||||
@ -1617,6 +1649,9 @@ export const ModelingMachineProvider = ({
|
|||||||
updatedPlaneNodePath: sketchDetails.planeNodePath,
|
updatedPlaneNodePath: sketchDetails.planeNodePath,
|
||||||
expressionIndexToDelete: -1,
|
expressionIndexToDelete: -1,
|
||||||
} as const
|
} as const
|
||||||
|
if (!sketchDetails?.sketchEntryNodePath?.length) {
|
||||||
|
return existingSketchInfoNoOp
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
!sketchDetails.sketchNodePaths.length &&
|
!sketchDetails.sketchNodePaths.length &&
|
||||||
sketchDetails.planeNodePath.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(
|
useSetupEngineManager(
|
||||||
streamRef,
|
streamRef,
|
||||||
modelingSend,
|
modelingSend,
|
||||||
|
@ -61,6 +61,7 @@ export function getNodeFromPath<T>(
|
|||||||
path: PathToNode,
|
path: PathToNode,
|
||||||
stopAt?: SyntaxType | SyntaxType[],
|
stopAt?: SyntaxType | SyntaxType[],
|
||||||
returnEarly = false,
|
returnEarly = false,
|
||||||
|
suppressNoise = false,
|
||||||
replacement?: any
|
replacement?: any
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
@ -105,9 +106,11 @@ export function getNodeFromPath<T>(
|
|||||||
.filter((a) => a)
|
.filter((a) => a)
|
||||||
.join(' > ')}`
|
.join(' > ')}`
|
||||||
)
|
)
|
||||||
console.error(tree)
|
if (!suppressNoise) {
|
||||||
console.error(sourceCode)
|
console.error(tree)
|
||||||
console.error(error.stack)
|
console.error(sourceCode)
|
||||||
|
console.error(error.stack)
|
||||||
|
}
|
||||||
return error
|
return error
|
||||||
}
|
}
|
||||||
parent = currentNode
|
parent = currentNode
|
||||||
@ -967,3 +970,11 @@ export function getSettingsAnnotation(
|
|||||||
|
|
||||||
return settings
|
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)
|
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.
|
// 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
|
const mustReplaceNode = primaryCallExp.type !== callExpr.node.type
|
||||||
if (mustReplaceNode) {
|
if (mustReplaceNode) {
|
||||||
getNodeFromPath(_node, pathToNode, ['CallExpression'], false, {
|
getNodeFromPath(_node, pathToNode, ['CallExpression'], false, false, {
|
||||||
...primaryCallExp,
|
...primaryCallExp,
|
||||||
start: callExpr.node.start,
|
start: callExpr.node.start,
|
||||||
end: callExpr.node.end,
|
end: callExpr.node.end,
|
||||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user