Fix Shift-Click to Deselect Edges/Faces (#5289)
* fix selections and support deselections * test deselections * rever commit * undo sloppy merge * re-commit the test * make ubuntu happy * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * support initial sketch * test sketch --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -712,6 +712,330 @@ openSketch = startSketchOn('XY')
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(`Shift-click to select and deselect edges and faces`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
}) => {
|
||||||
|
// Code samples
|
||||||
|
const initialCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-12, -6], %)
|
||||||
|
|> line(end = [0, 12])
|
||||||
|
|> line(end = [24, 0])
|
||||||
|
|> line(end = [0, -12])
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
|> extrude(%, length = -12)`
|
||||||
|
|
||||||
|
// Locators
|
||||||
|
const upperEdgeLocation = { x: 600, y: 192 }
|
||||||
|
const lowerEdgeLocation = { x: 600, y: 383 }
|
||||||
|
const faceLocation = { x: 630, y: 290 }
|
||||||
|
|
||||||
|
// Click helpers
|
||||||
|
const [clickOnUpperEdge] = scene.makeMouseHelpers(
|
||||||
|
upperEdgeLocation.x,
|
||||||
|
upperEdgeLocation.y
|
||||||
|
)
|
||||||
|
const [clickOnLowerEdge] = scene.makeMouseHelpers(
|
||||||
|
lowerEdgeLocation.x,
|
||||||
|
lowerEdgeLocation.y
|
||||||
|
)
|
||||||
|
const [clickOnFace] = scene.makeMouseHelpers(faceLocation.x, faceLocation.y)
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const edgeColorWhite: [number, number, number] = [220, 220, 220] // varies from 192 to 255
|
||||||
|
const edgeColorYellow: [number, number, number] = [251, 251, 40] // vaies from 12 to 67
|
||||||
|
const faceColorGray: [number, number, number] = [168, 168, 168]
|
||||||
|
const faceColorYellow: [number, number, number] = [155, 155, 155]
|
||||||
|
const tolerance = 40
|
||||||
|
const timeout = 150
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
await test.step(`Initial test setup`, async () => {
|
||||||
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// Wait for the scene and stream to load
|
||||||
|
await scene.expectPixelColor(faceColorGray, faceLocation, tolerance)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Select and deselect a single edge', async () => {
|
||||||
|
await test.step('Click the edge', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await clickOnUpperEdge()
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Shift-click the same edge to deselect', async () => {
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnUpperEdge()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Select and deselect multiple objects', async () => {
|
||||||
|
await test.step('Select both edges and the face', async () => {
|
||||||
|
await test.step('Select the upper edge', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await clickOnUpperEdge()
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Select the lower edge (Shift-click)', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
lowerEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnLowerEdge()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
lowerEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Select the face (Shift-click)', async () => {
|
||||||
|
await scene.expectPixelColor(faceColorGray, faceLocation, tolerance)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnFace()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(faceColorYellow, faceLocation, tolerance)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
await test.step('Deselect them one by one', async () => {
|
||||||
|
await test.step('Deselect the face (Shift-click)', async () => {
|
||||||
|
await scene.expectPixelColor(faceColorYellow, faceLocation, tolerance)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnFace()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(faceColorGray, faceLocation, tolerance)
|
||||||
|
})
|
||||||
|
await test.step('Deselect the lower edge (Shift-click)', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
lowerEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnLowerEdge()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
lowerEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Deselect the upper edge (Shift-click)', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorYellow,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickOnUpperEdge()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
upperEdgeLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`Shift-click to select and deselect sketch segments`, async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
}) => {
|
||||||
|
// Locators
|
||||||
|
const firstPointLocation = { x: 200, y: 100 }
|
||||||
|
const secondPointLocation = { x: 800, y: 100 }
|
||||||
|
const thirdPointLocation = { x: 800, y: 400 }
|
||||||
|
const fristSegmentLocation = { x: 750, y: 100 }
|
||||||
|
const secondSegmentLocation = { x: 800, y: 150 }
|
||||||
|
const planeLocation = { x: 700, y: 200 }
|
||||||
|
|
||||||
|
// Click helpers
|
||||||
|
const [clickFirstPoint] = scene.makeMouseHelpers(
|
||||||
|
firstPointLocation.x,
|
||||||
|
firstPointLocation.y
|
||||||
|
)
|
||||||
|
const [clickSecondPoint] = scene.makeMouseHelpers(
|
||||||
|
secondPointLocation.x,
|
||||||
|
secondPointLocation.y
|
||||||
|
)
|
||||||
|
const [clickThirdPoint] = scene.makeMouseHelpers(
|
||||||
|
thirdPointLocation.x,
|
||||||
|
thirdPointLocation.y
|
||||||
|
)
|
||||||
|
const [clickFirstSegment] = scene.makeMouseHelpers(
|
||||||
|
fristSegmentLocation.x,
|
||||||
|
fristSegmentLocation.y
|
||||||
|
)
|
||||||
|
const [clickSecondSegment] = scene.makeMouseHelpers(
|
||||||
|
secondSegmentLocation.x,
|
||||||
|
secondSegmentLocation.y
|
||||||
|
)
|
||||||
|
const [clickPlane] = scene.makeMouseHelpers(
|
||||||
|
planeLocation.x,
|
||||||
|
planeLocation.y
|
||||||
|
)
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const edgeColorWhite: [number, number, number] = [220, 220, 220]
|
||||||
|
const edgeColorBlue: [number, number, number] = [20, 20, 200]
|
||||||
|
const backgroundColor: [number, number, number] = [30, 30, 30]
|
||||||
|
const tolerance = 40
|
||||||
|
const timeout = 150
|
||||||
|
|
||||||
|
// Setup
|
||||||
|
await test.step(`Initial test setup`, async () => {
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
|
// Wait for the scene and stream to load
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
backgroundColor,
|
||||||
|
secondPointLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Select and deselect a single sketch segment', async () => {
|
||||||
|
await test.step('Get into sketch mode', async () => {
|
||||||
|
await editor.closePane()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickPlane()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
})
|
||||||
|
await test.step('Draw sketch', async () => {
|
||||||
|
await clickFirstPoint()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickSecondPoint()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickThirdPoint()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
})
|
||||||
|
await test.step('Deselect line tool', async () => {
|
||||||
|
const btnLine = page.getByTestId('line')
|
||||||
|
const btnLineAriaPressed = await btnLine.getAttribute('aria-pressed')
|
||||||
|
if (btnLineAriaPressed === 'true') {
|
||||||
|
await btnLine.click()
|
||||||
|
}
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
})
|
||||||
|
await test.step('Select the first segment', async () => {
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickFirstSegment()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorBlue,
|
||||||
|
fristSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
secondSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Select the second segment (Shift-click)', async () => {
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickSecondSegment()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorBlue,
|
||||||
|
fristSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorBlue,
|
||||||
|
secondSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Deselect the first segment', async () => {
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickFirstSegment()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
fristSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorBlue,
|
||||||
|
secondSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Deselect the second segment', async () => {
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await clickSecondSegment()
|
||||||
|
await page.waitForTimeout(timeout)
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
fristSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
secondSegmentLocation,
|
||||||
|
tolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test(`Offset plane point-and-click`, async ({
|
test(`Offset plane point-and-click`, async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Binary file not shown.
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
@ -329,11 +329,83 @@ export const ModelingMachineProvider = ({
|
|||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
}
|
}
|
||||||
} else if (setSelections.selection && editorManager.isShiftDown) {
|
} else if (setSelections.selection && editorManager.isShiftDown) {
|
||||||
selections = {
|
// selecting and deselecting multiple objects
|
||||||
graphSelections: [
|
|
||||||
|
/**
|
||||||
|
* There are two scenarios:
|
||||||
|
* 1. General case:
|
||||||
|
* When selecting and deselecting edges,
|
||||||
|
* faces or segment (during sketch edit)
|
||||||
|
* we use its artifact ID to identify the selection
|
||||||
|
* 2. Initial sketch setup:
|
||||||
|
* The artifact is not yet created
|
||||||
|
* so we use the codeRef.range
|
||||||
|
*/
|
||||||
|
|
||||||
|
let updatedSelections: typeof selectionRanges.graphSelections
|
||||||
|
|
||||||
|
// 1. General case: Artifact exists, use its ID
|
||||||
|
if (setSelections.selection.artifact?.id) {
|
||||||
|
// check if already selected
|
||||||
|
const alreadySelected = selectionRanges.graphSelections.some(
|
||||||
|
(selection) =>
|
||||||
|
selection.artifact?.id ===
|
||||||
|
setSelections.selection?.artifact?.id
|
||||||
|
)
|
||||||
|
if (
|
||||||
|
alreadySelected &&
|
||||||
|
setSelections.selection?.artifact?.id
|
||||||
|
) {
|
||||||
|
// remove it
|
||||||
|
updatedSelections = selectionRanges.graphSelections.filter(
|
||||||
|
(selection) =>
|
||||||
|
selection.artifact?.id !==
|
||||||
|
setSelections.selection?.artifact?.id
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// add it
|
||||||
|
updatedSelections = [
|
||||||
...selectionRanges.graphSelections,
|
...selectionRanges.graphSelections,
|
||||||
setSelections.selection,
|
setSelections.selection,
|
||||||
],
|
]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 2. Initial sketch setup: Artifact not yet created – use codeRef.range
|
||||||
|
const selectionRange = JSON.stringify(
|
||||||
|
setSelections.selection?.codeRef?.range
|
||||||
|
)
|
||||||
|
|
||||||
|
// check if already selected
|
||||||
|
const alreadySelected = selectionRanges.graphSelections.some(
|
||||||
|
(selection) => {
|
||||||
|
const existingRange = JSON.stringify(
|
||||||
|
selection.codeRef?.range
|
||||||
|
)
|
||||||
|
return existingRange === selectionRange
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
alreadySelected &&
|
||||||
|
setSelections.selection?.codeRef?.range
|
||||||
|
) {
|
||||||
|
// remove it
|
||||||
|
updatedSelections = selectionRanges.graphSelections.filter(
|
||||||
|
(selection) =>
|
||||||
|
JSON.stringify(selection.codeRef?.range) !==
|
||||||
|
selectionRange
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// add it
|
||||||
|
updatedSelections = [
|
||||||
|
...selectionRanges.graphSelections,
|
||||||
|
setSelections.selection,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selections = {
|
||||||
|
graphSelections: updatedSelections,
|
||||||
otherSelections: selectionRanges.otherSelections,
|
otherSelections: selectionRanges.otherSelections,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user