Set apperance in feature tree context menu (#5439)
* Revert "Revert multi-profile (#4812)" This reverts commitefe8089b08. * fix poor 1000ms wait UX * A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) * trigger CI * Add Rust side artifacts for startSketchOn face or plane (#4834) * Add Rust side artifacts for startSketchOn face or plane * move ast digging --------- Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch> * lint * lint * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-macos-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-16-cores) * trigger CI * chore: disabled file watcher which prevents faster file write (#4835) * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * partial fixes * 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 up all the tests * Fix partial execution * wip * WIP * wip * rust changes to make three point confrom to same as others since we're not ready with name params yet * most of the fix for 3 point circle * get overlays working for circle three point * fmt * fix types * cargo fmt * add face codef ref for walls and caps * fix sketch on face after updates to rust side artifact graph * some things needed for multi-profile tests * bad attempts at fixing rust * more * more * fix rust * more rust fixes * overlay fix * remove duplicate test * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * lint and typing * maybe fix a unit test * small thing * WIP: Add Delete right click menu item to Feature Tree Copying code around Fixes #5090 * I don't know why it works * WIP * fix circ dep * fix unit test * fix some tests * 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) * Working deletion machine loo * Working helix deletion * Extend deletion to more things * 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) * fix sweep point-and-click test * fix more tests and add a fix me * fix more tests * fix electron specific test * tsc * more test tweaks * update docs * commint snaps? * is clippy happy now? * clippy again * test works now without me changing anything big-fixed-itself * small bug * make three point have cross hair to make it consistent with othe rtools * fix up state diagram * fmt * add draft point for first click of three point circ * 1 test for three point circle * 2 test for three point circle * clean up * 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) * remove bad doc comment * remove test skip * remove onboarding test changes * Update src/lang/modifyAst.ts Co-authored-by: Jonathan Tran <jonnytran@gmail.com> * Update output from simulation tests * Fix to use correct source ranges This also reduces cloning. * Change back to skipping face cap none and both * Update output after changing back to skipping none and both * Fix clippy warning * fix profile start snap bug * WIP: migrate to actor * add path ids to cap * fix going into edit sketch * make other startSketchOn's work * fix snapshot test * explain function name * Update src/lib/rectangleTool.ts Co-authored-by: Frank Noirot <frank@zoo.dev> * rename error * remove file tree from diff * Update src/clientSideScene/segments.ts Co-authored-by: Frank Noirot <frank@zoo.dev> * nit * Continue actor migration * Prevent double write to KCL code on revolve * Clean up * Update output after adding cap-to-path graph edge * Clean up * Update machine diag * Update context menu hotkey class * Fix edit/select sketch-on-cap via feature tree * clean up for face codeRef * fix changing tools part way through circle/rect tools * fix delete of circle profile * fix close profiles * fix closing profile bug (tangentArcTo being ignored) * remove stale comment * Delete paths associated with sketch when the sketch plane is deleted * Add support for deleting sketches on caps (not walls) * get delet working for walls * make delet of extrusions work for multi profile * A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) * Delete the sketch statement too on the cap and wall cases * Don't write to file in `split-sketch-pipe-if-needed` unless necessary * Don't wait for file write to complete within `updateEditorWithAstAndWriteToFile` It is already debounced internally. If we await it, we will have to wait for a debounced timeout * Fix bad conflict resolution * Fix a few things post merge * Add guard back, fixing tests * 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) * Add e2e test * WIP: Add Set apperance right click menu item to Feature Tree Fixes #5372 * Working cheap implementation * Unset appearance via Default option * More colors * Add basic test * Add test * Lint * 🔪 them timers * Increase color matching threshold on appearance test * Fix colors in e2e * Move Set apperance down in the menu * Revert "Move Set apperance down in the menu" This reverts commiteb1d2e2c1c. * Attempt at fixing dual extrude for role option search in test --------- Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Jonathan Tran <jonnytran@gmail.com> Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com> Co-authored-by: 49lf <ircsurfer33@gmail.com> Co-authored-by: Frank Noirot <frank@zoo.dev> Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
This commit is contained in:
		@ -219,7 +219,11 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  test('Can extrude from the command bar', async ({ page, homePage }) => {
 | 
			
		||||
  test('Can extrude from the command bar', async ({
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    await page.addInitScript(async () => {
 | 
			
		||||
      localStorage.setItem(
 | 
			
		||||
        'persistCode',
 | 
			
		||||
@ -254,7 +258,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
 | 
			
		||||
    await expect(cmdSearchBar).toBeVisible()
 | 
			
		||||
 | 
			
		||||
    // Search for extrude command and choose it
 | 
			
		||||
    await page.getByRole('option', { name: 'Extrude' }).click()
 | 
			
		||||
    await cmdBar.cmdOptions.getByText('Extrude').click()
 | 
			
		||||
 | 
			
		||||
    // Assert that we're on the selection step
 | 
			
		||||
    await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()
 | 
			
		||||
 | 
			
		||||
@ -2796,4 +2796,107 @@ radius = 8.69
 | 
			
		||||
      expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test(`Set appearance`, async ({
 | 
			
		||||
    context,
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
    scene,
 | 
			
		||||
    editor,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    const initialCode = `sketch001 = startSketchOn('XZ')
 | 
			
		||||
profile001 = circle({
 | 
			
		||||
  center = [0, 0],
 | 
			
		||||
  radius = 100
 | 
			
		||||
}, sketch001)
 | 
			
		||||
extrude001 = extrude(profile001, length = 100)
 | 
			
		||||
`
 | 
			
		||||
    await context.addInitScript((initialCode) => {
 | 
			
		||||
      localStorage.setItem('persistCode', initialCode)
 | 
			
		||||
    }, initialCode)
 | 
			
		||||
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.waitForExecutionDone()
 | 
			
		||||
 | 
			
		||||
    // One dumb hardcoded screen pixel value
 | 
			
		||||
    const testPoint = { x: 500, y: 250 }
 | 
			
		||||
    const initialColor: [number, number, number] = [135, 135, 135]
 | 
			
		||||
 | 
			
		||||
    await test.step(`Confirm extrude exists with default appearance`, async () => {
 | 
			
		||||
      await toolbar.closePane('code')
 | 
			
		||||
      await scene.expectPixelColor(initialColor, testPoint, 15)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    async function setApperanceAndCheck(
 | 
			
		||||
      option: string,
 | 
			
		||||
      hex: string,
 | 
			
		||||
      shapeColor: [number, number, number]
 | 
			
		||||
    ) {
 | 
			
		||||
      await toolbar.openPane('feature-tree')
 | 
			
		||||
      const operationButton = await toolbar.getFeatureTreeOperation(
 | 
			
		||||
        'Extrude',
 | 
			
		||||
        0
 | 
			
		||||
      )
 | 
			
		||||
      await operationButton.click({ button: 'right' })
 | 
			
		||||
      const menuButton = page.getByTestId('context-menu-set-appearance')
 | 
			
		||||
      await menuButton.click()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        commandName: 'Appearance',
 | 
			
		||||
        currentArgKey: 'color',
 | 
			
		||||
        currentArgValue: '',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Color: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'color',
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
      })
 | 
			
		||||
      const item = page.getByText(option, { exact: true })
 | 
			
		||||
      await item.click()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        commandName: 'Appearance',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Color: hex,
 | 
			
		||||
        },
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await toolbar.closePane('feature-tree')
 | 
			
		||||
      await scene.expectPixelColor(shapeColor, testPoint, 40)
 | 
			
		||||
      await toolbar.openPane('code')
 | 
			
		||||
      if (hex === 'default') {
 | 
			
		||||
        const anyAppearanceDeclaration = `|> appearance(`
 | 
			
		||||
        await editor.expectEditor.not.toContain(anyAppearanceDeclaration)
 | 
			
		||||
      } else {
 | 
			
		||||
        const declaration = `|> appearance(%, color = '${hex}')`
 | 
			
		||||
        await editor.expectEditor.toContain(declaration)
 | 
			
		||||
        // TODO: fix selection range after appearance update
 | 
			
		||||
        // await editor.expectState({
 | 
			
		||||
        //   diagnostics: [],
 | 
			
		||||
        //   activeLines: [declaration],
 | 
			
		||||
        //   highlightedCode: '',
 | 
			
		||||
        // })
 | 
			
		||||
      }
 | 
			
		||||
      await toolbar.closePane('code')
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await test.step(`Go through the Set Appearance flow for all options`, async () => {
 | 
			
		||||
      await setApperanceAndCheck('Red', '#FF0000', [180, 0, 0])
 | 
			
		||||
      await setApperanceAndCheck('Green', '#00FF00', [0, 180, 0])
 | 
			
		||||
      await setApperanceAndCheck('Blue', '#0000FF', [0, 0, 180])
 | 
			
		||||
      await setApperanceAndCheck('Turquoise', '#00FFFF', [0, 180, 180])
 | 
			
		||||
      await setApperanceAndCheck('Purple', '#FF00FF', [180, 0, 180])
 | 
			
		||||
      await setApperanceAndCheck('Yellow', '#FFFF00', [180, 180, 0])
 | 
			
		||||
      await setApperanceAndCheck('Black', '#000000', [0, 0, 0])
 | 
			
		||||
      await setApperanceAndCheck('Dark Grey', '#080808', [10, 10, 10])
 | 
			
		||||
      await setApperanceAndCheck('Light Grey', '#D3D3D3', [190, 190, 190])
 | 
			
		||||
      await setApperanceAndCheck('White', '#FFFFFF', [200, 200, 200])
 | 
			
		||||
      await setApperanceAndCheck(
 | 
			
		||||
        'Default (clear appearance)',
 | 
			
		||||
        'default',
 | 
			
		||||
        initialColor
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB  | 
@ -324,6 +324,18 @@ const OperationItem = (props: {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function enterAppearanceFlow() {
 | 
			
		||||
    if (props.item.type === 'StdLibCall') {
 | 
			
		||||
      props.send({
 | 
			
		||||
        type: 'enterAppearanceFlow',
 | 
			
		||||
        data: {
 | 
			
		||||
          targetSourceRange: sourceRangeFromRust(props.item.sourceRange),
 | 
			
		||||
          currentOperation: props.item,
 | 
			
		||||
        },
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function deleteOperation() {
 | 
			
		||||
    if (
 | 
			
		||||
      props.item.type === 'StdLibCall' ||
 | 
			
		||||
@ -380,6 +392,13 @@ const OperationItem = (props: {
 | 
			
		||||
        : []),
 | 
			
		||||
      ...(props.item.type === 'StdLibCall'
 | 
			
		||||
        ? [
 | 
			
		||||
            <ContextMenuItem
 | 
			
		||||
              disabled={!stdLibMap[props.item.name]?.supportsAppearance}
 | 
			
		||||
              onClick={enterAppearanceFlow}
 | 
			
		||||
              data-testid="context-menu-set-appearance"
 | 
			
		||||
            >
 | 
			
		||||
              Set appearance
 | 
			
		||||
            </ContextMenuItem>,
 | 
			
		||||
            <ContextMenuItem
 | 
			
		||||
              disabled={!stdLibMap[props.item.name]?.prepareToEdit}
 | 
			
		||||
              onClick={enterEditFlow}
 | 
			
		||||
 | 
			
		||||
@ -397,10 +397,10 @@ export function getEdgeTagCall(
 | 
			
		||||
  return tagCall
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function locateExtrudeDeclarator(
 | 
			
		||||
export function locateExtrudeDeclarator(
 | 
			
		||||
  node: Program,
 | 
			
		||||
  pathToExtrudeNode: PathToNode
 | 
			
		||||
): { extrudeDeclarator: VariableDeclarator } | Error {
 | 
			
		||||
): { extrudeDeclarator: VariableDeclarator; shallowPath: PathToNode } | Error {
 | 
			
		||||
  const nodeOfExtrudeCall = getNodeFromPath<VariableDeclaration>(
 | 
			
		||||
    node,
 | 
			
		||||
    pathToExtrudeNode,
 | 
			
		||||
@ -427,7 +427,7 @@ function locateExtrudeDeclarator(
 | 
			
		||||
    return new Error('Extrude must be a PipeExpression or CallExpression')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return { extrudeDeclarator }
 | 
			
		||||
  return { extrudeDeclarator, shallowPath: nodeOfExtrudeCall.shallowPath }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getPathToNodeOfEdgeTreatmentLiteral(
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										70
									
								
								src/lang/modifyAst/setAppearance.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/lang/modifyAst/setAppearance.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
			
		||||
import { PathToNode, Program } from 'lang/wasm'
 | 
			
		||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
 | 
			
		||||
import { locateExtrudeDeclarator } from './addEdgeTreatment'
 | 
			
		||||
import { err } from 'lib/trap'
 | 
			
		||||
import {
 | 
			
		||||
  createCallExpressionStdLibKw,
 | 
			
		||||
  createLabeledArg,
 | 
			
		||||
  createLiteral,
 | 
			
		||||
  createPipeExpression,
 | 
			
		||||
} from 'lang/modifyAst'
 | 
			
		||||
import { createPipeSubstitution } from 'lang/modifyAst'
 | 
			
		||||
import { COMMAND_APPEARANCE_COLOR_DEFAULT } from 'lib/commandBarConfigs/modelingCommandConfig'
 | 
			
		||||
 | 
			
		||||
export function setAppearance({
 | 
			
		||||
  ast,
 | 
			
		||||
  nodeToEdit,
 | 
			
		||||
  color,
 | 
			
		||||
}: {
 | 
			
		||||
  ast: Node<Program>
 | 
			
		||||
  nodeToEdit: PathToNode
 | 
			
		||||
  color: string
 | 
			
		||||
}): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } {
 | 
			
		||||
  const modifiedAst = structuredClone(ast)
 | 
			
		||||
 | 
			
		||||
  // Locate the call (not necessarily an extrude here)
 | 
			
		||||
  const result = locateExtrudeDeclarator(modifiedAst, nodeToEdit)
 | 
			
		||||
  if (err(result)) {
 | 
			
		||||
    return result
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const declarator = result.extrudeDeclarator
 | 
			
		||||
  const call = createCallExpressionStdLibKw(
 | 
			
		||||
    'appearance',
 | 
			
		||||
    createPipeSubstitution(),
 | 
			
		||||
    [createLabeledArg('color', createLiteral(color))]
 | 
			
		||||
  )
 | 
			
		||||
  // Modify the expression
 | 
			
		||||
  if (
 | 
			
		||||
    declarator.init.type === 'CallExpression' ||
 | 
			
		||||
    declarator.init.type === 'CallExpressionKw'
 | 
			
		||||
  ) {
 | 
			
		||||
    // 1. case when no appearance exists, mutate in place
 | 
			
		||||
    declarator.init = createPipeExpression([declarator.init, call])
 | 
			
		||||
  } else if (declarator.init.type === 'PipeExpression') {
 | 
			
		||||
    // 2. case when appearance exists or extrude in sketch pipe
 | 
			
		||||
    const existingIndex = declarator.init.body.findIndex(
 | 
			
		||||
      (v) =>
 | 
			
		||||
        v.type === 'CallExpressionKw' &&
 | 
			
		||||
        v.callee.type === 'Identifier' &&
 | 
			
		||||
        v.callee.name === 'appearance'
 | 
			
		||||
    )
 | 
			
		||||
    if (existingIndex > -1) {
 | 
			
		||||
      if (color === COMMAND_APPEARANCE_COLOR_DEFAULT) {
 | 
			
		||||
        // Special case of unsetting the appearance aka deleting the node
 | 
			
		||||
        declarator.init.body.splice(existingIndex, 1)
 | 
			
		||||
      } else {
 | 
			
		||||
        declarator.init.body[existingIndex] = call
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      declarator.init.body.push(call)
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    return new Error('Unsupported operation type.')
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    modifiedAst,
 | 
			
		||||
    pathToNode: result.shallowPath,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -3,7 +3,11 @@ import { angleLengthInfo } from 'components/Toolbar/setAngleLength'
 | 
			
		||||
import { transformAstSketchLines } from 'lang/std/sketchcombos'
 | 
			
		||||
import { PathToNode } from 'lang/wasm'
 | 
			
		||||
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
 | 
			
		||||
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants'
 | 
			
		||||
import {
 | 
			
		||||
  KCL_DEFAULT_LENGTH,
 | 
			
		||||
  KCL_DEFAULT_DEGREE,
 | 
			
		||||
  KCL_DEFAULT_COLOR,
 | 
			
		||||
} from 'lib/constants'
 | 
			
		||||
import { components } from 'lib/machine-api'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { kclManager } from 'lib/singletons'
 | 
			
		||||
@ -28,6 +32,8 @@ export const EXTRUSION_RESULTS = [
 | 
			
		||||
  'intersect',
 | 
			
		||||
] as const
 | 
			
		||||
 | 
			
		||||
export const COMMAND_APPEARANCE_COLOR_DEFAULT = 'default'
 | 
			
		||||
 | 
			
		||||
export type ModelingCommandSchema = {
 | 
			
		||||
  'Enter sketch': {}
 | 
			
		||||
  Export: {
 | 
			
		||||
@ -107,6 +113,10 @@ export type ModelingCommandSchema = {
 | 
			
		||||
    selection: Selections
 | 
			
		||||
  }
 | 
			
		||||
  'Delete selection': {}
 | 
			
		||||
  Appearance: {
 | 
			
		||||
    nodeToEdit?: PathToNode
 | 
			
		||||
    color: string
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
 | 
			
		||||
@ -664,4 +674,40 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  Appearance: {
 | 
			
		||||
    description:
 | 
			
		||||
      'Set the appearance of a solid. This only works on solids, not sketches or individual paths.',
 | 
			
		||||
    icon: 'extrude',
 | 
			
		||||
    needsReview: true,
 | 
			
		||||
    args: {
 | 
			
		||||
      nodeToEdit: {
 | 
			
		||||
        description:
 | 
			
		||||
          'Path to the node in the AST to edit. Never shown to the user.',
 | 
			
		||||
        skip: true,
 | 
			
		||||
        inputType: 'text',
 | 
			
		||||
        required: false,
 | 
			
		||||
      },
 | 
			
		||||
      color: {
 | 
			
		||||
        inputType: 'options',
 | 
			
		||||
        required: true,
 | 
			
		||||
        options: [
 | 
			
		||||
          { name: 'Red', value: '#FF0000' },
 | 
			
		||||
          { name: 'Green', value: '#00FF00' },
 | 
			
		||||
          { name: 'Blue', value: '#0000FF' },
 | 
			
		||||
          { name: 'Turquoise', value: '#00FFFF' },
 | 
			
		||||
          { name: 'Purple', value: '#FF00FF' },
 | 
			
		||||
          { name: 'Yellow', value: '#FFFF00' },
 | 
			
		||||
          { name: 'Black', value: '#000000' },
 | 
			
		||||
          { name: 'Dark Grey', value: '#080808' },
 | 
			
		||||
          { name: 'Light Grey', value: '#D3D3D3' },
 | 
			
		||||
          { name: 'White', value: '#FFFFFF' },
 | 
			
		||||
          {
 | 
			
		||||
            name: 'Default (clear appearance)',
 | 
			
		||||
            value: COMMAND_APPEARANCE_COLOR_DEFAULT,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
      // Add more fields
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -66,6 +66,9 @@ export const KCL_DEFAULT_LENGTH = `5`
 | 
			
		||||
/** The default KCL degree expression */
 | 
			
		||||
export const KCL_DEFAULT_DEGREE = `360`
 | 
			
		||||
 | 
			
		||||
/** The default KCL color expression */
 | 
			
		||||
export const KCL_DEFAULT_COLOR = `#3c73ff`
 | 
			
		||||
 | 
			
		||||
/** localStorage key for the playwright test-specific app settings file */
 | 
			
		||||
export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,7 @@ interface StdLibCallInfo {
 | 
			
		||||
    | ExecuteCommandEventPayload
 | 
			
		||||
    | PrepareToEditCallback
 | 
			
		||||
    | PrepareToEditFailurePayload
 | 
			
		||||
  supportsAppearance?: boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -204,6 +205,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
 | 
			
		||||
    label: 'Extrude',
 | 
			
		||||
    icon: 'extrude',
 | 
			
		||||
    prepareToEdit: prepareToEditExtrude,
 | 
			
		||||
    supportsAppearance: true,
 | 
			
		||||
  },
 | 
			
		||||
  fillet: {
 | 
			
		||||
    label: 'Fillet',
 | 
			
		||||
@ -228,6 +230,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
 | 
			
		||||
  loft: {
 | 
			
		||||
    label: 'Loft',
 | 
			
		||||
    icon: 'loft',
 | 
			
		||||
    supportsAppearance: true,
 | 
			
		||||
  },
 | 
			
		||||
  offsetPlane: {
 | 
			
		||||
    label: 'Offset Plane',
 | 
			
		||||
@ -253,10 +256,12 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
 | 
			
		||||
  revolve: {
 | 
			
		||||
    label: 'Revolve',
 | 
			
		||||
    icon: 'revolve',
 | 
			
		||||
    supportsAppearance: true,
 | 
			
		||||
  },
 | 
			
		||||
  shell: {
 | 
			
		||||
    label: 'Shell',
 | 
			
		||||
    icon: 'shell',
 | 
			
		||||
    supportsAppearance: true,
 | 
			
		||||
  },
 | 
			
		||||
  startSketchOn: {
 | 
			
		||||
    label: 'Sketch',
 | 
			
		||||
@ -280,6 +285,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
 | 
			
		||||
  sweep: {
 | 
			
		||||
    label: 'Sweep',
 | 
			
		||||
    icon: 'sweep',
 | 
			
		||||
    supportsAppearance: true,
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -432,3 +438,37 @@ export async function enterEditFlow({
 | 
			
		||||
    'Feature tree editing not yet supported for this operation. Please edit in the code editor.'
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function enterAppearanceFlow({
 | 
			
		||||
  operation,
 | 
			
		||||
  artifact,
 | 
			
		||||
}: EnterEditFlowProps): Promise<Error | CommandBarMachineEvent> {
 | 
			
		||||
  if (operation.type !== 'StdLibCall') {
 | 
			
		||||
    return new Error(
 | 
			
		||||
      'Appearance setting not yet supported for user-defined functions. Please edit in the code editor.'
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
  const stdLibInfo = stdLibMap[operation.name]
 | 
			
		||||
 | 
			
		||||
  if (stdLibInfo && stdLibInfo.supportsAppearance) {
 | 
			
		||||
    const argDefaultValues = {
 | 
			
		||||
      nodeToEdit: getNodePathFromSourceRange(
 | 
			
		||||
        kclManager.ast,
 | 
			
		||||
        sourceRangeFromRust(operation.sourceRange)
 | 
			
		||||
      ),
 | 
			
		||||
    }
 | 
			
		||||
    console.log('argDefaultValues', argDefaultValues)
 | 
			
		||||
    return {
 | 
			
		||||
      type: 'Find and select command',
 | 
			
		||||
      data: {
 | 
			
		||||
        name: 'Appearance',
 | 
			
		||||
        groupId: 'modeling',
 | 
			
		||||
        argDefaultValues,
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return new Error(
 | 
			
		||||
    'Appearance setting not yet supported for this operation. Please edit in the code editor.'
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -88,6 +88,7 @@ import {
 | 
			
		||||
import { getPathsFromPlaneArtifact } from 'lang/std/artifactGraph'
 | 
			
		||||
import { createProfileStartHandle } from 'clientSideScene/segments'
 | 
			
		||||
import { DRAFT_POINT } from 'clientSideScene/sceneInfra'
 | 
			
		||||
import { setAppearance } from 'lang/modifyAst/setAppearance'
 | 
			
		||||
 | 
			
		||||
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
 | 
			
		||||
 | 
			
		||||
@ -314,6 +315,7 @@ export type ModelingMachineEvent =
 | 
			
		||||
      type: 'Delete selection'
 | 
			
		||||
      data: ModelingCommandSchema['Delete selection']
 | 
			
		||||
    }
 | 
			
		||||
  | { type: 'Appearance'; data: ModelingCommandSchema['Appearance'] }
 | 
			
		||||
  | {
 | 
			
		||||
      type: 'Add rectangle origin'
 | 
			
		||||
      data: [x: number, y: number]
 | 
			
		||||
@ -2172,6 +2174,47 @@ export const modelingMachine = setup({
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
    ),
 | 
			
		||||
    appearanceAstMod: fromPromise(
 | 
			
		||||
      async ({
 | 
			
		||||
        input,
 | 
			
		||||
      }: {
 | 
			
		||||
        input: ModelingCommandSchema['Appearance'] | undefined
 | 
			
		||||
      }) => {
 | 
			
		||||
        if (!input) return new Error('No input provided')
 | 
			
		||||
        // Extract inputs
 | 
			
		||||
        const ast = kclManager.ast
 | 
			
		||||
        const { color, nodeToEdit } = input
 | 
			
		||||
        if (!(nodeToEdit && typeof nodeToEdit[1][0] === 'number')) {
 | 
			
		||||
          return new Error('Appearance is only an edit flow')
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const result = setAppearance({
 | 
			
		||||
          ast,
 | 
			
		||||
          nodeToEdit,
 | 
			
		||||
          color,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        if (err(result)) {
 | 
			
		||||
          return err(result)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const updateAstResult = await kclManager.updateAst(
 | 
			
		||||
          result.modifiedAst,
 | 
			
		||||
          true,
 | 
			
		||||
          {
 | 
			
		||||
            focusPath: [result.pathToNode],
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        await codeManager.updateEditorWithAstAndWriteToFile(
 | 
			
		||||
          updateAstResult.newAst
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if (updateAstResult?.selections) {
 | 
			
		||||
          editorManager.selectRange(updateAstResult?.selections)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
  // end actors
 | 
			
		||||
}).createMachine({
 | 
			
		||||
@ -2267,6 +2310,11 @@ export const modelingMachine = setup({
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        'Prompt-to-edit': 'Applying Prompt-to-edit',
 | 
			
		||||
 | 
			
		||||
        Appearance: {
 | 
			
		||||
          target: 'Applying appearance',
 | 
			
		||||
          reenter: true,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      entry: 'reset client scene mouse handlers',
 | 
			
		||||
@ -3389,6 +3437,19 @@ export const modelingMachine = setup({
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    'Applying appearance': {
 | 
			
		||||
      invoke: {
 | 
			
		||||
        src: 'appearanceAstMod',
 | 
			
		||||
        id: 'appearanceAstMod',
 | 
			
		||||
        input: ({ event }) => {
 | 
			
		||||
          if (event.type !== 'Appearance') return undefined
 | 
			
		||||
          return event.data
 | 
			
		||||
        },
 | 
			
		||||
        onDone: ['idle'],
 | 
			
		||||
        onError: ['idle'],
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  initial: 'idle',
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user