Remove guards from modeling commands in the toolbar (#4800)
* Remove guards from modeling commands in the toolbar * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) * Remove the deprecated function, update doc comment for the one still in use * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores) * Remove more selection check functions that are no longer used * Update E2E tests that assumed the extrude button could be disabled due to selection * Update a few fillet tests that expected the button to disable based on selection * 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: windows-16-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-macos-8-cores) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-16-cores) * Trigger CI --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
@ -82,19 +82,16 @@ test.describe('Sketch tests', () => {
|
|||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await page.getByText(selectionsSnippets.startProfileAt1).click()
|
await page.getByText(selectionsSnippets.startProfileAt1).click()
|
||||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
await page.getByText(selectionsSnippets.startProfileAt2).click()
|
await page.getByText(selectionsSnippets.startProfileAt2).click()
|
||||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
await page.getByText(selectionsSnippets.startProfileAt3).click()
|
await page.getByText(selectionsSnippets.startProfileAt3).click()
|
||||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
@ -874,17 +874,15 @@ test.describe('Testing selections', () => {
|
|||||||
}
|
}
|
||||||
const clickEmpty = () => page.mouse.click(700, 460)
|
const clickEmpty = () => page.mouse.click(700, 460)
|
||||||
await selectUnExtrudable()
|
await selectUnExtrudable()
|
||||||
// expect extrude button to be disabled
|
// expect extrude button to be enabled, since we don't guard
|
||||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
// until the extrude button is clicked
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeEnabled()
|
||||||
|
|
||||||
await clickEmpty()
|
await clickEmpty()
|
||||||
|
|
||||||
// expect active line to contain nothing
|
// expect active line to contain nothing
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText('')
|
await expect(page.locator('.cm-activeLine')).toHaveText('')
|
||||||
|
|
||||||
// and extrude to still be disabled
|
|
||||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
|
||||||
|
|
||||||
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||||
sketch002 = startSketchOn(extrude001, $seg01)
|
sketch002 = startSketchOn(extrude001, $seg01)
|
||||||
|> startProfileAt([-12.94, 6.6], %)
|
|> startProfileAt([-12.94, 6.6], %)
|
||||||
@ -896,8 +894,9 @@ test.describe('Testing selections', () => {
|
|||||||
await u.codeLocator.fill(codeToAdd)
|
await u.codeLocator.fill(codeToAdd)
|
||||||
|
|
||||||
await selectUnExtrudable()
|
await selectUnExtrudable()
|
||||||
// expect extrude button to be disabled
|
// expect extrude button to be enabled, since we don't guard
|
||||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
// until the extrude button is clicked
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeEnabled()
|
||||||
|
|
||||||
await clickEmpty()
|
await clickEmpty()
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText('')
|
await expect(page.locator('.cm-activeLine')).toHaveText('')
|
||||||
@ -932,11 +931,14 @@ test.describe('Testing selections', () => {
|
|||||||
const selectClose = () => page.getByText(`close(%)`).click()
|
const selectClose = () => page.getByText(`close(%)`).click()
|
||||||
const clickEmpty = () => page.mouse.click(950, 100)
|
const clickEmpty = () => page.mouse.click(950, 100)
|
||||||
|
|
||||||
// expect fillet button without any bodies in the scene
|
// Now that we don't disable toolbar buttons based on selection,
|
||||||
|
// but rather based on a "selection" step in the command palette,
|
||||||
|
// the fillet button should always be enabled with a good network connection.
|
||||||
|
// I'm not sure if this test is actually useful anymore.
|
||||||
await selectSegment()
|
await selectSegment()
|
||||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||||
await clickEmpty()
|
await clickEmpty()
|
||||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||||
|
|
||||||
// test fillet button with the body in the scene
|
// test fillet button with the body in the scene
|
||||||
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||||
@ -946,7 +948,7 @@ test.describe('Testing selections', () => {
|
|||||||
await selectSegment()
|
await selectSegment()
|
||||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||||
await selectClose()
|
await selectClose()
|
||||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||||
await clickEmpty()
|
await clickEmpty()
|
||||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||||
})
|
})
|
||||||
@ -1201,7 +1203,9 @@ test.describe('Testing selections', () => {
|
|||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
|
|
||||||
await page.getByText(selectionsSnippets.extrudeAndEditBlocked).click()
|
await page.getByText(selectionsSnippets.extrudeAndEditBlocked).click()
|
||||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
// expect extrude button to be enabled, since we don't guard
|
||||||
|
// until the extrude button is clicked
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeEnabled()
|
||||||
|
|
||||||
await page.getByText(selectionsSnippets.extrudeAndEditAllowed).click()
|
await page.getByText(selectionsSnippets.extrudeAndEditAllowed).click()
|
||||||
await expect(
|
await expect(
|
||||||
@ -1212,7 +1216,9 @@ test.describe('Testing selections', () => {
|
|||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
|
|
||||||
await page.getByText(selectionsSnippets.editOnly).click()
|
await page.getByText(selectionsSnippets.editOnly).click()
|
||||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
// expect extrude button to be enabled, since we don't guard
|
||||||
|
// until the extrude button is clicked
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeEnabled()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
@ -1220,7 +1226,9 @@ test.describe('Testing selections', () => {
|
|||||||
await page
|
await page
|
||||||
.getByText(selectionsSnippets.extrudeAndEditBlockedInFunction)
|
.getByText(selectionsSnippets.extrudeAndEditBlockedInFunction)
|
||||||
.click()
|
.click()
|
||||||
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
|
// expect extrude button to be enabled, since we don't guard
|
||||||
|
// until the extrude button is clicked
|
||||||
|
await expect(page.getByRole('button', { name: 'Extrude' })).toBeEnabled()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
).not.toBeVisible()
|
).not.toBeVisible()
|
||||||
|
@ -46,16 +46,9 @@ import {
|
|||||||
applyConstraintLength,
|
applyConstraintLength,
|
||||||
} from './Toolbar/setAngleLength'
|
} from './Toolbar/setAngleLength'
|
||||||
import {
|
import {
|
||||||
canSweepSelection,
|
|
||||||
handleSelectionBatch,
|
handleSelectionBatch,
|
||||||
isSelectionLastLine,
|
|
||||||
isRangeBetweenCharacters,
|
|
||||||
isSketchPipe,
|
|
||||||
Selections,
|
Selections,
|
||||||
updateSelections,
|
updateSelections,
|
||||||
canLoftSelection,
|
|
||||||
canRevolveSelection,
|
|
||||||
canShellSelection,
|
|
||||||
} from 'lib/selections'
|
} from 'lib/selections'
|
||||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||||
@ -74,12 +67,7 @@ import {
|
|||||||
startSketchOnDefault,
|
startSketchOnDefault,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
|
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
|
||||||
import {
|
import { getNodePathFromSourceRange, isSingleCursorInPipe } from 'lang/queryAst'
|
||||||
doesSceneHaveExtrudedSketch,
|
|
||||||
doesSceneHaveSweepableSketch,
|
|
||||||
getNodePathFromSourceRange,
|
|
||||||
isSingleCursorInPipe,
|
|
||||||
} from 'lang/queryAst'
|
|
||||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
import { Models } from '@kittycad/lib/dist/types/src'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
@ -89,7 +77,6 @@ import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
|||||||
import { err, reportRejection, trap } from 'lib/trap'
|
import { err, reportRejection, trap } from 'lib/trap'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { modelingMachineEvent } from 'editor/manager'
|
import { modelingMachineEvent } from 'editor/manager'
|
||||||
import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addEdgeTreatment'
|
|
||||||
import {
|
import {
|
||||||
ExportIntent,
|
ExportIntent,
|
||||||
EngineConnectionStateType,
|
EngineConnectionStateType,
|
||||||
@ -556,78 +543,6 @@ export const ModelingMachineProvider = ({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
guards: {
|
guards: {
|
||||||
'has valid sweep selection': ({ context: { selectionRanges } }) => {
|
|
||||||
// A user can begin extruding if they either have 1+ faces selected or nothing selected
|
|
||||||
// TODO: I believe this guard only allows for extruding a single face at a time
|
|
||||||
const hasNoSelection =
|
|
||||||
selectionRanges.graphSelections.length === 0 ||
|
|
||||||
isRangeBetweenCharacters(selectionRanges) ||
|
|
||||||
isSelectionLastLine(selectionRanges, codeManager.code)
|
|
||||||
|
|
||||||
if (hasNoSelection) {
|
|
||||||
// they have no selection, we should enable the button
|
|
||||||
// so they can select the face through the cmdbar
|
|
||||||
// BUT only if there's extrudable geometry
|
|
||||||
return doesSceneHaveSweepableSketch(kclManager.ast)
|
|
||||||
}
|
|
||||||
if (!isSketchPipe(selectionRanges)) return false
|
|
||||||
|
|
||||||
const canSweep = canSweepSelection(selectionRanges)
|
|
||||||
if (err(canSweep)) return false
|
|
||||||
return canSweep
|
|
||||||
},
|
|
||||||
'has valid revolve selection': ({ context: { selectionRanges } }) => {
|
|
||||||
// A user can begin extruding if they either have 1+ faces selected or nothing selected
|
|
||||||
// TODO: I believe this guard only allows for extruding a single face at a time
|
|
||||||
const hasNoSelection =
|
|
||||||
selectionRanges.graphSelections.length === 0 ||
|
|
||||||
isRangeBetweenCharacters(selectionRanges) ||
|
|
||||||
isSelectionLastLine(selectionRanges, codeManager.code)
|
|
||||||
|
|
||||||
if (hasNoSelection) {
|
|
||||||
// they have no selection, we should enable the button
|
|
||||||
// so they can select the face through the cmdbar
|
|
||||||
// BUT only if there's extrudable geometry
|
|
||||||
return doesSceneHaveSweepableSketch(kclManager.ast)
|
|
||||||
}
|
|
||||||
if (!isSketchPipe(selectionRanges)) return false
|
|
||||||
|
|
||||||
const canSweep = canRevolveSelection(selectionRanges)
|
|
||||||
if (err(canSweep)) return false
|
|
||||||
return canSweep
|
|
||||||
},
|
|
||||||
'has valid loft selection': ({ context: { selectionRanges } }) => {
|
|
||||||
const hasNoSelection =
|
|
||||||
selectionRanges.graphSelections.length === 0 ||
|
|
||||||
isRangeBetweenCharacters(selectionRanges) ||
|
|
||||||
isSelectionLastLine(selectionRanges, codeManager.code)
|
|
||||||
|
|
||||||
if (hasNoSelection) {
|
|
||||||
const count = 2
|
|
||||||
return doesSceneHaveSweepableSketch(kclManager.ast, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
const canLoft = canLoftSelection(selectionRanges)
|
|
||||||
if (err(canLoft)) return false
|
|
||||||
return canLoft
|
|
||||||
},
|
|
||||||
'has valid shell selection': ({
|
|
||||||
context: { selectionRanges },
|
|
||||||
event,
|
|
||||||
}) => {
|
|
||||||
const hasNoSelection =
|
|
||||||
selectionRanges.graphSelections.length === 0 ||
|
|
||||||
isRangeBetweenCharacters(selectionRanges) ||
|
|
||||||
isSelectionLastLine(selectionRanges, codeManager.code)
|
|
||||||
|
|
||||||
if (hasNoSelection) {
|
|
||||||
return doesSceneHaveExtrudedSketch(kclManager.ast)
|
|
||||||
}
|
|
||||||
|
|
||||||
const canShell = canShellSelection(selectionRanges)
|
|
||||||
if (err(canShell)) return false
|
|
||||||
return canShell
|
|
||||||
},
|
|
||||||
'has valid selection for deletion': ({
|
'has valid selection for deletion': ({
|
||||||
context: { selectionRanges },
|
context: { selectionRanges },
|
||||||
}) => {
|
}) => {
|
||||||
@ -635,15 +550,6 @@ export const ModelingMachineProvider = ({
|
|||||||
if (selectionRanges.graphSelections.length <= 0) return false
|
if (selectionRanges.graphSelections.length <= 0) return false
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
'has valid edge treatment selection': ({
|
|
||||||
context: { selectionRanges },
|
|
||||||
}) => {
|
|
||||||
return hasValidEdgeTreatmentSelection({
|
|
||||||
selectionRanges,
|
|
||||||
ast: kclManager.ast,
|
|
||||||
code: codeManager.code,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'Selection is on face': ({ context: { selectionRanges }, event }) => {
|
'Selection is on face': ({ context: { selectionRanges }, event }) => {
|
||||||
if (event.type !== 'Enter sketch') return false
|
if (event.type !== 'Enter sketch') return false
|
||||||
if (event.data?.forceNewSketch) return false
|
if (event.data?.forceNewSketch) return false
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
isTypeInValue,
|
isTypeInValue,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
doesPipeHaveCallExp,
|
|
||||||
hasExtrudeSketch,
|
hasExtrudeSketch,
|
||||||
findUsesOfTagInPipe,
|
findUsesOfTagInPipe,
|
||||||
hasSketchPipeBeenExtruded,
|
hasSketchPipeBeenExtruded,
|
||||||
@ -362,82 +361,6 @@ describe('testing getNodePathFromSourceRange', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('testing doesPipeHave', () => {
|
|
||||||
it('finds close', () => {
|
|
||||||
const exampleCode = `length001 = 2
|
|
||||||
part001 = startSketchAt([-1.41, 3.46])
|
|
||||||
|> line([19.49, 1.16], %, $seg01)
|
|
||||||
|> angledLine([-35, length001], %)
|
|
||||||
|> line([-3.22, -7.36], %)
|
|
||||||
|> angledLine([-175, segLen(seg01)], %)
|
|
||||||
|> close(%)
|
|
||||||
`
|
|
||||||
const ast = assertParse(exampleCode)
|
|
||||||
|
|
||||||
const result = doesPipeHaveCallExp({
|
|
||||||
calleeName: 'close',
|
|
||||||
ast,
|
|
||||||
selection: {
|
|
||||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(result).toEqual(true)
|
|
||||||
})
|
|
||||||
it('finds extrude', () => {
|
|
||||||
const exampleCode = `length001 = 2
|
|
||||||
part001 = startSketchAt([-1.41, 3.46])
|
|
||||||
|> line([19.49, 1.16], %, $seg01)
|
|
||||||
|> angledLine([-35, length001], %)
|
|
||||||
|> line([-3.22, -7.36], %)
|
|
||||||
|> angledLine([-175, segLen(seg01)], %)
|
|
||||||
|> close(%)
|
|
||||||
|> extrude(1, %)
|
|
||||||
`
|
|
||||||
const ast = assertParse(exampleCode)
|
|
||||||
|
|
||||||
const result = doesPipeHaveCallExp({
|
|
||||||
calleeName: 'extrude',
|
|
||||||
ast,
|
|
||||||
selection: {
|
|
||||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(result).toEqual(true)
|
|
||||||
})
|
|
||||||
it('does NOT find close', () => {
|
|
||||||
const exampleCode = `length001 = 2
|
|
||||||
part001 = startSketchAt([-1.41, 3.46])
|
|
||||||
|> line([19.49, 1.16], %, $seg01)
|
|
||||||
|> angledLine([-35, length001], %)
|
|
||||||
|> line([-3.22, -7.36], %)
|
|
||||||
|> angledLine([-175, segLen(seg01)], %)
|
|
||||||
`
|
|
||||||
const ast = assertParse(exampleCode)
|
|
||||||
|
|
||||||
const result = doesPipeHaveCallExp({
|
|
||||||
calleeName: 'close',
|
|
||||||
ast,
|
|
||||||
selection: {
|
|
||||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(result).toEqual(false)
|
|
||||||
})
|
|
||||||
it('returns false if not a pipe', () => {
|
|
||||||
const exampleCode = `length001 = 2`
|
|
||||||
const ast = assertParse(exampleCode)
|
|
||||||
|
|
||||||
const result = doesPipeHaveCallExp({
|
|
||||||
calleeName: 'close',
|
|
||||||
ast,
|
|
||||||
selection: {
|
|
||||||
codeRef: codeRefFromRange([9, 10, true], ast),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
expect(result).toEqual(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('testing hasExtrudeSketch', () => {
|
describe('testing hasExtrudeSketch', () => {
|
||||||
it('find sketch', async () => {
|
it('find sketch', async () => {
|
||||||
const exampleCode = `length001 = 2
|
const exampleCode = `length001 = 2
|
||||||
|
@ -831,33 +831,6 @@ export function isLinesParallelAndConstrained(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function doesPipeHaveCallExp({
|
|
||||||
ast,
|
|
||||||
selection,
|
|
||||||
calleeName,
|
|
||||||
}: {
|
|
||||||
calleeName: string
|
|
||||||
ast: Program
|
|
||||||
selection: Selection
|
|
||||||
}): boolean {
|
|
||||||
const pipeExpressionMeta = getNodeFromPath<PipeExpression>(
|
|
||||||
ast,
|
|
||||||
selection?.codeRef?.pathToNode,
|
|
||||||
'PipeExpression'
|
|
||||||
)
|
|
||||||
if (err(pipeExpressionMeta)) {
|
|
||||||
console.error(pipeExpressionMeta)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const pipeExpression = pipeExpressionMeta.node
|
|
||||||
if (pipeExpression.type !== 'PipeExpression') return false
|
|
||||||
return pipeExpression.body.some(
|
|
||||||
(expression) =>
|
|
||||||
expression.type === 'CallExpression' &&
|
|
||||||
expression.callee.name === calleeName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hasExtrudeSketch({
|
export function hasExtrudeSketch({
|
||||||
ast,
|
ast,
|
||||||
selection,
|
selection,
|
||||||
|
@ -18,10 +18,8 @@ import { getNormalisedCoordinates, isOverlap } from 'lib/utils'
|
|||||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
import { Program } from 'lang/wasm'
|
import { Program } from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
doesPipeHaveCallExp,
|
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
hasSketchPipeBeenExtruded,
|
|
||||||
isSingleCursorInPipe,
|
isSingleCursorInPipe,
|
||||||
} from 'lang/queryAst'
|
} from 'lang/queryAst'
|
||||||
import { CommandArgument } from './commandTypes'
|
import { CommandArgument } from './commandTypes'
|
||||||
@ -490,6 +488,9 @@ function resetAndSetEngineEntitySelectionCmds(
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the selection a single cursor in a sketch pipe expression chain?
|
||||||
|
*/
|
||||||
export function isSketchPipe(selectionRanges: Selections) {
|
export function isSketchPipe(selectionRanges: Selections) {
|
||||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast)) return false
|
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast)) return false
|
||||||
return isCursorInSketchCommandRange(
|
return isCursorInSketchCommandRange(
|
||||||
@ -498,115 +499,6 @@ export function isSketchPipe(selectionRanges: Selections) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSelectionLastLine(
|
|
||||||
selectionRanges: Selections,
|
|
||||||
code: string,
|
|
||||||
i = 0
|
|
||||||
) {
|
|
||||||
return selectionRanges.graphSelections[i]?.codeRef?.range[1] === code.length
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isRangeBetweenCharacters(selectionRanges: Selections) {
|
|
||||||
return (
|
|
||||||
selectionRanges.graphSelections.length === 1 &&
|
|
||||||
selectionRanges.graphSelections[0]?.codeRef?.range[0] === 0 &&
|
|
||||||
selectionRanges.graphSelections[0]?.codeRef?.range[1] === 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CommonASTNode = {
|
|
||||||
selection: Selection
|
|
||||||
ast: Program
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildCommonNodeFromSelection(selectionRanges: Selections, i: number) {
|
|
||||||
return {
|
|
||||||
selection: selectionRanges.graphSelections[i],
|
|
||||||
ast: kclManager.ast,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function nodeHasExtrude(node: CommonASTNode) {
|
|
||||||
return (
|
|
||||||
doesPipeHaveCallExp({
|
|
||||||
calleeName: 'extrude',
|
|
||||||
...node,
|
|
||||||
}) ||
|
|
||||||
doesPipeHaveCallExp({
|
|
||||||
calleeName: 'revolve',
|
|
||||||
...node,
|
|
||||||
}) ||
|
|
||||||
doesPipeHaveCallExp({
|
|
||||||
calleeName: 'loft',
|
|
||||||
...node,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function nodeHasClose(node: CommonASTNode) {
|
|
||||||
return doesPipeHaveCallExp({
|
|
||||||
calleeName: 'close',
|
|
||||||
...node,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
function nodeHasCircle(node: CommonASTNode) {
|
|
||||||
return doesPipeHaveCallExp({
|
|
||||||
calleeName: 'circle',
|
|
||||||
...node,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function canSweepSelection(selection: Selections) {
|
|
||||||
const commonNodes = selection.graphSelections.map((_, i) =>
|
|
||||||
buildCommonNodeFromSelection(selection, i)
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
!!isSketchPipe(selection) &&
|
|
||||||
commonNodes.every((n) => !hasSketchPipeBeenExtruded(n.selection, n.ast)) &&
|
|
||||||
(commonNodes.every((n) => nodeHasClose(n)) ||
|
|
||||||
commonNodes.every((n) => nodeHasCircle(n))) &&
|
|
||||||
commonNodes.every((n) => !nodeHasExtrude(n))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function canRevolveSelection(selection: Selections) {
|
|
||||||
const commonNodes = selection.graphSelections.map((_, i) =>
|
|
||||||
buildCommonNodeFromSelection(selection, i)
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
!!isSketchPipe(selection) &&
|
|
||||||
(commonNodes.every((n) => nodeHasClose(n)) ||
|
|
||||||
commonNodes.every((n) => nodeHasCircle(n)))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function canLoftSelection(selection: Selections) {
|
|
||||||
const commonNodes = selection.graphSelections.map((_, i) =>
|
|
||||||
buildCommonNodeFromSelection(selection, i)
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
!!isCursorInSketchCommandRange(
|
|
||||||
engineCommandManager.artifactGraph,
|
|
||||||
selection
|
|
||||||
) &&
|
|
||||||
commonNodes.length > 1 &&
|
|
||||||
commonNodes.every((n) => !hasSketchPipeBeenExtruded(n.selection, n.ast)) &&
|
|
||||||
commonNodes.every((n) => nodeHasClose(n) || nodeHasCircle(n)) &&
|
|
||||||
commonNodes.every((n) => !nodeHasExtrude(n))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function canShellSelection(selection: Selections) {
|
|
||||||
const commonNodes = selection.graphSelections.map((_, i) =>
|
|
||||||
buildCommonNodeFromSelection(selection, i)
|
|
||||||
)
|
|
||||||
return commonNodes.every(
|
|
||||||
(n) =>
|
|
||||||
n.selection.artifact?.type === 'cap' ||
|
|
||||||
n.selection.artifact?.type === 'wall'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// This accounts for non-geometry selections under "other"
|
// This accounts for non-geometry selections under "other"
|
||||||
export type ResolvedSelectionType = Artifact['type'] | 'other'
|
export type ResolvedSelectionType = Artifact['type'] | 'other'
|
||||||
export type SelectionCountsByType = Map<ResolvedSelectionType, number>
|
export type SelectionCountsByType = Map<ResolvedSelectionType, number>
|
||||||
|
@ -71,7 +71,6 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
: modelingSend({ type: 'Enter sketch' }),
|
: modelingSend({ type: 'Enter sketch' }),
|
||||||
icon: 'sketch',
|
icon: 'sketch',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
disabled: (state) => !state.matches('idle'),
|
|
||||||
title: ({ sketchPathId }) =>
|
title: ({ sketchPathId }) =>
|
||||||
`${sketchPathId ? 'Edit' : 'Start'} Sketch`,
|
`${sketchPathId ? 'Edit' : 'Start'} Sketch`,
|
||||||
showTitle: true,
|
showTitle: true,
|
||||||
@ -89,7 +88,6 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: { name: 'Extrude', groupId: 'modeling' },
|
data: { name: 'Extrude', groupId: 'modeling' },
|
||||||
}),
|
}),
|
||||||
disabled: (state) => !state.can({ type: 'Extrude' }),
|
|
||||||
icon: 'extrude',
|
icon: 'extrude',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
title: 'Extrude',
|
title: 'Extrude',
|
||||||
@ -104,9 +102,6 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: { name: 'Revolve', groupId: 'modeling' },
|
data: { name: 'Revolve', groupId: 'modeling' },
|
||||||
}),
|
}),
|
||||||
// TODO: disabled
|
|
||||||
// Who's state is this?
|
|
||||||
disabled: (state) => !state.can({ type: 'Revolve' }),
|
|
||||||
icon: 'revolve',
|
icon: 'revolve',
|
||||||
status: DEV ? 'available' : 'kcl-only',
|
status: DEV ? 'available' : 'kcl-only',
|
||||||
title: 'Revolve',
|
title: 'Revolve',
|
||||||
@ -144,7 +139,6 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: { name: 'Loft', groupId: 'modeling' },
|
data: { name: 'Loft', groupId: 'modeling' },
|
||||||
}),
|
}),
|
||||||
disabled: (state) => !state.can({ type: 'Loft' }),
|
|
||||||
icon: 'loft',
|
icon: 'loft',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
title: 'Loft',
|
title: 'Loft',
|
||||||
@ -172,7 +166,6 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
}),
|
}),
|
||||||
icon: 'fillet3d',
|
icon: 'fillet3d',
|
||||||
status: DEV ? 'available' : 'kcl-only',
|
status: DEV ? 'available' : 'kcl-only',
|
||||||
disabled: (state) => !state.can({ type: 'Fillet' }),
|
|
||||||
title: 'Fillet',
|
title: 'Fillet',
|
||||||
hotkey: 'F',
|
hotkey: 'F',
|
||||||
description: 'Round the edges of a 3D solid.',
|
description: 'Round the edges of a 3D solid.',
|
||||||
@ -196,7 +189,6 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
data: { name: 'Shell', groupId: 'modeling' },
|
data: { name: 'Shell', groupId: 'modeling' },
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
disabled: (state) => !state.can({ type: 'Shell' }),
|
|
||||||
icon: 'shell',
|
icon: 'shell',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
title: 'Shell',
|
title: 'Shell',
|
||||||
|
@ -393,11 +393,6 @@ export const modelingMachine = setup({
|
|||||||
},
|
},
|
||||||
guards: {
|
guards: {
|
||||||
'Selection is on face': () => false,
|
'Selection is on face': () => false,
|
||||||
'has valid sweep selection': () => false,
|
|
||||||
'has valid revolve selection': () => false,
|
|
||||||
'has valid loft selection': () => false,
|
|
||||||
'has valid shell selection': () => false,
|
|
||||||
'has valid edge treatment selection': () => false,
|
|
||||||
'Has exportable geometry': () => false,
|
'Has exportable geometry': () => false,
|
||||||
'has valid selection for deletion': () => false,
|
'has valid selection for deletion': () => false,
|
||||||
'has made first point': ({ context }) => {
|
'has made first point': ({ context }) => {
|
||||||
@ -1687,33 +1682,28 @@ export const modelingMachine = setup({
|
|||||||
|
|
||||||
Extrude: {
|
Extrude: {
|
||||||
target: 'idle',
|
target: 'idle',
|
||||||
guard: 'has valid sweep selection',
|
|
||||||
actions: ['AST extrude'],
|
actions: ['AST extrude'],
|
||||||
reenter: false,
|
reenter: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
Revolve: {
|
Revolve: {
|
||||||
target: 'idle',
|
target: 'idle',
|
||||||
guard: 'has valid revolve selection',
|
|
||||||
actions: ['AST revolve'],
|
actions: ['AST revolve'],
|
||||||
reenter: false,
|
reenter: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
Loft: {
|
Loft: {
|
||||||
target: 'Applying loft',
|
target: 'Applying loft',
|
||||||
guard: 'has valid loft selection',
|
|
||||||
reenter: true,
|
reenter: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
Shell: {
|
Shell: {
|
||||||
target: 'Applying shell',
|
target: 'Applying shell',
|
||||||
guard: 'has valid shell selection',
|
|
||||||
reenter: true,
|
reenter: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
Fillet: {
|
Fillet: {
|
||||||
target: 'idle',
|
target: 'idle',
|
||||||
guard: 'has valid edge treatment selection',
|
|
||||||
actions: ['AST fillet'],
|
actions: ['AST fillet'],
|
||||||
reenter: false,
|
reenter: false,
|
||||||
},
|
},
|
||||||
|