Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions e2e/playwright/sketch-solve-edit.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,4 +355,65 @@ test.describe('Sketch solve edit tests', { tag: '@desktop' }, () => {
await editor.expectEditor.toContain('sketch2::parallel([line1, line2])')
})
})

test('can delete individual constraints and the sketch block from the feature tree', async ({
page,
context,
homePage,
scene,
cmdBar,
editor,
toolbar,
}) => {
await test.step('Set up the app with test code', async () => {
const code = `@settings(experimentalFeatures = allow)

sketch(on = XY) {
line1 = sketch2::line(start = [var -3.58mm, var 3.79mm], end = [var 6.18mm, var 5.34mm])
sketch2::horizontal(line1)
line2 = sketch2::line(start = [var 6.79mm, var 3.56mm], end = [var 6.5mm, var -2.56mm])
sketch2::coincident([line2.start, line1.end])
}`
await context.addInitScript(async (code) => {
localStorage.setItem('persistCode', code)
}, code)
await page.setBodyDimensions({ width: 1200, height: 1000 })
await homePage.goToModelingScene()
await scene.settled(cmdBar)
await editor.expectEditor.toContain('sketch(on')
await toolbar.openFeatureTreePane()
})

await test.step('Delete first constraint from feature tree and verify code updates', async () => {
const op = await toolbar.getFeatureTreeOperation(
'Horizontal Constraint',
0
)
await op.click({ button: 'right' })
await page.getByRole('button', { name: 'Delete' }).click()
await scene.settled(cmdBar)
await editor.expectEditor.not.toContain('sketch2::horizontal(line1)')
})

await test.step('Delete second constraint from feature tree and verify code updates', async () => {
const op = await toolbar.getFeatureTreeOperation(
'Coincident Constraint',
0
)
await op.click({ button: 'right' })
await page.getByRole('button', { name: 'Delete' }).click()
await scene.settled(cmdBar)
await editor.expectEditor.not.toContain(
'sketch2::coincident([line2.start, line1.end])'
)
})

await test.step('Delete sketch block from feature tree and verify code updates', async () => {
const op = await toolbar.getFeatureTreeOperation('Solve Sketch', 0)
await op.click({ button: 'right' })
await page.getByRole('button', { name: 'Delete' }).click()
await scene.settled(cmdBar)
await editor.expectEditor.not.toContain('sketch(on')
})
})
})
102 changes: 96 additions & 6 deletions src/components/CustomIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,21 @@ const CustomIconMap = Object.freeze({
/>
</svg>
),
concentric: (
<svg
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="concentric"
>
<path
fillRule="evenodd"
clipRule="evenodd"
fill="currentColor"
d="M10 2C11.0506 2 12.0909 2.20734 13.0615 2.60938C14.0321 3.01141 14.9144 3.59995 15.6572 4.34277C16.4 5.08559 16.9886 5.96795 17.3906 6.93848C17.7927 7.90908 18 8.94942 18 10C18 11.0506 17.7927 12.0909 17.3906 13.0615C16.9886 14.0321 16.4 14.9144 15.6572 15.6572C14.9144 16.4 14.0321 16.9886 13.0615 17.3906C12.0909 17.7927 11.0506 18 10 18C8.94943 18 7.90908 17.7927 6.93848 17.3906C5.96795 16.9886 5.08559 16.4 4.34277 15.6572C3.59995 14.9144 3.01141 14.0321 2.60938 13.0615C2.20734 12.0909 2 11.0506 2 10C2 8.94942 2.20734 7.90908 2.60938 6.93848C3.01141 5.96795 3.59995 5.08559 4.34277 4.34277C5.08559 3.59995 5.96795 3.01141 6.93848 2.60938C7.90908 2.20734 8.94943 2 10 2ZM10 3C9.08075 3 8.17057 3.18142 7.32129 3.5332C6.47204 3.88499 5.6998 4.39981 5.0498 5.0498C4.39981 5.6998 3.88499 6.47204 3.5332 7.32129C3.18142 8.17057 3 9.08075 3 10C3 10.9193 3.18142 11.8294 3.5332 12.6787C3.88499 13.528 4.39981 14.3002 5.0498 14.9502C5.6998 15.6002 6.47204 16.115 7.32129 16.4668C8.17057 16.8186 9.08075 17 10 17C10.9193 17 11.8294 16.8186 12.6787 16.4668C13.528 16.115 14.3002 15.6002 14.9502 14.9502C15.6002 14.3002 16.115 13.528 16.4668 12.6787C16.8186 11.8294 17 10.9193 17 10C17 9.08075 16.8186 8.17057 16.4668 7.32129C16.115 6.47204 15.6002 5.6998 14.9502 5.0498C14.3002 4.39981 13.528 3.88499 12.6787 3.5332C11.8294 3.18142 10.9193 3 10 3ZM10 5C10.6565 5 11.3065 5.12964 11.9131 5.38086C12.5197 5.63213 13.0709 6.00055 13.5352 6.46484C13.9994 6.92914 14.3679 7.48029 14.6191 8.08691C14.8704 8.69349 15 9.34346 15 10C15 10.6565 14.8704 11.3065 14.6191 11.9131C14.3679 12.5197 13.9994 13.0709 13.5352 13.5352C13.0709 13.9994 12.5197 14.3679 11.9131 14.6191C11.3065 14.8704 10.6565 15 10 15C9.34346 15 8.69349 14.8704 8.08691 14.6191C7.48029 14.3679 6.92914 13.9994 6.46484 13.5352C6.00055 13.0709 5.63213 12.5197 5.38086 11.9131C5.12964 11.3065 5 10.6565 5 10C5 9.34346 5.12964 8.69349 5.38086 8.08691C5.63213 7.48029 6.00055 6.92914 6.46484 6.46484C6.92914 6.00055 7.48029 5.63213 8.08691 5.38086C8.69349 5.12964 9.34346 5 10 5ZM10 6C9.47483 6 8.95494 6.10376 8.46973 6.30469C7.98442 6.50571 7.54331 6.80044 7.17188 7.17188C6.80044 7.54331 6.50571 7.98442 6.30469 8.46973C6.10376 8.95494 6 9.47483 6 10C6 10.5252 6.10376 11.0451 6.30469 11.5303C6.50571 12.0156 6.80044 12.4567 7.17188 12.8281C7.54331 13.1996 7.98442 13.4943 8.46973 13.6953C8.95494 13.8962 9.47483 14 10 14C10.5252 14 11.0451 13.8962 11.5303 13.6953C12.0156 13.4943 12.4567 13.1996 12.8281 12.8281C13.1996 12.4567 13.4943 12.0156 13.6953 11.5303C13.8962 11.0451 14 10.5252 14 10C14 9.47483 13.8962 8.95494 13.6953 8.46973C13.4943 7.98442 13.1996 7.54331 12.8281 7.17188C12.4567 6.80044 12.0156 6.50571 11.5303 6.30469C11.0451 6.10376 10.5252 6 10 6Z"
/>
</svg>
),
command: (
<svg
width="20"
Expand Down Expand Up @@ -551,7 +566,7 @@ const CustomIconMap = Object.freeze({
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.6455 3.6455L14 2V5.291L5.291 14H2L6 18V14.7052L14.7052 6H18L16.3526 4.35261L16.3546 4.35065L15.6475 3.64354L15.6455 3.6455Z"
d="M15.6455 3.64551L15.6475 3.64355L16.3545 4.35059L16.3525 4.35254L18 6H14.7051L6 14.7051V18L2 14H5.29102L14 5.29102V2L15.6455 3.64551Z"
fill="currentColor"
/>
</svg>
Expand Down Expand Up @@ -609,7 +624,7 @@ const CustomIconMap = Object.freeze({
aria-label="equal"
>
<path
d="M5 8.78V7H14.52V8.78H5ZM5 13.02V11.24H14.52V13.02H5Z"
d="M14.5195 13.0195H5V11.2402H14.5195V13.0195ZM14.5195 8.78027H5V7H14.5195V8.78027Z"
fill="currentColor"
/>
</svg>
Expand Down Expand Up @@ -753,6 +768,21 @@ const CustomIconMap = Object.freeze({
/>
</svg>
),
fix: (
<svg
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="fix"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M16 10H14.0205L12.4326 12.75L11.5674 12.25L12.8662 10H10.6211L9.03418 12.75L8.60059 12.5L8.16797 12.25L9.4668 10H7.28809L5.70117 12.75L5.26758 12.5L4.83496 12.25L6.13379 10H4V9H16V10ZM16 8H4V7H16V8Z"
fill="currentColor"
/>
</svg>
),
floppyDiskArrow: (
<svg
viewBox="0 0 20 20"
Expand Down Expand Up @@ -928,7 +958,7 @@ const CustomIconMap = Object.freeze({
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4 9.5H16V11.5H4V9.5Z"
d="M16 11H4V10H16V11Z"
fill="currentColor"
/>
</svg>
Expand All @@ -948,6 +978,21 @@ const CustomIconMap = Object.freeze({
/>
</svg>
),
horizontalVertical: (
<svg
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="horizontal vertical"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7 13H18V14H7V18H6V14H2V13H6V2H7V13ZM16 16H13V15H16V16ZM9 7H8V4H9V7Z"
fill="currentColor"
/>
</svg>
),
import: (
<svg
width="20"
Expand Down Expand Up @@ -1190,6 +1235,21 @@ const CustomIconMap = Object.freeze({
/>
</svg>
),
midpoint: (
<svg
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="midpoint"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.25 8.5C11.0784 8.5 11.75 9.17157 11.75 10C11.75 10.8284 11.0784 11.5 10.25 11.5C9.42157 11.5 8.75 10.8284 8.75 10C8.75 9.17157 9.42157 8.5 10.25 8.5ZM7.54785 9.5C7.51802 9.66228 7.5 9.8291 7.5 10C7.5 10.1709 7.51802 10.3377 7.54785 10.5H3V9.5H7.54785ZM17 10.5H12.9521C12.982 10.3377 13 10.1709 13 10C13 9.8291 12.982 9.66228 12.9521 9.5H17V10.5Z"
fill="currentColor"
/>
</svg>
),
mirror: (
<svg
viewBox="0 0 20 20"
Expand Down Expand Up @@ -1235,6 +1295,21 @@ const CustomIconMap = Object.freeze({
/>
</svg>
),
normal: (
<svg
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="normal"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.89844 7.80078C8.62604 8.03085 8.40922 8.32391 8.27051 8.6582C7.96307 8.58324 7.64494 8.54395 7.32324 8.54394C6.26259 8.54407 5.24514 8.96584 4.49512 9.71582L3.78809 9.00879C4.72565 8.07127 5.99737 7.54504 7.32324 7.54492C7.86373 7.54493 8.3951 7.6334 8.89844 7.80078ZM11.8486 9.53027C11.8482 10.3582 11.1766 11.03 10.3486 11.0303C9.52066 11.03 8.84903 10.3582 8.84863 9.53027C8.84863 8.702 9.52042 8.03052 10.3486 8.03027C11.1768 8.03053 11.8486 8.702 11.8486 9.53027ZM12.3232 12.5439C12.3232 13.87 11.796 15.1414 10.8584 16.0791L10.1514 15.3721C10.9015 14.6219 11.3232 13.6048 11.3232 12.5439C11.3232 12.2224 11.2839 11.905 11.209 11.5977C11.5434 11.4589 11.8373 11.2422 12.0674 10.9697C12.2349 11.4728 12.3232 12.0036 12.3232 12.5439ZM12.3018 8.4043C12.1339 8.11091 11.9021 7.85936 11.625 7.66797L15.293 3.99902L16 4.70605L12.3018 8.4043Z"
fill="currentColor"
/>
</svg>
),
oneDot: (
<svg
viewBox="0 0 20 20"
Expand Down Expand Up @@ -1333,7 +1408,7 @@ const CustomIconMap = Object.freeze({
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 16V4H6V16H8ZM14 16V4H12V16H14Z"
d="M4.34318 12.8284L3.63607 12.1213L12.1214 3.63604L12.8285 4.34315L4.34318 12.8284ZM8.58582 17.0711L7.87871 16.364L16.364 7.87868L17.0711 8.58579L8.58582 17.0711Z"
fill="currentColor"
/>
</svg>
Expand Down Expand Up @@ -1684,6 +1759,21 @@ const CustomIconMap = Object.freeze({
/>
</svg>
),
symmetry: (
<svg
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-label="symmetry"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.5 5H12.1611V6H10.5V7.66699H9.5V6H4.5V14H9.5V12.333H10.5V14H12.1611V15H10.5V17H9.5V15H3.5V5H9.5V3H10.5V5ZM16.5 15H14.0889V14H15.5V11.4287H16.5V15ZM10.5 11H9.5V9H10.5V11ZM16.5 8.57129H15.5V6H14.0889V5H16.5V8.57129Z"
fill="currentColor"
/>
</svg>
),
tangent: (
<svg
viewBox="0 0 20 20"
Expand All @@ -1694,7 +1784,7 @@ const CustomIconMap = Object.freeze({
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12.73 2.73L9.23398 6.226C9.72614 6.46571 10.1178 6.87964 10.3288 7.38755C10.6321 7.31396 10.949 7.27497 11.275 7.27497C13.4841 7.27497 15.275 9.06583 15.275 11.275C15.275 13.4841 13.4841 15.275 11.275 15.275C9.06587 15.275 7.27501 13.4841 7.27501 11.275C7.27501 10.949 7.314 10.6321 7.38757 10.3288C6.87965 10.1178 6.46571 9.72614 6.226 9.23398L2.72998 12.73L3.43709 13.4371L6.32769 10.5465C6.29298 10.7843 6.27501 11.0275 6.27501 11.275C6.27501 14.0364 8.51358 16.275 11.275 16.275C14.0364 16.275 16.275 14.0364 16.275 11.275C16.275 8.51355 14.0364 6.27497 11.275 6.27497C11.0276 6.27497 10.7843 6.29294 10.5465 6.32765L13.4371 3.4371L12.73 2.73ZM8.26001 9.75C9.08844 9.75 9.76001 9.07843 9.76001 8.25C9.76001 7.42157 9.08844 6.75 8.26001 6.75C7.43158 6.75 6.76001 7.42157 6.76001 8.25C6.76001 9.07843 7.43158 9.75 8.26001 9.75Z"
d="M13.4375 3.4375L10.5459 6.32812C10.7839 6.29335 11.0278 6.2754 11.2754 6.27539C14.0366 6.27565 16.2754 8.51415 16.2754 11.2754C16.2751 14.0364 14.0364 16.2751 11.2754 16.2754C8.51417 16.2753 6.27564 14.0366 6.27539 11.2754C6.27539 11.0277 6.29335 10.7839 6.32812 10.5459L3.4375 13.4375L2.73047 12.7305L6.22852 9.23145C6.46817 9.72415 6.88033 10.117 7.38867 10.3281C7.31496 10.6317 7.27539 10.9491 7.27539 11.2754C7.27564 13.4843 9.06645 15.2753 11.2754 15.2754C13.4841 15.2751 15.2751 13.4842 15.2754 11.2754C15.2754 9.06644 13.4843 7.27565 11.2754 7.27539C10.9491 7.2754 10.6317 7.31496 10.3281 7.38867C10.117 6.88033 9.72415 6.46817 9.23145 6.22852L12.7305 2.73047L13.4375 3.4375ZM8.26074 6.75C9.08868 6.7504 9.76049 7.42203 9.76074 8.25C9.76049 9.07797 9.08868 9.7496 8.26074 9.75C7.43247 9.75 6.76099 9.07821 6.76074 8.25C6.761 7.42179 7.43247 6.75 8.26074 6.75Z"
fill="currentColor"
/>
</svg>
Expand Down Expand Up @@ -1750,7 +1840,7 @@ const CustomIconMap = Object.freeze({
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11 4V16H9V4H11Z"
d="M10 16H9V4H10V16Z"
fill="currentColor"
/>
</svg>
Expand Down
23 changes: 9 additions & 14 deletions src/components/layout/areas/FeatureTreePane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,8 @@ const OperationItem = (props: OperationProps) => {
if (
props.item.type === 'StdLibCall' ||
props.item.type === 'GroupBegin' ||
props.item.type === 'VariableDeclaration'
props.item.type === 'VariableDeclaration' ||
props.item.type === 'SketchSolve'
) {
props.send({
type: 'deleteOperation',
Expand Down Expand Up @@ -773,14 +774,14 @@ const OperationItem = (props: OperationProps) => {
]
: []),
...(props.item.type === 'StdLibCall' ||
props.item.type === 'VariableDeclaration'
props.item.type === 'VariableDeclaration' ||
props.item.type === 'SketchSolve'
? [
<ContextMenuItem
disabled={
!(
stdLibMap[props.item.name]?.prepareToEdit ||
props.item.type === 'VariableDeclaration'
)
props.item.type !== 'VariableDeclaration' &&
props.item.type !== 'SketchSolve' &&
stdLibMap[props.item.name]?.prepareToEdit === undefined
}
onClick={enterEditFlow}
hotkey="Double click"
Expand Down Expand Up @@ -855,7 +856,8 @@ const OperationItem = (props: OperationProps) => {
: []),
...(props.item.type === 'StdLibCall' ||
props.item.type === 'GroupBegin' ||
props.item.type === 'VariableDeclaration'
props.item.type === 'VariableDeclaration' ||
props.item.type === 'SketchSolve'
? [
<ContextMenuItem
onClick={deleteOperation}
Expand All @@ -866,13 +868,6 @@ const OperationItem = (props: OperationProps) => {
</ContextMenuItem>,
]
: []),
...(props.item.type === 'SketchSolve'
? [
<ContextMenuItem onClick={enterEditFlow} hotkey="Double click">
Edit
</ContextMenuItem>,
]
: []),
],
// eslint-disable-next-line react-hooks/exhaustive-deps -- TODO: blanket-ignored fix me!
[props.item, props.send]
Expand Down
32 changes: 32 additions & 0 deletions src/lang/modifyAst/deleteFromSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { err, reportRejection } from '@src/lib/trap'
import { isArray, roundOff } from '@src/lib/utils'
import { deleteEdgeTreatment } from '@src/lang/modifyAst/edges'
import type { ModuleType } from '@src/lib/wasm_lib_wrapper'
import type { SketchBlock } from '@rust/kcl-lib/bindings/SketchBlock'

export async function deleteFromSelection(
ast: Node<Program>,
Expand Down Expand Up @@ -153,6 +154,37 @@ export async function deleteFromSelection(
return astClone
}

// Sketch Solve Constraint case
const sketchBlock = getNodeFromPath<SketchBlock>(
astClone,
selection.codeRef.pathToNode,
wasmInstance,
'SketchBlock'
)
if (!err(sketchBlock) && sketchBlock.node.type === 'SketchBlock') {
const isItemInSketchBlock =
sketchBlock.deepPath.length > sketchBlock.shallowPath.length
const itemsIndex = -2 // index of items in body is second to last
const nodeIndex = sketchBlock.deepPath.at(itemsIndex)?.[0]
if (
isItemInSketchBlock &&
typeof nodeIndex === 'number' &&
sketchBlock.node.body.items[nodeIndex] !== undefined
) {
sketchBlock.node.body.items.splice(nodeIndex, 1)
return astClone
}

// Whole sketch block deletion case
if (
typeof nodeIndex === 'number' &&
astClone.body[nodeIndex] !== undefined
) {
astClone.body.splice(nodeIndex, 1)
return astClone
}
}

Comment on lines +157 to +187
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one more case 😬

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We surely can move this new deletion case to Rust, but I feel like this is something we could do all at once once we move there?

// Below is all AST-based deletion logic
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
Expand Down
1 change: 1 addition & 0 deletions src/lang/wasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export type SyntaxType =
| 'NonCodeNode'
| 'UnaryExpression'
| 'ImportStatement'
| 'SketchBlock'

export type { ExtrudeSurface } from '@rust/kcl-lib/bindings/ExtrudeSurface'
export type { KclValue } from '@rust/kcl-lib/bindings/KclValue'
Expand Down
Loading
Loading