Hook up chamfer UI with AST-mod (#4694)

* button

* config

* hook up with ast

* cmd bar test

* button states fix and test

* little naming fix

* xState action to actor

* remove button state test updates

* fixture-based approach

* nightly

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>

* Update src/lib/toolbar.ts

Co-authored-by: Frank Noirot <frank@zoo.dev>

---------

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>
This commit is contained in:
max
2025-01-14 18:08:32 +01:00
committed by GitHub
parent bf9d01a8dd
commit 9737c2550a
5 changed files with 299 additions and 5 deletions

View File

@ -15,6 +15,7 @@ export class ToolbarFixture {
extrudeButton!: Locator
loftButton!: Locator
sweepButton!: Locator
chamferButton!: Locator
shellButton!: Locator
offsetPlaneButton!: Locator
startSketchBtn!: Locator
@ -42,6 +43,7 @@ export class ToolbarFixture {
this.extrudeButton = page.getByTestId('extrude')
this.loftButton = page.getByTestId('loft')
this.sweepButton = page.getByTestId('sweep')
this.chamferButton = page.getByTestId('chamfer3d')
this.shellButton = page.getByTestId('shell')
this.offsetPlaneButton = page.getByTestId('plane-offset')
this.startSketchBtn = page.getByTestId('sketch')

View File

@ -1032,6 +1032,222 @@ sketch002 = startSketchOn('XZ')
})
})
test(`Chamfer point-and-click`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
// TODO: fix this test on windows after the electron migration
test.skip(process.platform === 'win32', 'Skip on windows')
// Code samples
const initialCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-12, -6], %)
|> line([0, 12], %)
|> line([24, 0], %)
|> line([0, -12], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(-12, sketch001)
`
const firstChamferDeclaration = 'chamfer({ length = 5, tags = [seg01] }, %)'
const secondChamferDeclaration =
'chamfer({ length = 5, tags = [getOppositeEdge(seg01)] }, %)'
// Locators
const firstEdgeLocation = { x: 600, y: 193 }
const secondEdgeLocation = { x: 600, y: 383 }
const bodyLocation = { x: 630, y: 290 }
const [clickOnFirstEdge] = scene.makeMouseHelpers(
firstEdgeLocation.x,
firstEdgeLocation.y
)
const [clickOnSecondEdge] = scene.makeMouseHelpers(
secondEdgeLocation.x,
secondEdgeLocation.y
)
// Colors
const edgeColorWhite: [number, number, number] = [248, 248, 248]
const edgeColorYellow: [number, number, number] = [251, 251, 40] // Mac:B=67 Ubuntu:B=12
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 context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await test.step(`Verify scene is loaded`, async () => {
// verify modeling scene is loaded
await scene.expectPixelColor(
backgroundColor,
secondEdgeLocation,
lowTolerance
)
// wait for stream to load
await scene.expectPixelColor(bodyColor, bodyLocation, highTolerance)
})
// Test 1: Command bar flow with preselected edges
await test.step(`Select first edge`, async () => {
await scene.expectPixelColor(
edgeColorWhite,
firstEdgeLocation,
lowTolerance
)
await clickOnFirstEdge()
await scene.expectPixelColor(
edgeColorYellow,
firstEdgeLocation,
highTolerance // Ubuntu color mismatch can require high tolerance
)
})
await test.step(`Apply chamfer to the preselected edge`, async () => {
await toolbar.chamferButton.click()
await cmdBar.expectState({
commandName: 'Chamfer',
highlightedHeaderArg: 'selection',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Length: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Chamfer',
highlightedHeaderArg: 'length',
currentArgKey: 'length',
currentArgValue: '5',
headerArguments: {
Selection: '1 face',
Length: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Chamfer',
headerArguments: {
Selection: '1 face',
Length: '5',
},
stage: 'review',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor`, async () => {
await editor.expectEditor.toContain(firstChamferDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: ['|>chamfer({length=5,tags=[seg01]},%)'],
highlightedCode: '',
})
})
await test.step(`Confirm scene has changed`, async () => {
await scene.expectPixelColor(chamferColor, firstEdgeLocation, lowTolerance)
})
// Test 2: Command bar flow without preselected edges
await test.step(`Open chamfer UI without selecting edges`, async () => {
await toolbar.chamferButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Length: '',
},
highlightedHeaderArg: 'selection',
commandName: 'Chamfer',
})
})
await test.step(`Select second edge`, async () => {
await scene.expectPixelColor(
edgeColorWhite,
secondEdgeLocation,
lowTolerance
)
await clickOnSecondEdge()
await scene.expectPixelColor(
edgeColorYellow,
secondEdgeLocation,
highTolerance // Ubuntu color mismatch can require high tolerance
)
})
await test.step(`Apply chamfer to the second edge`, async () => {
await cmdBar.expectState({
commandName: 'Chamfer',
highlightedHeaderArg: 'selection',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Length: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Chamfer',
highlightedHeaderArg: 'length',
currentArgKey: 'length',
currentArgValue: '5',
headerArguments: {
Selection: '1 sweepEdge',
Length: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Chamfer',
headerArguments: {
Selection: '1 sweepEdge',
Length: '5',
},
stage: 'review',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor`, async () => {
await editor.expectEditor.toContain(secondChamferDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: ['length=5,'],
highlightedCode: '',
})
})
await test.step(`Confirm scene has changed`, async () => {
await scene.expectPixelColor(
backgroundColor,
secondEdgeLocation,
lowTolerance
)
})
})
const shellPointAndClickCapCases = [
{ shouldPreselect: true },
{ shouldPreselect: false },

View File

@ -56,10 +56,13 @@ export type ModelingCommandSchema = {
edge: Selections
}
Fillet: {
// todo
selection: Selections
radius: KclCommandValue
}
Chamfer: {
selection: Selections
length: KclCommandValue
}
'Offset plane': {
plane: Selections
distance: KclCommandValue
@ -429,7 +432,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
},
Fillet: {
description: 'Fillet edge',
icon: 'fillet',
icon: 'fillet3d',
status: 'development',
needsReview: true,
args: {
@ -449,6 +452,28 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
},
},
},
Chamfer: {
description: 'Chamfer edge',
icon: 'chamfer3d',
status: 'development',
needsReview: true,
args: {
selection: {
inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: true,
required: true,
skip: false,
warningMessage:
'Chamfers cannot touch other chamfers yet. This is under development.',
},
length: {
inputType: 'kcl',
defaultValue: KCL_DEFAULT_LENGTH,
required: true,
},
},
},
'Constrain length': {
description: 'Constrain the length of one or more segments.',
icon: 'dimension',

View File

@ -173,10 +173,14 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/fillet' }],
},
{
id: 'chamfer',
onClick: () => console.error('Chamfer not yet implemented'),
id: 'chamfer3d',
onClick: ({ commandBarSend }) =>
commandBarSend({
type: 'Find and select command',
data: { name: 'Chamfer', groupId: 'modeling' },
}),
icon: 'chamfer3d',
status: 'kcl-only',
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
title: 'Chamfer',
hotkey: 'C',
description: 'Bevel the edges of a 3D solid.',

View File

@ -52,6 +52,7 @@ import {
} from 'lang/modifyAst'
import {
applyEdgeTreatmentToSelection,
ChamferParameters,
EdgeTreatmentType,
FilletParameters,
} from 'lang/modifyAst/addEdgeTreatment'
@ -272,6 +273,7 @@ export type ModelingMachineEvent =
| { type: 'Shell'; data?: ModelingCommandSchema['Shell'] }
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
| { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
| { type: 'Chamfer'; data?: ModelingCommandSchema['Chamfer'] }
| { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
| { type: 'Text-to-CAD'; data: ModelingCommandSchema['Text-to-CAD'] }
| { type: 'Prompt-to-edit'; data: ModelingCommandSchema['Prompt-to-edit'] }
@ -1737,6 +1739,33 @@ export const modelingMachine = setup({
if (err(filletResult)) return filletResult
}
),
chamferAstMod: fromPromise(
async ({
input,
}: {
input: ModelingCommandSchema['Chamfer'] | undefined
}) => {
if (!input) {
return new Error('No input provided')
}
// Extract inputs
const ast = kclManager.ast
const { selection, length } = input
const parameters: ChamferParameters = {
type: EdgeTreatmentType.Chamfer,
length,
}
// Apply chamfer to selection
const chamferResult = await applyEdgeTreatmentToSelection(
ast,
selection,
parameters
)
if (err(chamferResult)) return chamferResult
}
),
'submit-prompt-edit': fromPromise(
async ({ input }: { input: ModelingCommandSchema['Prompt-to-edit'] }) => {
console.log('doing thing', input)
@ -1821,6 +1850,11 @@ export const modelingMachine = setup({
reenter: true,
},
Chamfer: {
target: 'Applying chamfer',
reenter: true,
},
Export: {
target: 'idle',
reenter: false,
@ -2650,6 +2684,19 @@ export const modelingMachine = setup({
},
},
'Applying chamfer': {
invoke: {
src: 'chamferAstMod',
id: 'chamferAstMod',
input: ({ event }) => {
if (event.type !== 'Chamfer') return undefined
return event.data
},
onDone: ['idle'],
onError: ['idle'],
},
},
'Applying Prompt-to-edit': {
invoke: {
src: 'submit-prompt-edit',