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