Add Point-and-Click Deletion for Fillets and Chamfers (#5098)
* ast mod * point and click test * tsc * test test * unit test edit * topLevelRange * disable unit test * remove bad imports * fix typo * Fix cyclic dependency hell with getNodePathFromSourceRange * tsc * fix ImportStatement * fix isValueZero * pre-emptively ==> preemptively * yarn fmt-check * reenable the unit test * fmt * 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) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * add test * 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) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * several treatments * consolidate * typos * fix imports, consolidate * consolidate import * fix imports * add tests * stress test CI * fix test * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * fix tests * clean test for fillets * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Trigger CI * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * test chamfers * comments * simplify main tests * typo * typo2 * remove import * clean up comments --------- Co-authored-by: 49lf <ircsurfer33@gmail.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
This commit is contained in:
@ -1296,6 +1296,167 @@ extrude001 = extrude(-12, sketch001)
|
|||||||
lowTolerance
|
lowTolerance
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Test 3: Delete fillets
|
||||||
|
await test.step('Delete fillet via feature tree selection', async () => {
|
||||||
|
await test.step('Open Feature Tree Pane', async () => {
|
||||||
|
await toolbar.openPane('feature-tree')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
})
|
||||||
|
await test.step('Delete fillet via feature tree selection', async () => {
|
||||||
|
await editor.expectEditor.toContain(secondFilletDeclaration)
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation('Fillet', 1)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await scene.expectPixelColor(edgeColorWhite, secondEdgeLocation, 15) // deleted
|
||||||
|
await editor.expectEditor.not.toContain(secondFilletDeclaration)
|
||||||
|
await scene.expectPixelColor(filletColor, firstEdgeLocation, 15) // stayed
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`Fillet point-and-click delete`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
}) => {
|
||||||
|
// Code samples
|
||||||
|
const initialCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-12, -6], %)
|
||||||
|
|> line([0, 12], %)
|
||||||
|
|> line([24, 0], %, $seg02)
|
||||||
|
|> line([0, -12], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %, $seg01)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-12, sketch001)
|
||||||
|
|> fillet({ radius = 5, tags = [seg01] }, %) // fillet01
|
||||||
|
|> fillet({ radius = 5, tags = [seg02] }, %) // fillet02
|
||||||
|
fillet03 = fillet({ radius = 5, tags = [getOppositeEdge(seg01)]}, extrude001)
|
||||||
|
fillet04 = fillet({ radius = 5, tags = [getOppositeEdge(seg02)]}, extrude001)
|
||||||
|
`
|
||||||
|
const pipedFilletDeclaration = 'fillet({ radius = 5, tags = [seg01] }, %)'
|
||||||
|
const secondPipedFilletDeclaration =
|
||||||
|
'fillet({ radius = 5, tags = [seg02] }, %)'
|
||||||
|
const standaloneFilletDeclaration =
|
||||||
|
'fillet03 = fillet({ radius = 5, tags = [getOppositeEdge(seg01)]}, extrude001)'
|
||||||
|
const secondStandaloneFilletDeclaration =
|
||||||
|
'fillet04 = fillet({ radius = 5, tags = [getOppositeEdge(seg02)]}, extrude001)'
|
||||||
|
|
||||||
|
// Locators
|
||||||
|
const pipedFilletEdgeLocation = { x: 600, y: 193 }
|
||||||
|
const standaloneFilletEdgeLocation = { x: 600, y: 383 }
|
||||||
|
const bodyLocation = { x: 630, y: 290 }
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const edgeColorWhite: [number, number, number] = [248, 248, 248]
|
||||||
|
const bodyColor: [number, number, number] = [155, 155, 155]
|
||||||
|
const filletColor: [number, number, number] = [127, 127, 127]
|
||||||
|
const backgroundColor: [number, number, number] = [30, 30, 30]
|
||||||
|
const lowTolerance = 20
|
||||||
|
const highTolerance = 40
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
// verify modeling scene is loaded
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
backgroundColor,
|
||||||
|
standaloneFilletEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
|
||||||
|
// wait for stream to load
|
||||||
|
await scene.expectPixelColor(bodyColor, bodyLocation, highTolerance)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test
|
||||||
|
await test.step('Delete fillet via feature tree selection', async () => {
|
||||||
|
await test.step('Open Feature Tree Pane', async () => {
|
||||||
|
await toolbar.openPane('feature-tree')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Delete piped fillet via feature tree selection', async () => {
|
||||||
|
await test.step('Verify all fillets are present in the editor', async () => {
|
||||||
|
await editor.expectEditor.toContain(pipedFilletDeclaration)
|
||||||
|
await editor.expectEditor.toContain(secondPipedFilletDeclaration)
|
||||||
|
await editor.expectEditor.toContain(standaloneFilletDeclaration)
|
||||||
|
await editor.expectEditor.toContain(secondStandaloneFilletDeclaration)
|
||||||
|
})
|
||||||
|
await test.step('Verify test fillets are present in the scene', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
filletColor,
|
||||||
|
pipedFilletEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
backgroundColor,
|
||||||
|
standaloneFilletEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Delete piped fillet', async () => {
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||||
|
'Fillet',
|
||||||
|
0
|
||||||
|
)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
})
|
||||||
|
await test.step('Verify piped fillet is deleted but other fillets are not (in the editor)', async () => {
|
||||||
|
await editor.expectEditor.not.toContain(pipedFilletDeclaration)
|
||||||
|
await editor.expectEditor.toContain(secondPipedFilletDeclaration)
|
||||||
|
await editor.expectEditor.toContain(standaloneFilletDeclaration)
|
||||||
|
await editor.expectEditor.toContain(secondStandaloneFilletDeclaration)
|
||||||
|
})
|
||||||
|
await test.step('Verify piped fillet is deleted but non-piped is not (in the scene)', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite, // you see edge because fillet is deleted
|
||||||
|
pipedFilletEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
backgroundColor, // you see background because fillet is not deleted
|
||||||
|
standaloneFilletEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Delete non-piped fillet via feature tree selection', async () => {
|
||||||
|
await test.step('Delete non-piped fillet', async () => {
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||||
|
'Fillet',
|
||||||
|
1
|
||||||
|
)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
})
|
||||||
|
await test.step('Verify non-piped fillet is deleted but other two fillets are not (in the editor)', async () => {
|
||||||
|
await editor.expectEditor.toContain(secondPipedFilletDeclaration)
|
||||||
|
await editor.expectEditor.not.toContain(standaloneFilletDeclaration)
|
||||||
|
await editor.expectEditor.toContain(secondStandaloneFilletDeclaration)
|
||||||
|
})
|
||||||
|
await test.step('Verify non-piped fillet is deleted but piped is not (in the scene)', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
standaloneFilletEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test(`Chamfer point-and-click`, async ({
|
test(`Chamfer point-and-click`, async ({
|
||||||
@ -1511,6 +1672,163 @@ extrude001 = extrude(-12, sketch001)
|
|||||||
lowTolerance
|
lowTolerance
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Test 3: Delete chamfer via feature tree selection
|
||||||
|
await test.step('Open Feature Tree Pane', async () => {
|
||||||
|
await toolbar.openPane('feature-tree')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
})
|
||||||
|
await test.step('Delete chamfer via feature tree selection', async () => {
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation('Chamfer', 1)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await scene.expectPixelColor(edgeColorWhite, secondEdgeLocation, 15) // deleted
|
||||||
|
await scene.expectPixelColor(chamferColor, firstEdgeLocation, 15) // stayed
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`Chamfer point-and-click delete`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
}) => {
|
||||||
|
// Code samples
|
||||||
|
const initialCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-12, -6], %)
|
||||||
|
|> line([0, 12], %)
|
||||||
|
|> line([24, 0], %, $seg02)
|
||||||
|
|> line([0, -12], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %, $seg01)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-12, sketch001)
|
||||||
|
|> chamfer({ length = 5, tags = [seg01] }, %) // chamfer01
|
||||||
|
|> chamfer({ length = 5, tags = [seg02] }, %) // chamfer02
|
||||||
|
chamfer03 = chamfer({ length = 5, tags = [getOppositeEdge(seg01)]}, extrude001)
|
||||||
|
chamfer04 = chamfer({ length = 5, tags = [getOppositeEdge(seg02)]}, extrude001)
|
||||||
|
`
|
||||||
|
const pipedChamferDeclaration = 'chamfer({ length = 5, tags = [seg01] }, %)'
|
||||||
|
const secondPipedChamferDeclaration =
|
||||||
|
'chamfer({ length = 5, tags = [seg02] }, %)'
|
||||||
|
const standaloneChamferDeclaration =
|
||||||
|
'chamfer03 = chamfer({ length = 5, tags = [getOppositeEdge(seg01)]}, extrude001)'
|
||||||
|
const secondStandaloneChamferDeclaration =
|
||||||
|
'chamfer04 = chamfer({ length = 5, tags = [getOppositeEdge(seg02)]}, extrude001)'
|
||||||
|
|
||||||
|
// Locators
|
||||||
|
const pipedChamferEdgeLocation = { x: 600, y: 193 }
|
||||||
|
const standaloneChamferEdgeLocation = { x: 600, y: 383 }
|
||||||
|
const bodyLocation = { x: 630, y: 290 }
|
||||||
|
|
||||||
|
// Colors
|
||||||
|
const edgeColorWhite: [number, number, number] = [248, 248, 248]
|
||||||
|
const bodyColor: [number, number, number] = [155, 155, 155]
|
||||||
|
const chamferColor: [number, number, number] = [168, 168, 168]
|
||||||
|
const backgroundColor: [number, number, number] = [30, 30, 30]
|
||||||
|
const lowTolerance = 20
|
||||||
|
const highTolerance = 40
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
|
||||||
|
// verify modeling scene is loaded
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
backgroundColor,
|
||||||
|
standaloneChamferEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
|
||||||
|
// wait for stream to load
|
||||||
|
await scene.expectPixelColor(bodyColor, bodyLocation, highTolerance)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test
|
||||||
|
await test.step('Delete chamfer via feature tree selection', async () => {
|
||||||
|
await test.step('Open Feature Tree Pane', async () => {
|
||||||
|
await toolbar.openPane('feature-tree')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Delete piped chamfer via feature tree selection', async () => {
|
||||||
|
await test.step('Verify all chamfers are present in the editor', async () => {
|
||||||
|
await editor.expectEditor.toContain(pipedChamferDeclaration)
|
||||||
|
await editor.expectEditor.toContain(secondPipedChamferDeclaration)
|
||||||
|
await editor.expectEditor.toContain(standaloneChamferDeclaration)
|
||||||
|
await editor.expectEditor.toContain(secondStandaloneChamferDeclaration)
|
||||||
|
})
|
||||||
|
await test.step('Verify test chamfers are present in the scene', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
chamferColor,
|
||||||
|
pipedChamferEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
backgroundColor,
|
||||||
|
standaloneChamferEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
await test.step('Delete piped chamfer', async () => {
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||||
|
'Chamfer',
|
||||||
|
0
|
||||||
|
)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
})
|
||||||
|
await test.step('Verify piped chamfer is deleted but other chamfers are not (in the editor)', async () => {
|
||||||
|
await editor.expectEditor.not.toContain(pipedChamferDeclaration)
|
||||||
|
await editor.expectEditor.toContain(secondPipedChamferDeclaration)
|
||||||
|
await editor.expectEditor.toContain(standaloneChamferDeclaration)
|
||||||
|
await editor.expectEditor.toContain(secondStandaloneChamferDeclaration)
|
||||||
|
})
|
||||||
|
await test.step('Verify piped chamfer is deleted but non-piped is not (in the scene)', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite, // you see edge color because chamfer is deleted
|
||||||
|
pipedChamferEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
backgroundColor, // you see background color instead of edge because it's chamfered
|
||||||
|
standaloneChamferEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Delete non-piped chamfer via feature tree selection', async () => {
|
||||||
|
await test.step('Delete non-piped chamfer', async () => {
|
||||||
|
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||||
|
'Chamfer',
|
||||||
|
1
|
||||||
|
)
|
||||||
|
await operationButton.click({ button: 'left' })
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
})
|
||||||
|
await test.step('Verify non-piped chamfer is deleted but other two chamfers are not (in the editor)', async () => {
|
||||||
|
await editor.expectEditor.toContain(secondPipedChamferDeclaration)
|
||||||
|
await editor.expectEditor.not.toContain(standaloneChamferDeclaration)
|
||||||
|
await editor.expectEditor.toContain(secondStandaloneChamferDeclaration)
|
||||||
|
})
|
||||||
|
await test.step('Verify non-piped chamfer is deleted but piped is not (in the scene)', async () => {
|
||||||
|
await scene.expectPixelColor(
|
||||||
|
edgeColorWhite,
|
||||||
|
standaloneChamferEdgeLocation,
|
||||||
|
lowTolerance
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const shellPointAndClickCapCases = [
|
const shellPointAndClickCapCases = [
|
||||||
|
@ -47,6 +47,7 @@ import { Models } from '@kittycad/lib'
|
|||||||
import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
||||||
|
import { deleteEdgeTreatment } from './modifyAst/addEdgeTreatment'
|
||||||
|
|
||||||
export function startSketchOnDefault(
|
export function startSketchOnDefault(
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
@ -1371,6 +1372,8 @@ export async function deleteFromSelection(
|
|||||||
}
|
}
|
||||||
// await prom
|
// await prom
|
||||||
return astClone
|
return astClone
|
||||||
|
} else if (selection.artifact?.type === 'edgeCut') {
|
||||||
|
return deleteEdgeTreatment(astClone, selection)
|
||||||
} else if (varDec.node.init.type === 'PipeExpression') {
|
} else if (varDec.node.init.type === 'PipeExpression') {
|
||||||
const pipeBody = varDec.node.init.body
|
const pipeBody = varDec.node.init.body
|
||||||
if (
|
if (
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
FilletParameters,
|
FilletParameters,
|
||||||
ChamferParameters,
|
ChamferParameters,
|
||||||
EdgeTreatmentParameters,
|
EdgeTreatmentParameters,
|
||||||
|
deleteEdgeTreatment,
|
||||||
} from './addEdgeTreatment'
|
} from './addEdgeTreatment'
|
||||||
import { getNodeFromPath } from '../queryAst'
|
import { getNodeFromPath } from '../queryAst'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
@ -287,7 +288,7 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
|||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply edge treatment to seleciton
|
// apply edge treatment to selection
|
||||||
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
|
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
|
||||||
if (err(result)) {
|
if (err(result)) {
|
||||||
return result
|
return result
|
||||||
@ -298,6 +299,46 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
|||||||
|
|
||||||
expect(newCode).toContain(expectedCode)
|
expect(newCode).toContain(expectedCode)
|
||||||
}
|
}
|
||||||
|
const runDeleteEdgeTreatmentTest = async (
|
||||||
|
code: string,
|
||||||
|
edgeTreatmentSnippet: string,
|
||||||
|
expectedCode: string
|
||||||
|
) => {
|
||||||
|
// parse ast
|
||||||
|
const ast = assertParse(code)
|
||||||
|
|
||||||
|
// update artifact graph
|
||||||
|
await kclManager.executeAst({ ast })
|
||||||
|
const artifactGraph = engineCommandManager.artifactGraph
|
||||||
|
|
||||||
|
// define snippet range
|
||||||
|
const edgeTreatmentRange = topLevelRange(
|
||||||
|
code.indexOf(edgeTreatmentSnippet),
|
||||||
|
code.indexOf(edgeTreatmentSnippet) + edgeTreatmentSnippet.length
|
||||||
|
)
|
||||||
|
|
||||||
|
// find artifact
|
||||||
|
const maybeArtifact = [...artifactGraph].find(([, artifact]) => {
|
||||||
|
if (!('codeRef' in artifact)) return false
|
||||||
|
return isOverlap(artifact.codeRef.range, edgeTreatmentRange)
|
||||||
|
})
|
||||||
|
|
||||||
|
// build selection
|
||||||
|
const selection: Selection = {
|
||||||
|
codeRef: codeRefFromRange(edgeTreatmentRange, ast),
|
||||||
|
artifact: maybeArtifact ? maybeArtifact[1] : undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete edge treatment
|
||||||
|
const result = await deleteEdgeTreatment(ast, selection)
|
||||||
|
if (err(result)) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// recast and check
|
||||||
|
const newCode = recast(result)
|
||||||
|
expect(newCode).toContain(expectedCode)
|
||||||
|
}
|
||||||
const createFilletParameters = (radiusValue: number): FilletParameters => ({
|
const createFilletParameters = (radiusValue: number): FilletParameters => ({
|
||||||
type: EdgeTreatmentType.Fillet,
|
type: EdgeTreatmentType.Fillet,
|
||||||
radius: {
|
radius: {
|
||||||
@ -574,6 +615,191 @@ extrude002 = extrude(-25, sketch002)
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
describe(`Testing deleteEdgeTreatment with ${edgeTreatmentType}s`, () => {
|
||||||
|
// simple cases
|
||||||
|
it(`should delete a piped ${edgeTreatmentType} from a single segment`, async () => {
|
||||||
|
const code = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg01)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)
|
||||||
|
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
|
||||||
|
const edgeTreatmentSnippet = `${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
|
||||||
|
const expectedCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg01)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)`
|
||||||
|
|
||||||
|
await runDeleteEdgeTreatmentTest(
|
||||||
|
code,
|
||||||
|
edgeTreatmentSnippet,
|
||||||
|
expectedCode
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it(`should delete a non-piped ${edgeTreatmentType} from a single segment`, async () => {
|
||||||
|
const code = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg01)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)
|
||||||
|
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, extrude001)`
|
||||||
|
const edgeTreatmentSnippet = `fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, extrude001)`
|
||||||
|
const expectedCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg01)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)`
|
||||||
|
|
||||||
|
await runDeleteEdgeTreatmentTest(
|
||||||
|
code,
|
||||||
|
edgeTreatmentSnippet,
|
||||||
|
expectedCode
|
||||||
|
)
|
||||||
|
})
|
||||||
|
// getOppositeEdge and getNextAdjacentEdge cases
|
||||||
|
it(`should delete a piped ${edgeTreatmentType} tagged with getOppositeEdge`, async () => {
|
||||||
|
const code = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg01)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)
|
||||||
|
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [getOppositeEdge(seg01)] }, extrude001)`
|
||||||
|
const edgeTreatmentSnippet = `fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [getOppositeEdge(seg01)] }, extrude001)`
|
||||||
|
const expectedCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg01)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)`
|
||||||
|
|
||||||
|
await runDeleteEdgeTreatmentTest(
|
||||||
|
code,
|
||||||
|
edgeTreatmentSnippet,
|
||||||
|
expectedCode
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it(`should delete a non-piped ${edgeTreatmentType} tagged with getNextAdjacentEdge`, async () => {
|
||||||
|
const code = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg01)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)
|
||||||
|
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [getNextAdjacentEdge(seg01)] }, extrude001)`
|
||||||
|
const edgeTreatmentSnippet = `fillet001 = ${edgeTreatmentType}({ ${parameterName} = 3, tags = [getNextAdjacentEdge(seg01)] }, extrude001)`
|
||||||
|
const expectedCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg01)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)`
|
||||||
|
|
||||||
|
await runDeleteEdgeTreatmentTest(
|
||||||
|
code,
|
||||||
|
edgeTreatmentSnippet,
|
||||||
|
expectedCode
|
||||||
|
)
|
||||||
|
})
|
||||||
|
// cases with several edge treatments
|
||||||
|
it(`should delete a piped ${edgeTreatmentType} from a body with multiple treatments`, async () => {
|
||||||
|
const code = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %, $seg01)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg02)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)
|
||||||
|
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)
|
||||||
|
|> fillet({ radius = 5, tags = [getOppositeEdge(seg02)] }, %)
|
||||||
|
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 6, tags = [seg02] }, extrude001)
|
||||||
|
chamfer001 = chamfer({ length = 5, tags = [getOppositeEdge(seg01)] }, extrude001)`
|
||||||
|
const edgeTreatmentSnippet = `${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)`
|
||||||
|
const expectedCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %, $seg01)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg02)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)
|
||||||
|
|> fillet({
|
||||||
|
radius = 5,
|
||||||
|
tags = [getOppositeEdge(seg02)]
|
||||||
|
}, %)
|
||||||
|
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 6, tags = [seg02] }, extrude001)
|
||||||
|
chamfer001 = chamfer({
|
||||||
|
length = 5,
|
||||||
|
tags = [getOppositeEdge(seg01)]
|
||||||
|
}, extrude001)`
|
||||||
|
|
||||||
|
await runDeleteEdgeTreatmentTest(
|
||||||
|
code,
|
||||||
|
edgeTreatmentSnippet,
|
||||||
|
expectedCode
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it(`should delete a non-piped ${edgeTreatmentType} from a body with multiple treatments`, async () => {
|
||||||
|
const code = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %, $seg01)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg02)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)
|
||||||
|
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)
|
||||||
|
|> fillet({ radius = 5, tags = [getOppositeEdge(seg02)] }, %)
|
||||||
|
fillet001 = ${edgeTreatmentType}({ ${parameterName} = 6, tags = [seg02] }, extrude001)
|
||||||
|
chamfer001 = chamfer({ length = 5, tags = [getOppositeEdge(seg01)] }, extrude001)`
|
||||||
|
const edgeTreatmentSnippet = `fillet001 = ${edgeTreatmentType}({ ${parameterName} = 6, tags = [seg02] }, extrude001)`
|
||||||
|
const expectedCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %, $seg01)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %, $seg02)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(-15, sketch001)
|
||||||
|
|> ${edgeTreatmentType}({ ${parameterName} = 3, tags = [seg01] }, %)
|
||||||
|
|> fillet({
|
||||||
|
radius = 5,
|
||||||
|
tags = [getOppositeEdge(seg02)]
|
||||||
|
}, %)
|
||||||
|
chamfer001 = chamfer({
|
||||||
|
length = 5,
|
||||||
|
tags = [getOppositeEdge(seg01)]
|
||||||
|
}, extrude001)`
|
||||||
|
|
||||||
|
await runDeleteEdgeTreatmentTest(
|
||||||
|
code,
|
||||||
|
edgeTreatmentSnippet,
|
||||||
|
expectedCode
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import {
|
|||||||
Identifier,
|
Identifier,
|
||||||
ObjectExpression,
|
ObjectExpression,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
|
PipeExpression,
|
||||||
Program,
|
Program,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
@ -722,3 +723,148 @@ export const isTagUsedInEdgeTreatment = ({
|
|||||||
|
|
||||||
return edges
|
return edges
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Delete Edge Treatment
|
||||||
|
export async function deleteEdgeTreatment(
|
||||||
|
ast: Node<Program>,
|
||||||
|
selection: Selection
|
||||||
|
): Promise<Node<Program> | Error> {
|
||||||
|
/**
|
||||||
|
* Deletes an edge treatment (fillet or chamfer)
|
||||||
|
* from the AST based on the selection.
|
||||||
|
* Handles both standalone treatments
|
||||||
|
* and those within a PipeExpression.
|
||||||
|
*
|
||||||
|
* Supported cases:
|
||||||
|
* [+] fillet and chamfer
|
||||||
|
* [+] piped and non-piped edge treatments
|
||||||
|
* [-] delete single tag from array of tags (currently whole expression is deleted)
|
||||||
|
* [-] multiple selections with different edge treatments (currently single selection is supported)
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 1. Validate Selection Type
|
||||||
|
const { artifact } = selection
|
||||||
|
if (!artifact || artifact.type !== 'edgeCut') {
|
||||||
|
return new Error('Selection is not an edge cut')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { subType: edgeTreatmentType } = artifact
|
||||||
|
if (
|
||||||
|
!edgeTreatmentType ||
|
||||||
|
!['fillet', 'chamfer'].includes(edgeTreatmentType)
|
||||||
|
) {
|
||||||
|
return new Error('Unsupported or missing edge treatment type')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Clone ast and retrieve the VariableDeclarator
|
||||||
|
const astClone = structuredClone(ast)
|
||||||
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
|
ast,
|
||||||
|
selection?.codeRef?.pathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
if (err(varDec)) return varDec
|
||||||
|
|
||||||
|
// 3: Check if edge treatment is in a pipe
|
||||||
|
const inPipe = varDec.node.init.type === 'PipeExpression'
|
||||||
|
|
||||||
|
// 4A. Handle standalone edge treatment
|
||||||
|
if (!inPipe) {
|
||||||
|
const varDecPathStep = varDec.shallowPath[1]
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Array.isArray(varDecPathStep) ||
|
||||||
|
typeof varDecPathStep[0] !== 'number'
|
||||||
|
) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid shallowPath structure: expected a number at shallowPath[1][0]'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const varDecIndex: number = varDecPathStep[0]
|
||||||
|
|
||||||
|
// Remove entire VariableDeclarator from the ast
|
||||||
|
astClone.body.splice(varDecIndex, 1)
|
||||||
|
return astClone
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4B. Handle edge treatment within pipe
|
||||||
|
if (inPipe) {
|
||||||
|
// Retrieve the CallExpression path
|
||||||
|
const callExp =
|
||||||
|
getNodeFromPath<CallExpression>(
|
||||||
|
ast,
|
||||||
|
selection?.codeRef?.pathToNode,
|
||||||
|
'CallExpression'
|
||||||
|
) ?? null
|
||||||
|
if (err(callExp)) return callExp
|
||||||
|
|
||||||
|
const shallowPath = callExp.shallowPath
|
||||||
|
|
||||||
|
// Initialize variables to hold the PipeExpression path and callIndex
|
||||||
|
let pipeExpressionPath: PathToNode | null = null
|
||||||
|
let callIndex: number | null = null
|
||||||
|
|
||||||
|
// Iterate through the shallowPath to find the PipeExpression and callIndex
|
||||||
|
for (let i = 0; i < shallowPath.length - 1; i++) {
|
||||||
|
const [key, value] = shallowPath[i]
|
||||||
|
|
||||||
|
if (key === 'body' && value === 'PipeExpression') {
|
||||||
|
pipeExpressionPath = shallowPath.slice(0, i + 1)
|
||||||
|
|
||||||
|
const nextStep = shallowPath[i + 1]
|
||||||
|
if (
|
||||||
|
nextStep &&
|
||||||
|
nextStep[1] === 'index' &&
|
||||||
|
typeof nextStep[0] === 'number'
|
||||||
|
) {
|
||||||
|
callIndex = nextStep[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pipeExpressionPath) {
|
||||||
|
return new Error('PipeExpression not found in path')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callIndex === null) {
|
||||||
|
return new Error('Failed to extract CallExpression index')
|
||||||
|
}
|
||||||
|
// Retrieve the PipeExpression node
|
||||||
|
const pipeExpressionNode = getNodeFromPath<PipeExpression>(
|
||||||
|
astClone,
|
||||||
|
pipeExpressionPath,
|
||||||
|
'PipeExpression'
|
||||||
|
)
|
||||||
|
if (err(pipeExpressionNode)) return pipeExpressionNode
|
||||||
|
|
||||||
|
// Ensure that the PipeExpression.body is an array
|
||||||
|
if (!Array.isArray(pipeExpressionNode.node.body)) {
|
||||||
|
return new Error('PipeExpression body is not an array')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the CallExpression at the specified index
|
||||||
|
pipeExpressionNode.node.body.splice(callIndex, 1)
|
||||||
|
|
||||||
|
// Remove VariableDeclarator if PipeExpression.body is empty
|
||||||
|
if (pipeExpressionNode.node.body.length === 0) {
|
||||||
|
const varDecPathStep = varDec.shallowPath[1]
|
||||||
|
if (
|
||||||
|
!Array.isArray(varDecPathStep) ||
|
||||||
|
typeof varDecPathStep[0] !== 'number'
|
||||||
|
) {
|
||||||
|
return new Error(
|
||||||
|
'Invalid shallowPath structure: expected a number at shallowPath[1][0]'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const varDecIndex: number = varDecPathStep[0]
|
||||||
|
astClone.body.splice(varDecIndex, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return astClone
|
||||||
|
}
|
||||||
|
|
||||||
|
return Error('Delete fillets not implemented')
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user