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
|
||||
)
|
||||
})
|
||||
|
||||
// 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 ({
|
||||
@ -1511,6 +1672,163 @@ extrude001 = extrude(-12, sketch001)
|
||||
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 = [
|
||||
|
@ -47,6 +47,7 @@ import { Models } from '@kittycad/lib'
|
||||
import { ExtrudeFacePlane } from 'machines/modelingMachine'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { KclExpressionWithVariable } from 'lib/commandTypes'
|
||||
import { deleteEdgeTreatment } from './modifyAst/addEdgeTreatment'
|
||||
|
||||
export function startSketchOnDefault(
|
||||
node: Node<Program>,
|
||||
@ -1371,6 +1372,8 @@ export async function deleteFromSelection(
|
||||
}
|
||||
// await prom
|
||||
return astClone
|
||||
} else if (selection.artifact?.type === 'edgeCut') {
|
||||
return deleteEdgeTreatment(astClone, selection)
|
||||
} else if (varDec.node.init.type === 'PipeExpression') {
|
||||
const pipeBody = varDec.node.init.body
|
||||
if (
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
FilletParameters,
|
||||
ChamferParameters,
|
||||
EdgeTreatmentParameters,
|
||||
deleteEdgeTreatment,
|
||||
} from './addEdgeTreatment'
|
||||
import { getNodeFromPath } from '../queryAst'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
@ -287,7 +288,7 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
||||
otherSelections: [],
|
||||
}
|
||||
|
||||
// apply edge treatment to seleciton
|
||||
// apply edge treatment to selection
|
||||
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
|
||||
if (err(result)) {
|
||||
return result
|
||||
@ -298,6 +299,46 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
||||
|
||||
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 => ({
|
||||
type: EdgeTreatmentType.Fillet,
|
||||
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,
|
||||
ObjectExpression,
|
||||
PathToNode,
|
||||
PipeExpression,
|
||||
Program,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
@ -722,3 +723,148 @@ export const isTagUsedInEdgeTreatment = ({
|
||||
|
||||
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