Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
9001006
WIP: Add point-and-click Import for geometry
pierremtb Apr 2, 2025
3afcb33
Better pathToNOde, log on non-working cm dispatch call
pierremtb Apr 3, 2025
e00f6c2
Add workaround to updateModelingState not working
pierremtb Apr 3, 2025
2bae5ff
Back to updateModelingState with a skip flag
pierremtb Apr 3, 2025
79fd435
Better todo
pierremtb Apr 3, 2025
2d08d47
Update from main
pierremtb Apr 3, 2025
1bb37fc
Change working from Import to Insert, cleanups
pierremtb Apr 3, 2025
9371860
Sister command in kclCommands to populate file options
pierremtb Apr 3, 2025
69a0198
Merge branch 'main' into pierremtb/issue6120-Add-point-and-click-Impo…
pierremtb Apr 3, 2025
58c87ac
Merge branch 'main' into pierremtb/issue6120-Add-point-and-click-Impo…
pierremtb Apr 4, 2025
9e32ce3
Improve path selector
pierremtb Apr 4, 2025
5d8f7a5
Merge branch 'main' into pierremtb/issue6120-Add-point-and-click-Impo…
pierremtb Apr 4, 2025
7b7d8b4
Unsure: move importAstMod to kclCommands onSubmit :no_mouth:
pierremtb Apr 4, 2025
eaf1f09
Add e2e test
pierremtb Apr 4, 2025
8052af8
Clean up for review
pierremtb Apr 4, 2025
efcf164
Merge branch 'main' into pierremtb/issue6120-Add-point-and-click-Impo…
pierremtb Apr 4, 2025
8fa8b16
Add native file menu entry and test
pierremtb Apr 4, 2025
5f11d61
No await yo lint said so
pierremtb Apr 4, 2025
f36905b
WIP: UX improvements around foreign file imports
pierremtb Apr 4, 2025
4698339
@lrev-Dev's suggestion to remove a comment
pierremtb Apr 7, 2025
a932339
Merge branch 'main' into pierremtb/issue6120-Add-point-and-click-Impo…
pierremtb Apr 7, 2025
c5e18ba
Update to scene.settled(cmdBar)
pierremtb Apr 7, 2025
ea6eaf5
Merge branch 'pierremtb/issue6120-Add-point-and-click-Import-for-geom…
pierremtb Apr 7, 2025
7689afb
Add partNNN default name for alias
pierremtb Apr 7, 2025
36c17fd
UX improvements around foreign file imports
pierremtb Apr 7, 2025
cbaf345
Lint
pierremtb Apr 7, 2025
b098d8c
Lint
pierremtb Apr 7, 2025
60c3580
Fix unit tests
pierremtb Apr 7, 2025
3c38880
Add sad path insert test
pierremtb Apr 7, 2025
7574663
Add step insert test
pierremtb Apr 8, 2025
cd0627b
Merge branch 'main' into pierremtb/issue6152-UX-improvements-around-f…
pierremtb Apr 8, 2025
17be7a3
Lint
pierremtb Apr 8, 2025
ae72c7c
Add test for second foreign import thru file tree click
pierremtb Apr 8, 2025
f21c6fe
Add default value for local name alias
pierremtb Apr 8, 2025
7d80d30
Merge branch 'main' into pierremtb/issue6152-UX-improvements-around-f…
pierremtb Apr 8, 2025
b99a12f
Aligning tests
pierremtb Apr 8, 2025
1a538cd
Fix tests
pierremtb Apr 8, 2025
2f94ce7
Add padding for filenames starting with a digit
pierremtb Apr 8, 2025
cf34a9d
Merge branch 'main' into pierremtb/issue6152-UX-improvements-around-f…
pierremtb Apr 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 222 additions & 45 deletions e2e/playwright/point-click-assemblies.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,47 @@
import * as fsp from 'fs/promises'
import path from 'path'

import { executorInputPath } from '@e2e/playwright/test-utils'
import { test } from '@e2e/playwright/zoo-test'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
import {
executorInputPath,
getUtils,
testsInputPath,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import type { Page } from '@playwright/test'

async function insertPartIntoAssembly(
path: string,
alias: string,
toolbar: ToolbarFixture,
cmdBar: CmdBarFixture,
page: Page
) {
await toolbar.insertButton.click()
await cmdBar.selectOption({ name: path }).click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'localName',
currentArgValue: '',
headerArguments: { Path: path, LocalName: '' },
highlightedHeaderArg: 'localName',
commandName: 'Insert',
})
await page.keyboard.insertText(alias)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Path: path, LocalName: alias },
commandName: 'Insert',
})
await cmdBar.progressCmdBar()
}

// test file is for testing point an click code gen functionality that's assemblies related
test.describe('Point-and-click assemblies tests', () => {
test(
`Insert kcl part into assembly as whole module import`,
`Insert kcl parts into assembly as whole module import`,
{ tag: ['@electron'] },
async ({
context,
Expand All @@ -23,11 +57,14 @@ test.describe('Point-and-click assemblies tests', () => {
fail()
}

// One dumb hardcoded screen pixel value
const testPoint = { x: 575, y: 200 }
const initialColor: [number, number, number] = [50, 50, 50]
const partColor: [number, number, number] = [150, 150, 150]
const midPoint = { x: 500, y: 250 }
const partPoint = { x: midPoint.x + 30, y: midPoint.y - 30 } // mid point, just off top right
const defaultPlanesColor: [number, number, number] = [180, 220, 180]
const partColor: [number, number, number] = [100, 100, 100]
const tolerance = 50
const u = await getUtils(page)
const gizmo = page.locator('[aria-label*=gizmo]')
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })

await test.step('Setup parts and expect empty assembly scene', async () => {
const projectName = 'assembly'
Expand All @@ -36,79 +73,219 @@ test.describe('Point-and-click assemblies tests', () => {
await fsp.mkdir(bracketDir, { recursive: true })
await Promise.all([
fsp.copyFile(
executorInputPath('cylinder-inches.kcl'),
executorInputPath('cylinder.kcl'),
path.join(bracketDir, 'cylinder.kcl')
),
fsp.copyFile(
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
path.join(bracketDir, 'bracket.kcl')
),
fsp.copyFile(
testsInputPath('cube.step'),
path.join(bracketDir, 'cube.step')
),
fsp.writeFile(path.join(bracketDir, 'main.kcl'), ''),
])
})
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.openProject(projectName)
await scene.settled(cmdBar)
await scene.expectPixelColor(initialColor, testPoint, tolerance)
await toolbar.closePane('code')
await scene.expectPixelColor(defaultPlanesColor, midPoint, tolerance)
})

await test.step('Insert first part into the assembly', async () => {
await toolbar.insertButton.click()
await cmdBar.selectOption({ name: 'cylinder.kcl' }).click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'localName',
currentArgValue: '',
headerArguments: { Path: 'cylinder.kcl', LocalName: '' },
highlightedHeaderArg: 'localName',
commandName: 'Insert',
})
await page.keyboard.insertText('cylinder')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Path: 'cylinder.kcl', LocalName: 'cylinder' },
commandName: 'Insert',
})
await cmdBar.progressCmdBar()
await test.step('Insert kcl as first part as module', async () => {
await insertPartIntoAssembly(
'cylinder.kcl',
'cylinder',
toolbar,
cmdBar,
page
)
await toolbar.openPane('code')
await editor.expectEditor.toContain(
`
import "cylinder.kcl" as cylinder
cylinder
`,
{ shouldNormalise: true }
)
await scene.expectPixelColor(partColor, testPoint, tolerance)
await scene.settled(cmdBar)

// Check scene for changes
await toolbar.closePane('code')
await u.doAndWaitForCmd(async () => {
await gizmo.click({ button: 'right' })
await resetCameraButton.click()
}, 'zoom_to_fit')
await toolbar.closePane('debug')
await scene.expectPixelColor(partColor, partPoint, tolerance)
await toolbar.openPane('code')
})

await test.step('Insert second part into the assembly', async () => {
await toolbar.insertButton.click()
await cmdBar.selectOption({ name: 'bracket.kcl' }).click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'localName',
currentArgValue: '',
headerArguments: { Path: 'bracket.kcl', LocalName: '' },
highlightedHeaderArg: 'localName',
commandName: 'Insert',
await test.step('Insert kcl second part as module', async () => {
await insertPartIntoAssembly(
'bracket.kcl',
'bracket',
toolbar,
cmdBar,
page
)
await editor.expectEditor.toContain(
`
import "cylinder.kcl" as cylinder
import "bracket.kcl" as bracket
cylinder
bracket
`,
{ shouldNormalise: true }
)
await scene.settled(cmdBar)
})

await test.step('Insert a second time and expect error', async () => {
// TODO: revisit once we have clone with #6209
await insertPartIntoAssembly(
'bracket.kcl',
'bracket',
toolbar,
cmdBar,
page
)
await editor.expectEditor.toContain(
`
import "cylinder.kcl" as cylinder
import "bracket.kcl" as bracket
import "bracket.kcl" as bracket
cylinder
bracket
bracket
`,
{ shouldNormalise: true }
)
await scene.settled(cmdBar)
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
})
}
)

test(
`Insert foreign parts into assembly as whole module import`,
{ tag: ['@electron'] },
async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
tronApp,
}) => {
if (!tronApp) {
fail()
}

const midPoint = { x: 500, y: 250 }
const partPoint = { x: midPoint.x + 30, y: midPoint.y - 30 } // mid point, just off top right
const defaultPlanesColor: [number, number, number] = [180, 220, 180]
const partColor: [number, number, number] = [150, 150, 150]
const tolerance = 50

const complexPlmFileName = 'cube_Complex-PLM_Name_-001.sldprt'
const camelCasedSolidworksFileName = 'cubeComplexPLMName001'

await test.step('Setup parts and expect empty assembly scene', async () => {
const projectName = 'assembly'
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, projectName)
await fsp.mkdir(bracketDir, { recursive: true })
await Promise.all([
fsp.copyFile(
testsInputPath('cube.step'),
path.join(bracketDir, 'cube.step')
),
fsp.copyFile(
testsInputPath('cube.sldprt'),
path.join(bracketDir, complexPlmFileName)
),
fsp.writeFile(path.join(bracketDir, 'main.kcl'), ''),
])
})
await page.keyboard.insertText('bracket')
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.openProject(projectName)
await scene.settled(cmdBar)
await toolbar.closePane('code')
await scene.expectPixelColor(defaultPlanesColor, midPoint, tolerance)
})

await test.step('Insert step part as module', async () => {
await insertPartIntoAssembly('cube.step', 'cube', toolbar, cmdBar, page)
await toolbar.openPane('code')
await editor.expectEditor.toContain(
`
import "cube.step" as cube
cube
`,
{ shouldNormalise: true }
)
await scene.settled(cmdBar)

// TODO: remove this once #5780 is fixed
await page.reload()

await scene.settled(cmdBar)
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await toolbar.closePane('code')
await scene.expectPixelColor(partColor, partPoint, tolerance)
})

await test.step('Insert second step part by clicking', async () => {
await toolbar.openPane('files')
await toolbar.expectFileTreeState([
complexPlmFileName,
'cube.step',
'main.kcl',
])
await toolbar.openFile(complexPlmFileName)

// Go through the ToastInsert prompt
await page.getByText('Insert into my current file').click()

// Check getPathFilenameInVariableCase output
const parsedValueFromFile =
await cmdBar.currentArgumentInput.inputValue()
expect(parsedValueFromFile).toEqual(camelCasedSolidworksFileName)

// Continue on with the flow
await page.keyboard.insertText('cubeSw')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Path: 'bracket.kcl', LocalName: 'bracket' },
headerArguments: { Path: complexPlmFileName, LocalName: 'cubeSw' },
commandName: 'Insert',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('files')
await toolbar.openPane('code')
await editor.expectEditor.toContain(
`
import "cylinder.kcl" as cylinder
import "bracket.kcl" as bracket
cylinder
bracket
import "cube.step" as cube
import "${complexPlmFileName}" as cubeSw
cube
cubeSw
`,
{ shouldNormalise: true }
)
await scene.settled(cmdBar)

// TODO: remove this once #5780 is fixed
await page.reload()
await scene.settled(cmdBar)

await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await toolbar.closePane('code')
await scene.expectPixelColor(partColor, partPoint, tolerance)
})
}
)
Expand Down
4 changes: 4 additions & 0 deletions e2e/playwright/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1021,6 +1021,10 @@ export function executorInputPath(fileName: string): string {
return path.join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName)
}

export function testsInputPath(fileName: string): string {
return path.join('rust', 'kcl-lib', 'tests', 'inputs', fileName)
}

export async function doAndWaitForImageDiff(
page: Page,
fn: () => Promise<unknown>,
Expand Down
27 changes: 21 additions & 6 deletions src/components/CommandBar/CommandBarBasicInput.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import { useEffect, useRef } from 'react'
import { useSelector } from '@xstate/react'
import { useEffect, useMemo, useRef } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'

import type { CommandArgument } from '@src/lib/commandTypes'
import {
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
import type { AnyStateMachine, SnapshotFrom } from 'xstate'

// TODO: remove the need for this selector once we decouple all actors from React
const machineContextSelector = (snapshot?: SnapshotFrom<AnyStateMachine>) =>
snapshot?.context

function CommandBarBasicInput({
arg,
Expand All @@ -22,6 +28,19 @@ function CommandBarBasicInput({
const commandBarState = useCommandBarState()
useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
const inputRef = useRef<HTMLInputElement>(null)
const argMachineContext = useSelector(
arg.machineActor,
machineContextSelector
)
const defaultValue = useMemo(
() =>
arg.defaultValue
? arg.defaultValue instanceof Function
? arg.defaultValue(commandBarState.context, argMachineContext)
: arg.defaultValue
: '',
[arg.defaultValue, commandBarState.context, argMachineContext]
)

useEffect(() => {
if (inputRef.current) {
Expand Down Expand Up @@ -53,11 +72,7 @@ function CommandBarBasicInput({
required
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
placeholder="Enter a value"
defaultValue={
(commandBarState.context.argumentsToSubmit[arg.name] as
| string
| undefined) || (arg.defaultValue as string)
}
defaultValue={defaultValue}
onKeyDown={(event) => {
if (event.key === 'Backspace' && event.shiftKey) {
stepBack()
Expand Down
Loading
Loading