Compare commits
32 Commits
Author | SHA1 | Date | |
---|---|---|---|
cba953c245 | |||
54ca6ea0b2 | |||
6a01608c3a | |||
530f15e04a | |||
725e59d987 | |||
54313c9b03 | |||
890d96496c | |||
35999366a7 | |||
2affc7271d | |||
d30fbf8b4b | |||
3f7e776464 | |||
79cff57f43 | |||
1cd2cd82b2 | |||
60e187bd3e | |||
c64175425b | |||
36464e6984 | |||
2f0002e53c | |||
482833c88f | |||
d9d0a72306 | |||
65cd9fab64 | |||
5e41e382ce | |||
1e3cb00092 | |||
d1a2bd01ca | |||
aca13d087b | |||
fcdde3e482 | |||
a1df3d0ffc | |||
1852e6167b | |||
29bf77bb82 | |||
e81b614523 | |||
5a5fe3bb95 | |||
0710f6e5f2 | |||
c9d5633647 |
37
.github/ISSUE_TEMPLATE/cryptic_error.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
name: Cryptic KCL Error
|
||||||
|
description: File a bug report for source code that produces a confusing error
|
||||||
|
title: "[CRYPTIC]: "
|
||||||
|
labels: ["cryptic-error"]
|
||||||
|
assignees: []
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: "Thank you for taking the time to report a confusing error. Please provide as much information as possible to help us resolve it."
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: kcl
|
||||||
|
attributes:
|
||||||
|
label: Paste minimal KCL source that produces a cryptic error
|
||||||
|
description: Minimal KCL reproducer that produces a cryptic error
|
||||||
|
placeholder: "const ..."
|
||||||
|
render: javascript
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: expected-behavior
|
||||||
|
attributes:
|
||||||
|
label: Expected Behavior
|
||||||
|
description: Description of what you expected to happen (if you know).
|
||||||
|
placeholder: "I expected that..."
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
|
attributes:
|
||||||
|
label: Additional Context
|
||||||
|
description: Add any other context about the problem here.
|
||||||
|
placeholder: "Anything else you want to add..."
|
||||||
|
validations:
|
||||||
|
required: false
|
38
README.md
@ -124,20 +124,40 @@ Before you submit a contribution PR to this repo, please ensure that:
|
|||||||
|
|
||||||
## Release a new version
|
## Release a new version
|
||||||
|
|
||||||
1. Bump the versions by running `./make-realease.sh` while on a fresh pull of main
|
#### 1. Bump the versions by running `./make-release.sh` and create a Cut Release PR
|
||||||
|
|
||||||
That will create the branch with the updated json files for you.
|
That will create the branch with the updated json files for you:
|
||||||
run `./make-release.sh` for a patch update
|
- run `./make-release.sh` or `./make-release.sh patch` for a patch update;
|
||||||
run `./make-release.sh "minor"` for minor
|
- run `./make-release.sh minor` for minor; or
|
||||||
run `./make-release.sh "major"` for major
|
- run `./make-release.sh major` for major.
|
||||||
|
|
||||||
After it runs you should just need to push the push the branch and open a PR (it will suggest a changelog for you too, delete any that are not user facing)
|
After it runs you should just need the push the branch and open a PR.
|
||||||
|
|
||||||
The PR may serve as a place to discuss the human-readable changelog and extra QA.
|
**Important:** It needs to be prefixed with `Cut release v` to build in release mode and a few other things to test in the best context possible, the intent would be for instance to have `Cut release v1.2.3` for the `v1.2.3` release candidate.
|
||||||
|
|
||||||
2. Merge the PR
|
The PR may then serve as a place to discuss the human-readable changelog and extra QA. The `make-release.sh` tool suggests a changelog for you too to be used as PR description, just make sure to delete lines that are not user facing.
|
||||||
|
|
||||||
|
#### 2. Smoke test artifacts from the Cut Release PR
|
||||||
|
|
||||||
|
The release builds can be find under the `artifact` zip, at the very bottom of the `ci` action page for each commit on this branch.
|
||||||
|
|
||||||
|
We don't have a strict process, but click around and check for anything obvious, posting results as comments in the Cut Release PR.
|
||||||
|
|
||||||
|
The other `ci` output in Cut Release PRs is `updater-test`, because we don't have a way to test this fully automated, we have a semi-automated process. Download updater-test zip file, install the app, run it, expect an updater prompt to a dummy v0.99.99, install it and check that the app comes back at that version (on both macOS and Windows).
|
||||||
|
|
||||||
|
#### 3. Merge the Cut Release PR
|
||||||
|
|
||||||
|
This will kick the `create-release` action, that creates a _Draft_ release out of this Cut Release PR merge after less than a minute, with the new version as title and Cut Release PR as description.
|
||||||
|
|
||||||
|
|
||||||
|
#### 4. Publish the release
|
||||||
|
|
||||||
|
Head over to https://github.com/KittyCAD/modeling-app/releases, the draft release corresponding to the merged Cut Release PR should show up at the top as _Draft_. Click on it, verify the content, and hit _Publish_.
|
||||||
|
|
||||||
|
#### 5. Profit
|
||||||
|
|
||||||
|
A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, which can be found under `release` event filter.
|
||||||
|
|
||||||
3. Profit (A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions if the PR was correctly named)
|
|
||||||
|
|
||||||
## Fuzzing the parser
|
## Fuzzing the parser
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 249 KiB After Width: | Height: | Size: 249 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 171 KiB |
@ -3099,6 +3099,49 @@ const sketch002 = startSketchOn(extrude001, $seg01)
|
|||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Fillet button states test', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-5, -5], %)
|
||||||
|
|> line([0, 10], %)
|
||||||
|
|> line([10, 0], %)
|
||||||
|
|> line([0, -10], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
const selectSegment = () => page.getByText(`line([10, 0], %)`).click()
|
||||||
|
const selectClose = () => page.getByText(`close(%)`).click()
|
||||||
|
const clickEmpty = () => page.mouse.click(950, 100)
|
||||||
|
|
||||||
|
// expect fillet button without any bodies in the scene
|
||||||
|
await selectSegment()
|
||||||
|
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
||||||
|
await clickEmpty()
|
||||||
|
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
||||||
|
|
||||||
|
// test fillet button with the body in the scene
|
||||||
|
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||||
|
const extrude001 = extrude(10, sketch001)`
|
||||||
|
await u.codeLocator.fill(codeToAdd)
|
||||||
|
await selectSegment()
|
||||||
|
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||||
|
await selectClose()
|
||||||
|
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
||||||
|
await clickEmpty()
|
||||||
|
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||||
|
})
|
||||||
|
|
||||||
const removeAfterFirstParenthesis = (inputString: string) => {
|
const removeAfterFirstParenthesis = (inputString: string) => {
|
||||||
const index = inputString.indexOf('(')
|
const index = inputString.indexOf('(')
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
@ -3500,11 +3543,62 @@ test.describe('Command bar tests', () => {
|
|||||||
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
|
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
test('Command bar works and can change a setting', async ({ page }) => {
|
|
||||||
|
test('Fillet from command bar', async ({ page }) => {
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-5, -5], %)
|
||||||
|
|> line([0, 10], %)
|
||||||
|
|> line([10, 0], %)
|
||||||
|
|> line([0, -10], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(-10, sketch001)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
const selectSegment = () => page.getByText(`line([0, -10], %)`).click()
|
||||||
|
|
||||||
|
await selectSegment()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.getByRole('button', { name: 'Fillet' }).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.locator('.cm-activeLine')).toContainText(
|
||||||
|
`fillet({ radius: ${KCL_DEFAULT_LENGTH}, tags: [seg01] }, %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
|
const themeOption = page.getByRole('option', {
|
||||||
|
name: 'theme',
|
||||||
|
exact: false,
|
||||||
|
})
|
||||||
|
const commandLevelArgButton = page.getByRole('button', { name: 'level' })
|
||||||
|
const commandThemeArgButton = page.getByRole('button', { name: 'value' })
|
||||||
|
// This selector changes after we set the setting
|
||||||
|
let commandOptionInput = page.getByPlaceholder('Select an option')
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
@ -3515,23 +3609,17 @@ test.describe('Command bar tests', () => {
|
|||||||
.or(page.getByRole('button', { name: '⌘K' }))
|
.or(page.getByRole('button', { name: '⌘K' }))
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
await page.keyboard.press('Escape')
|
await page.keyboard.press('Escape')
|
||||||
cmdSearchBar = page.getByPlaceholder('Search commands')
|
|
||||||
await expect(cmdSearchBar).not.toBeVisible()
|
await expect(cmdSearchBar).not.toBeVisible()
|
||||||
|
|
||||||
// Now try the same, but with the keyboard shortcut, check focus
|
// Now try the same, but with the keyboard shortcut, check focus
|
||||||
await page.keyboard.press('Meta+K')
|
await page.keyboard.press('Meta+K')
|
||||||
cmdSearchBar = page.getByPlaceholder('Search commands')
|
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
await expect(cmdSearchBar).toBeFocused()
|
await expect(cmdSearchBar).toBeFocused()
|
||||||
|
|
||||||
// Try typing in the command bar
|
// Try typing in the command bar
|
||||||
await page.keyboard.type('theme')
|
await cmdSearchBar.fill('theme')
|
||||||
const themeOption = page.getByRole('option', {
|
|
||||||
name: 'Settings · app · theme',
|
|
||||||
})
|
|
||||||
await expect(themeOption).toBeVisible()
|
await expect(themeOption).toBeVisible()
|
||||||
await themeOption.click()
|
await themeOption.click()
|
||||||
const themeInput = page.getByPlaceholder('Select an option')
|
const themeInput = page.getByPlaceholder('Select an option')
|
||||||
@ -3553,6 +3641,24 @@ test.describe('Command bar tests', () => {
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
// Check that the theme changed
|
// Check that the theme changed
|
||||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||||
|
|
||||||
|
commandOptionInput = page.getByPlaceholder('system')
|
||||||
|
|
||||||
|
// Test case for https://github.com/KittyCAD/modeling-app/issues/2882
|
||||||
|
await commandBarButton.click()
|
||||||
|
await cmdSearchBar.focus()
|
||||||
|
await cmdSearchBar.fill('theme')
|
||||||
|
await themeOption.click()
|
||||||
|
await expect(commandThemeArgButton).toBeDisabled()
|
||||||
|
await commandOptionInput.focus()
|
||||||
|
await commandOptionInput.fill('lig')
|
||||||
|
await commandLevelArgButton.click()
|
||||||
|
await expect(commandLevelArgButton).toBeDisabled()
|
||||||
|
|
||||||
|
// Test case for https://github.com/KittyCAD/modeling-app/issues/2881
|
||||||
|
await commandThemeArgButton.click()
|
||||||
|
await expect(commandThemeArgButton).toBeDisabled()
|
||||||
|
await expect(commandLevelArgButton).toHaveText('level: project')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Command bar keybinding works from code editor and can change a setting', async ({
|
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||||
@ -3577,7 +3683,7 @@ test.describe('Command bar tests', () => {
|
|||||||
await expect(cmdSearchBar).toBeFocused()
|
await expect(cmdSearchBar).toBeFocused()
|
||||||
|
|
||||||
// Try typing in the command bar
|
// Try typing in the command bar
|
||||||
await page.keyboard.type('theme')
|
await cmdSearchBar.fill('theme')
|
||||||
const themeOption = page.getByRole('option', {
|
const themeOption = page.getByRole('option', {
|
||||||
name: 'Settings · app · theme',
|
name: 'Settings · app · theme',
|
||||||
})
|
})
|
||||||
@ -3648,7 +3754,9 @@ test.describe('Command bar tests', () => {
|
|||||||
await page.mouse.click(700, 200)
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
// Assert that we're on the distance step
|
// Assert that we're on the distance step
|
||||||
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'distance', exact: false })
|
||||||
|
).toBeDisabled()
|
||||||
|
|
||||||
// Assert that the an alternative variable name is chosen,
|
// Assert that the an alternative variable name is chosen,
|
||||||
// since the default variable name is already in use (distance)
|
// since the default variable name is already in use (distance)
|
||||||
@ -3663,11 +3771,12 @@ test.describe('Command bar tests', () => {
|
|||||||
|
|
||||||
// Review step and argument hotkeys
|
// Review step and argument hotkeys
|
||||||
await expect(submitButton).toBeEnabled()
|
await expect(submitButton).toBeEnabled()
|
||||||
await page.keyboard.press('Backspace')
|
await expect(submitButton).toBeFocused()
|
||||||
|
await submitButton.press('Backspace')
|
||||||
|
|
||||||
// Assert we're back on the distance step
|
// Assert we're back on the distance step
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Distance 5', exact: false })
|
page.getByRole('button', { name: 'distance', exact: false })
|
||||||
).toBeDisabled()
|
).toBeDisabled()
|
||||||
|
|
||||||
await continueButton.click()
|
await continueButton.click()
|
||||||
@ -3691,6 +3800,47 @@ const extrude001 = extrude(distance001, sketch001)`.replace(
|
|||||||
) // remove newlines
|
) // remove newlines
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Can switch between sketch tools via command bar', async ({ page }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
|
||||||
|
const cmdBarButton = page.getByRole('button', { name: 'Commands' })
|
||||||
|
const rectangleToolCommand = page.getByRole('option', {
|
||||||
|
name: 'Rectangle',
|
||||||
|
})
|
||||||
|
const rectangleToolButton = page.getByRole('button', { name: 'Rectangle' })
|
||||||
|
const lineToolCommand = page.getByRole('option', { name: 'Line' })
|
||||||
|
const lineToolButton = page.getByRole('button', { name: 'Line' })
|
||||||
|
const arcToolCommand = page.getByRole('option', { name: 'Tangential Arc' })
|
||||||
|
const arcToolButton = page.getByRole('button', { name: 'Tangential Arc' })
|
||||||
|
|
||||||
|
// Start a sketch
|
||||||
|
await sketchButton.click()
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
|
||||||
|
// Switch between sketch tools via the command bar
|
||||||
|
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
await cmdBarButton.click()
|
||||||
|
await rectangleToolCommand.click()
|
||||||
|
await expect(rectangleToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
await cmdBarButton.click()
|
||||||
|
await lineToolCommand.click()
|
||||||
|
await expect(lineToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
|
||||||
|
// Click in the scene a couple times to draw a line
|
||||||
|
// so tangential arc is valid
|
||||||
|
await page.mouse.click(700, 200)
|
||||||
|
await page.mouse.move(700, 300, { steps: 5 })
|
||||||
|
await page.mouse.click(700, 300)
|
||||||
|
|
||||||
|
// switch to tangential arc via command bar
|
||||||
|
await cmdBarButton.click()
|
||||||
|
await arcToolCommand.click()
|
||||||
|
await expect(arcToolButton).toHaveAttribute('aria-pressed', 'true')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Regression tests', () => {
|
test.describe('Regression tests', () => {
|
||||||
@ -4642,10 +4792,10 @@ test.describe('Sketch tests', () => {
|
|||||||
// click extrude
|
// click extrude
|
||||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
await page.getByRole('button', { name: 'Extrude' }).click()
|
||||||
|
|
||||||
// sketch selection should already have been made. "Selection 1 face" only show up when the selection has been made already
|
// sketch selection should already have been made. "Selection: 1 face" only show up when the selection has been made already
|
||||||
// otherwise the cmdbar would be waiting for a selection.
|
// otherwise the cmdbar would be waiting for a selection.
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Selection 1 face' })
|
page.getByRole('button', { name: 'selection : 1 face', exact: false })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
test("Existing sketch with bad code delete user's code", async ({ page }) => {
|
test("Existing sketch with bad code delete user's code", async ({ page }) => {
|
||||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.24.1",
|
"version": "0.24.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.17.0",
|
"@codemirror/autocomplete": "^6.17.0",
|
||||||
|
@ -30,7 +30,7 @@ import { URI } from 'vscode-uri'
|
|||||||
import { LanguageServerClient } from '../client'
|
import { LanguageServerClient } from '../client'
|
||||||
import { CompletionItemKindMap } from './autocomplete'
|
import { CompletionItemKindMap } from './autocomplete'
|
||||||
import { addToken, SemanticToken } from './semantic-tokens'
|
import { addToken, SemanticToken } from './semantic-tokens'
|
||||||
import { deferExecution, posToOffset, formatMarkdownContents } from './util'
|
import { posToOffset, formatMarkdownContents } from './util'
|
||||||
import lspAutocompleteExt from './autocomplete'
|
import lspAutocompleteExt from './autocomplete'
|
||||||
import lspHoverExt from './hover'
|
import lspHoverExt from './hover'
|
||||||
import lspFormatExt from './format'
|
import lspFormatExt from './format'
|
||||||
|
@ -80,5 +80,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"version": "0.24.1"
|
"version": "0.24.3"
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ export function App() {
|
|||||||
}, [projectName, projectPath])
|
}, [projectName, projectPath])
|
||||||
|
|
||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
const { context } = useModelingContext()
|
const { context, state } = useModelingContext()
|
||||||
|
|
||||||
const { auth, settings } = useSettingsAuthContext()
|
const { auth, settings } = useSettingsAuthContext()
|
||||||
const token = auth?.context?.token
|
const token = auth?.context?.token
|
||||||
@ -57,7 +57,6 @@ export function App() {
|
|||||||
const {
|
const {
|
||||||
app: { onboardingStatus },
|
app: { onboardingStatus },
|
||||||
} = settings.context
|
} = settings.context
|
||||||
const { state } = useModelingContext()
|
|
||||||
|
|
||||||
useHotkeys('backspace', (e) => {
|
useHotkeys('backspace', (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
canRectangleTool,
|
canRectangleTool,
|
||||||
isEditingExistingSketch,
|
isEditingExistingSketch,
|
||||||
} from 'machines/modelingMachine'
|
} from 'machines/modelingMachine'
|
||||||
|
import { DEV } from 'env'
|
||||||
|
|
||||||
export function Toolbar({
|
export function Toolbar({
|
||||||
className = '',
|
className = '',
|
||||||
@ -60,7 +61,7 @@ export function Toolbar({
|
|||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'line',
|
data: { tool: 'line' },
|
||||||
}),
|
}),
|
||||||
{ enabled: !disableLineButton, scopes: ['sketch'] }
|
{ enabled: !disableLineButton, scopes: ['sketch'] }
|
||||||
)
|
)
|
||||||
@ -75,7 +76,7 @@ export function Toolbar({
|
|||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'tangentialArc',
|
data: { tool: 'tangentialArc' },
|
||||||
}),
|
}),
|
||||||
{ enabled: !disableTangentialArc, scopes: ['sketch'] }
|
{ enabled: !disableTangentialArc, scopes: ['sketch'] }
|
||||||
)
|
)
|
||||||
@ -89,7 +90,7 @@ export function Toolbar({
|
|||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'rectangle',
|
data: { tool: 'rectangle' },
|
||||||
}),
|
}),
|
||||||
{ enabled: !disableRectangle, scopes: ['sketch'] }
|
{ enabled: !disableRectangle, scopes: ['sketch'] }
|
||||||
)
|
)
|
||||||
@ -118,6 +119,16 @@ export function Toolbar({
|
|||||||
}),
|
}),
|
||||||
{ enabled: !disableAllButtons, scopes: ['modeling'] }
|
{ enabled: !disableAllButtons, scopes: ['modeling'] }
|
||||||
)
|
)
|
||||||
|
const disableFillet = !state.can('Fillet') || disableAllButtons
|
||||||
|
useHotkeys(
|
||||||
|
'f',
|
||||||
|
() =>
|
||||||
|
commandBarSend({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: { name: 'Fillet', groupId: 'modeling' },
|
||||||
|
}),
|
||||||
|
{ enabled: !disableFillet, scopes: ['modeling'] }
|
||||||
|
)
|
||||||
|
|
||||||
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
|
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
|
||||||
const span = toolbarButtonsRef.current
|
const span = toolbarButtonsRef.current
|
||||||
@ -263,7 +274,7 @@ export function Toolbar({
|
|||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'line',
|
data: { tool: 'line' },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
aria-pressed={state?.matches('Sketch.Line tool')}
|
aria-pressed={state?.matches('Sketch.Line tool')}
|
||||||
@ -293,7 +304,7 @@ export function Toolbar({
|
|||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'tangentialArc',
|
data: { tool: 'tangentialArc' },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
aria-pressed={state.matches('Sketch.Tangential arc to')}
|
||||||
@ -323,7 +334,7 @@ export function Toolbar({
|
|||||||
? send('CancelSketch')
|
? send('CancelSketch')
|
||||||
: send({
|
: send({
|
||||||
type: 'change tool',
|
type: 'change tool',
|
||||||
data: 'rectangle',
|
data: { tool: 'rectangle' },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
aria-pressed={state.matches('Sketch.Rectangle tool')}
|
||||||
@ -404,6 +415,36 @@ export function Toolbar({
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
</li>
|
</li>
|
||||||
)}
|
)}
|
||||||
|
{state.matches('idle') && (DEV || (window as any)._enableFillet) && (
|
||||||
|
<li className="contents">
|
||||||
|
<ActionButton
|
||||||
|
className={buttonClassName}
|
||||||
|
Element="button"
|
||||||
|
onClick={() =>
|
||||||
|
commandBarSend({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: { name: 'Fillet', groupId: 'modeling' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={disableFillet}
|
||||||
|
title={disableFillet ? 'fillet' : "edge can't be filleted"}
|
||||||
|
iconStart={{
|
||||||
|
icon: 'fillet', // todo: add fillet icon
|
||||||
|
iconClassName,
|
||||||
|
bgClassName,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Fillet
|
||||||
|
<Tooltip
|
||||||
|
delay={1250}
|
||||||
|
position="bottom"
|
||||||
|
className="!px-2 !text-xs"
|
||||||
|
>
|
||||||
|
Shortcut: F
|
||||||
|
</Tooltip>
|
||||||
|
</ActionButton>
|
||||||
|
</li>
|
||||||
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
</menu>
|
</menu>
|
||||||
)
|
)
|
||||||
|
@ -41,6 +41,7 @@ function CommandArgOptionInput({
|
|||||||
)
|
)
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
const formRef = useRef<HTMLFormElement>(null)
|
const formRef = useRef<HTMLFormElement>(null)
|
||||||
|
const [shouldSubmitOnChange, setShouldSubmitOnChange] = useState(false)
|
||||||
const [selectedOption, setSelectedOption] = useState<
|
const [selectedOption, setSelectedOption] = useState<
|
||||||
CommandArgumentOption<unknown>
|
CommandArgumentOption<unknown>
|
||||||
>(currentOption || resolvedOptions[0])
|
>(currentOption || resolvedOptions[0])
|
||||||
@ -82,9 +83,11 @@ function CommandArgOptionInput({
|
|||||||
// We deal with the whole option object internally
|
// We deal with the whole option object internally
|
||||||
setSelectedOption(option)
|
setSelectedOption(option)
|
||||||
|
|
||||||
// But we only submit the value
|
// But we only submit the value itself
|
||||||
|
if (shouldSubmitOnChange) {
|
||||||
onSubmit(option.value)
|
onSubmit(option.value)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@ -94,7 +97,18 @@ function CommandArgOptionInput({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form id="arg-form" onSubmit={handleSubmit} ref={formRef}>
|
<form
|
||||||
|
id="arg-form"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
ref={formRef}
|
||||||
|
onKeyDownCapture={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
setShouldSubmitOnChange(true)
|
||||||
|
} else {
|
||||||
|
setShouldSubmitOnChange(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Combobox
|
<Combobox
|
||||||
value={selectedOption}
|
value={selectedOption}
|
||||||
onChange={handleSelectOption}
|
onChange={handleSelectOption}
|
||||||
@ -118,6 +132,12 @@ function CommandArgOptionInput({
|
|||||||
if (event.key === 'Backspace' && !event.currentTarget.value) {
|
if (event.key === 'Backspace' && !event.currentTarget.value) {
|
||||||
stepBack()
|
stepBack()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
setShouldSubmitOnChange(true)
|
||||||
|
} else {
|
||||||
|
setShouldSubmitOnChange(false)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
value={query}
|
value={query}
|
||||||
placeholder={
|
placeholder={
|
||||||
@ -136,6 +156,9 @@ function CommandArgOptionInput({
|
|||||||
<Combobox.Options
|
<Combobox.Options
|
||||||
static
|
static
|
||||||
className="overflow-y-auto max-h-96 cursor-pointer"
|
className="overflow-y-auto max-h-96 cursor-pointer"
|
||||||
|
onMouseDown={() => {
|
||||||
|
setShouldSubmitOnChange(true)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{filteredOptions?.map((option) => (
|
{filteredOptions?.map((option) => (
|
||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
|
@ -114,6 +114,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
|||||||
>
|
>
|
||||||
{argName}
|
{argName}
|
||||||
</span>
|
</span>
|
||||||
|
<span className="sr-only">: </span>
|
||||||
{argValue ? (
|
{argValue ? (
|
||||||
arg.inputType === 'selection' ? (
|
arg.inputType === 'selection' ? (
|
||||||
getSelectionTypeDisplayText(argValue as Selections)
|
getSelectionTypeDisplayText(argValue as Selections)
|
||||||
|
@ -28,6 +28,11 @@ export const CommandBarProvider = ({
|
|||||||
Object.keys(context.selectedCommand?.args).length === 0
|
Object.keys(context.selectedCommand?.args).length === 0
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
'All arguments are skippable': (context, _event) => {
|
||||||
|
return Object.values(context.selectedCommand!.args!).every(
|
||||||
|
(argConfig) => argConfig.skip
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
|||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import { CommandArgument } from 'lib/commandTypes'
|
import { CommandArgument } from 'lib/commandTypes'
|
||||||
import {
|
import {
|
||||||
|
Selection,
|
||||||
canSubmitSelectionArg,
|
canSubmitSelectionArg,
|
||||||
getSelectionType,
|
getSelectionType,
|
||||||
getSelectionTypeDisplayText,
|
getSelectionTypeDisplayText,
|
||||||
@ -11,6 +12,25 @@ import { modelingMachine } from 'machines/modelingMachine'
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { StateFrom } from 'xstate'
|
import { StateFrom } from 'xstate'
|
||||||
|
|
||||||
|
const semanticEntityNames: { [key: string]: Array<Selection['type']> } = {
|
||||||
|
face: ['extrude-wall', 'start-cap', 'end-cap'],
|
||||||
|
edge: ['edge', 'line', 'arc'],
|
||||||
|
point: ['point', 'line-end', 'line-mid'],
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSemanticSelectionType(selectionType: Array<Selection['type']>) {
|
||||||
|
const semanticSelectionType = new Set()
|
||||||
|
selectionType.forEach((type) => {
|
||||||
|
Object.entries(semanticEntityNames).forEach(([entity, entityTypes]) => {
|
||||||
|
if (entityTypes.includes(type)) {
|
||||||
|
semanticSelectionType.add(entity)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return Array.from(semanticSelectionType)
|
||||||
|
}
|
||||||
|
|
||||||
const selectionSelector = (snapshot: StateFrom<typeof modelingMachine>) =>
|
const selectionSelector = (snapshot: StateFrom<typeof modelingMachine>) =>
|
||||||
snapshot.context.selectionRanges
|
snapshot.context.selectionRanges
|
||||||
|
|
||||||
@ -85,7 +105,9 @@ function CommandBarSelectionInput({
|
|||||||
>
|
>
|
||||||
{canSubmitSelection
|
{canSubmitSelection
|
||||||
? getSelectionTypeDisplayText(selection) + ' selected'
|
? getSelectionTypeDisplayText(selection) + ' selected'
|
||||||
: `Please select ${arg.multiple ? 'one or more faces' : 'one face'}`}
|
: `Please select ${
|
||||||
|
arg.multiple ? 'one or more ' : 'one '
|
||||||
|
}${getSemanticSelectionType(arg.selectionTypes).join(' or ')}`}
|
||||||
<input
|
<input
|
||||||
id="selection"
|
id="selection"
|
||||||
name="selection"
|
name="selection"
|
||||||
|
@ -70,19 +70,23 @@ function CommandComboBox({
|
|||||||
>
|
>
|
||||||
{filteredOptions?.map((option) => (
|
{filteredOptions?.map((option) => (
|
||||||
<Combobox.Option
|
<Combobox.Option
|
||||||
key={option.name}
|
key={option.groupId + option.name + (option.displayName || '')}
|
||||||
value={option}
|
value={option}
|
||||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||||
>
|
>
|
||||||
{'icon' in option && option.icon && (
|
{'icon' in option && option.icon && (
|
||||||
<CustomIcon name={option.icon} className="w-5 h-5" />
|
<CustomIcon name={option.icon} className="w-5 h-5" />
|
||||||
)}
|
)}
|
||||||
<p className="flex-grow">{option.displayName || option.name} </p>
|
<div className="flex-grow flex flex-col">
|
||||||
|
<p className="my-0 leading-tight">
|
||||||
|
{option.displayName || option.name}{' '}
|
||||||
|
</p>
|
||||||
{option.description && (
|
{option.description && (
|
||||||
<p className="text-xs text-chalkboard-60 dark:text-chalkboard-40">
|
<p className="my-0 text-xs text-chalkboard-60 dark:text-chalkboard-50">
|
||||||
{option.description}
|
{option.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</Combobox.Option>
|
</Combobox.Option>
|
||||||
))}
|
))}
|
||||||
</Combobox.Options>
|
</Combobox.Options>
|
||||||
|
@ -131,6 +131,16 @@ const CustomIconMap = {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
|
code: (
|
||||||
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M10.7071 5L7.77734 14.7794L8.73527 15.0663L11.665 5.28698L10.7071 5ZM2.35356 9.64644L5.85362 6.14644L6.56072 6.85355L3.41423 10L6.56072 13.1464L5.85362 13.8536L2.35356 10.3536L2 10L2.35356 9.64644ZM17.0607 9.64644L13.5607 6.14644L12.8536 6.85355L16 10L12.8536 13.1464L13.5607 13.8535L17.0607 10.3536L17.4142 10L17.0607 9.64644Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
dimension: (
|
dimension: (
|
||||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
@ -177,6 +187,22 @@ const CustomIconMap = {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
|
fillet: (
|
||||||
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M8 5H5V15H15V12C15 8.13401 11.866 5 8 5ZM5 4H4V5V15V16H5H15H16V15V12C16 7.58172 12.4183 4 8 4H5Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M4.5 3.5H5.5H8.5C12.9183 3.5 16.5 7.08172 16.5 11.5V14.5V15.5H16V12C16 7.58172 12.4182 4 7.99996 4H4.5V3.5Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
file: (
|
file: (
|
||||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
|
@ -42,7 +42,7 @@ import {
|
|||||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||||
import { modelingMachineConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
import { modelingMachineCommandConfig } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||||
import {
|
import {
|
||||||
STRAIGHT_SEGMENT,
|
STRAIGHT_SEGMENT,
|
||||||
TANGENTIAL_ARC_TO_SEGMENT,
|
TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
@ -72,6 +72,7 @@ import { uuidv4 } from 'lib/utils'
|
|||||||
import { err, trap } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { modelingMachineEvent } from 'editor/manager'
|
import { modelingMachineEvent } from 'editor/manager'
|
||||||
|
import { hasValidFilletSelection } from 'lang/modifyAst/addFillet'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -130,6 +131,9 @@ export const ModelingMachineProvider = ({
|
|||||||
},
|
},
|
||||||
'sketch exit execute': ({ store }) => {
|
'sketch exit execute': ({ store }) => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
|
// blocks entering a sketch until after exit sketch code has run
|
||||||
|
kclManager.isExecuting = true
|
||||||
|
|
||||||
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
|
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
|
||||||
|
|
||||||
sceneInfra.camControls.syncDirection = 'engineToClient'
|
sceneInfra.camControls.syncDirection = 'engineToClient'
|
||||||
@ -164,7 +168,7 @@ export const ModelingMachineProvider = ({
|
|||||||
|
|
||||||
store.videoElement?.pause()
|
store.videoElement?.pause()
|
||||||
kclManager.executeCode(true).then(() => {
|
kclManager.executeCode(true).then(() => {
|
||||||
if (engineCommandManager.engineConnection?.freezeFrame) return
|
if (engineCommandManager.engineConnection?.idleMode) return
|
||||||
|
|
||||||
store.videoElement?.play()
|
store.videoElement?.play()
|
||||||
})
|
})
|
||||||
@ -444,6 +448,12 @@ export const ModelingMachineProvider = ({
|
|||||||
if (selectionRanges.codeBasedSelections.length <= 0) return false
|
if (selectionRanges.codeBasedSelections.length <= 0) return false
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
'has valid fillet selection': ({ selectionRanges }) =>
|
||||||
|
hasValidFilletSelection({
|
||||||
|
selectionRanges,
|
||||||
|
ast: kclManager.ast,
|
||||||
|
code: codeManager.code,
|
||||||
|
}),
|
||||||
'Selection is on face': ({ selectionRanges }, { data }) => {
|
'Selection is on face': ({ selectionRanges }, { data }) => {
|
||||||
if (data?.forceNewSketch) return false
|
if (data?.forceNewSketch) return false
|
||||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
||||||
@ -494,7 +504,6 @@ export const ModelingMachineProvider = ({
|
|||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
data.sketchPathToNode,
|
data.sketchPathToNode,
|
||||||
data.extrudePathToNode,
|
data.extrudePathToNode,
|
||||||
kclManager.programMemory,
|
|
||||||
data.cap
|
data.cap
|
||||||
)
|
)
|
||||||
if (trap(sketched)) return Promise.reject(sketched)
|
if (trap(sketched)) return Promise.reject(sketched)
|
||||||
@ -920,7 +929,7 @@ export const ModelingMachineProvider = ({
|
|||||||
state: modelingState,
|
state: modelingState,
|
||||||
send: modelingSend,
|
send: modelingSend,
|
||||||
actor: modelingActor,
|
actor: modelingActor,
|
||||||
commandBarConfig: modelingMachineConfig,
|
commandBarConfig: modelingMachineCommandConfig,
|
||||||
allCommandsRequireNetwork: true,
|
allCommandsRequireNetwork: true,
|
||||||
// TODO for when sketch tools are in the toolbar: This was added when we used one "Cancel" event,
|
// TODO for when sketch tools are in the toolbar: This was added when we used one "Cancel" event,
|
||||||
// but we need to support "SketchCancel" and basically
|
// but we need to support "SketchCancel" and basically
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import styles from './ModelingPane.module.css'
|
import styles from './ModelingPane.module.css'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import { ActionButton } from 'components/ActionButton'
|
||||||
|
import Tooltip from 'components/Tooltip'
|
||||||
|
|
||||||
export interface ModelingPaneProps
|
export interface ModelingPaneProps
|
||||||
extends React.PropsWithChildren,
|
extends React.PropsWithChildren,
|
||||||
@ -8,16 +10,32 @@ export interface ModelingPaneProps
|
|||||||
title: string
|
title: string
|
||||||
Menu?: React.ReactNode | React.FC
|
Menu?: React.ReactNode | React.FC
|
||||||
detailsTestId?: string
|
detailsTestId?: string
|
||||||
|
onClose: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModelingPaneHeader = ({
|
export const ModelingPaneHeader = ({
|
||||||
title,
|
title,
|
||||||
Menu,
|
Menu,
|
||||||
}: Pick<ModelingPaneProps, 'title' | 'Menu'>) => {
|
onClose,
|
||||||
|
}: Pick<ModelingPaneProps, 'title' | 'Menu' | 'onClose'>) => {
|
||||||
return (
|
return (
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<div className="flex gap-2 items-center flex-1">{title}</div>
|
<div className="flex gap-2 items-center flex-1">{title}</div>
|
||||||
{Menu instanceof Function ? <Menu /> : Menu}
|
{Menu instanceof Function ? <Menu /> : Menu}
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
iconStart={{
|
||||||
|
icon: 'close',
|
||||||
|
iconClassName: '!text-current',
|
||||||
|
bgClassName: 'bg-transparent dark:bg-transparent',
|
||||||
|
}}
|
||||||
|
className="!p-0 !bg-transparent hover:text-primary border-transparent dark:!border-transparent hover:!border-primary dark:hover:!border-chalkboard-70 !outline-none"
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
<Tooltip position="bottom-right" delay={750}>
|
||||||
|
Close
|
||||||
|
</Tooltip>
|
||||||
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -29,6 +47,7 @@ export const ModelingPane = ({
|
|||||||
className,
|
className,
|
||||||
Menu,
|
Menu,
|
||||||
detailsTestId,
|
detailsTestId,
|
||||||
|
onClose,
|
||||||
...props
|
...props
|
||||||
}: ModelingPaneProps) => {
|
}: ModelingPaneProps) => {
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
@ -51,7 +70,7 @@ export const ModelingPane = ({
|
|||||||
(className || '')
|
(className || '')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ModelingPaneHeader title={title} Menu={Menu} />
|
<ModelingPaneHeader title={title} Menu={Menu} onClose={onClose} />
|
||||||
<div className="relative w-full">{children}</div>
|
<div className="relative w-full">{children}</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|