Compare commits

...

28 Commits

Author SHA1 Message Date
c184a7d4d8 Cut release v0.24.4 (#3120) 2024-07-25 01:03:02 -04:00
c38e52fbb7 Toolbar rewrite: (mostly) fixed content, separate config, rich tooltips, and roadmapped tools (#3119)
* Basic implementation of rich tooltips

* Break out config to its own file, add a bunch of items

* Better lower right control tooltip sizing

* Add a bunch of sketch tools to the config

* Fix hotkey collisions and UX polish

* Get tests working again

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Re-run CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* We updated how the sidebar buttons' test IDs are generated, fix it post-merge

* fmt

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Re-run CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Re-run CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Re-run CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-24 23:33:31 -04:00
ea0a3ac3ba Reunite split sidebar, add ability to register action buttons to it (#3100)
* Rework ribbon to support panes and actions

* Restore nice focus-within highlighting

* A better export icon

* Fix up some issues with tests due to sidebar and tooltip tweaks

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Re-run CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-24 22:02:16 -04:00
385589ddf9 Clear the diagnostics before processing (#3118)
clear diagnostics when we update the text

Signed-off-by: Paul R. Tagliamonte <paul@zoo.dev>
2024-07-24 16:11:56 -04:00
22df47fa96 remove _deffer (#3114)
* remove _deffer

Signed-off-by: Paul Tagliamonte <paul@zoo.dev>
2024-07-23 20:37:04 -04:00
a68748abcf Seperate pending messages from artifact map (#3084)
* start of seperating pending message from artifact map

* continue migration to sendCommandVersion2

* mostly massage types

* process artifact after the fact

* clean up
2024-07-23 17:13:23 +10:00
1b8688f274 Add lexical scope and redefining variables in functions (#3015)
* Fix to allow variable shadowing inside functions

* Implement closures

* Fix KCL test code to not reference future tag definition

* Remove tag declarator from function parameters

This is an example where the scoping change revealed a subtle issue
with TagDeclarators.  You cannot bind a new tag using a function
parameter.

The issue is that evaluating a TagDeclarator like $foo binds an
identifier to its corresponding TagIdentifier, but returns the
TagDeclarator.  If you have a TagDeclarator passed in as a parameter
to a function, you can never get its corresponding TagIdentifier.

This seems like a case where TagDeclarator evaluation needs to be
revisited, especially now that we have scoped tags.

* Fix to query return, functions, and tag declarator AST nodes correctly
2024-07-22 19:43:40 -04:00
397839da84 Fix syntax highlighting on code pane open/close (#3083) 2024-07-20 01:45:38 -07:00
ac120838e5 setIsLoading false earlier (#3072)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-07-19 22:18:31 -04:00
e6a2ac9c4a Typecheck KCL args via generics, not handwritten impls (#3025)
In KCL, arguments to functions are passed in the Args struct. This struct contains a list of args, but each arg could be any KCL type (they're stored in an enum of all possible types). To get args of the correct type, these enums are fallibly converted into the type expected for the matching parameter.

Until now, the fallible conversion was handwritten for nearly each function. This is unnecessary, I've replaced it with composable traits.
2024-07-19 20:30:13 -05:00
6e7e6e96cf Make engine reconnection test pass every time (#3066)
* Ensure that isFreezeFrame is reset by isFirstRender, because it can't be a freeze frame if it's the first render

* `restart`-type engine starts should count as first renders

* Ensure we don't see a loading spinner after network is reconnected in test

* Make `waitForPageLoad` robust against if the page has already loaded
and make it actually wait for the Start Sketch button to be enabled

---------

Co-authored-by: 49fl <ircsurfer33@gmail.com>
2024-07-18 16:16:17 -04:00
73e155d79b Fix JS error about fill-rule when opening user menu (#3069) 2024-07-18 20:13:40 +00:00
a782f26ec2 Use ..Default::default() with a struct constructor (#3068)
As @jtran pointed out - I had misunderstood the behavior of
Default::default(), we can instead rely on this syntax to do the same
thing. This won't use each field's default value -- rather, it'll use
the type's Default, and override each field. Neat!

Signed-off-by: Paul Tagliamonte <paul@zoo.dev>
2024-07-18 15:20:50 -04:00
01076c3aed Remove sidebar menus in favor of lil' popovers (#3046)
* Convert user menu to a popover from a sidebar

* Move the user menu over to the left menu cluster

* Replace project sidebar with popover-style menu

* Styling tweaks, give export button a proper tooltip when disabled

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Filter orphan breaks, tweak space to remove mouse gaps

* Unify with and without avatar image code

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Rerun CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Rerun CI

* Prepare to move UserSidebarMenu over to right

* Revert AppHeader tweaks

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Rerun CI

* Fix typo in README

* Fix export E2E tests that relied on button text

* Missed the data-testid we used to have on the data-testid we had on the settings button

* Dang I missed another testId

* Update snapshots

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Rerun CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-18 14:29:15 -04:00
fe512611ac Fix doc comment to match generated docs (#3067) 2024-07-18 18:00:06 +00:00
cba953c245 Cut release v0.24.3 (#3053) 2024-07-18 10:55:23 -07:00
54ca6ea0b2 artifact map clean up 1 (#3064)
remove old shit
2024-07-18 17:52:39 +10:00
max
6a01608c3a Unit Tests for hasValidFilletSelection (#3063)
* tests

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* "err" instead of "instanceof Error"

* trigger CI

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2024-07-18 16:33:49 +10:00
530f15e04a switch to js 2024-07-17 19:49:50 -04:00
725e59d987 use shell for now 2024-07-17 19:47:02 -04:00
54313c9b03 fix template again 2024-07-17 19:44:02 -04:00
890d96496c add a new cryptic_error template 2024-07-17 19:42:28 -04:00
35999366a7 Stl test for larger file (#3052)
* add shit

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add image

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* images updated

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-07-17 15:55:59 -07:00
2affc7271d Bump max_frame_size (#3050)
We use the WebSocket connection to send binary data (in the form of
shapefiles) from the engine to the client. These can very easily get
larger than the default 16MB limit on the max_frame_size. I don't
understand why it won't stich multiple frames together - but given what
I can see when this crashes, the max_message_size isn't the LIMFAC,
max_frame_size is.

That's an issue for future-us.

Signed-off-by: Paul Tagliamonte <paul@zoo.dev>
2024-07-17 15:32:57 -04:00
d30fbf8b4b Bump tokio from 1.38.0 to 1.38.1 in /src/wasm-lib (#3043)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.38.0 to 1.38.1.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.38.0...tokio-1.38.1)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-17 09:06:39 -07:00
3f7e776464 Improve 'Release a new version' readme (#3048) 2024-07-17 10:23:21 -04:00
79cff57f43 show default planes bug (#3047) 2024-07-17 18:58:01 +10:00
1cd2cd82b2 Add a close button to sidebar panes (#3038)
* Add a close button to sidebar panes

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Rerun CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Fix up dark mode look and feel

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-17 01:17:53 -04:00
302 changed files with 163920 additions and 2827 deletions

View 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

View File

@ -124,28 +124,39 @@ Before you submit a contribution PR to this repo, please ensure that:
## 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.
run `./make-release.sh` for a patch update
run `./make-release.sh "minor"` for minor
run `./make-release.sh "major"` for major
That will create the branch with the updated json files for you:
- run `./make-release.sh` or `./make-release.sh patch` for a patch update;
- run `./make-release.sh minor` for minor; or
- 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. Smoke test the artifact from the above PR
We don't have a strict process, but click around and check for anything obvious
One of the artifacts is called updater-test, because we don't have a way to test this fully automated, we have a semi-automated process.
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.
Download updater-test zip file, install the app, run it, expect an updater prompt to v0.99.99, install it and check that the app comes back at that version (on both macOS and Windows).
#### 2. Smoke test artifacts from the Cut Release PR
3. Merge the 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. Profit (A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions if the PR was correctly named)
#### 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.
## Fuzzing the parser

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -175,7 +175,7 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
}
// deselect line tool
await page.getByRole('button', { name: 'Line' }).click()
await page.getByRole('button', { name: 'Line', exact: true }).click()
await page.waitForTimeout(500)
const line1 = await u.getSegmentBodyCoords(`[data-overlay-index="${0}"]`, 0)
@ -203,7 +203,7 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
await expect(page.locator('.cm-cursor')).toHaveCount(2)
}
await page.getByRole('button', { name: 'Constraints' }).click()
await page.getByRole('button', { name: 'Length: open menu' }).click()
await page.getByRole('button', { name: 'Equal Length' }).click()
// Open the code pane.
@ -452,7 +452,7 @@ test.describe('Testing Camera Movement', () => {
// await expect(u.codeLocator).toHaveText(code)
// click the line button
await page.getByRole('button', { name: 'Line' }).click()
await page.getByRole('button', { name: 'Line', exact: true }).click()
const hoverOverNothing = async () => {
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
@ -1462,7 +1462,9 @@ test.describe('Can create sketches on all planes and their back sides', () => {
await page.mouse.click(clickCoords.x, clickCoords.y)
await page.waitForTimeout(300) // wait for animation
await expect(page.getByRole('button', { name: 'Line' })).toBeVisible()
await expect(
page.getByRole('button', { name: 'Line', exact: true })
).toBeVisible()
// draw a line
const startXPx = 600
@ -1472,7 +1474,7 @@ test.describe('Can create sketches on all planes and their back sides', () => {
await expect(page.locator('.cm-content')).toHaveText(code)
await page.getByRole('button', { name: 'Line' }).click()
await page.getByRole('button', { name: 'Line', exact: true }).click()
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')
@ -1511,6 +1513,8 @@ test.describe('Can create sketches on all planes and their back sides', () => {
})
test.describe('Copilot ghost text', () => {
test.skip(true, 'Needs to get covered again')
test('completes code in empty file', async ({ page }) => {
const u = await getUtils(page)
// const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -1549,7 +1553,9 @@ test.describe('Copilot ghost text', () => {
await expect(page.locator('.cm-ghostText')).not.toBeVisible()
})
test('copilot disabled in sketch mode no select plane', async ({ page }) => {
test.skip('copilot disabled in sketch mode no select plane', async ({
page,
}) => {
const u = await getUtils(page)
// const PUR = 400 / 37.5 //pixeltoUnitRatio
await page.setViewportSize({ width: 1200, height: 500 })
@ -2096,7 +2102,7 @@ test.describe('Testing settings', () => {
.hover()
await page
.getByRole('button', {
name: 'Roll back theme ; Has tooltip: Roll back to match default',
name: 'Roll back theme',
})
.click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
@ -2148,7 +2154,7 @@ test.describe('Testing settings', () => {
.hover()
await page
.getByRole('button', {
name: 'Roll back theme ; Has tooltip: Roll back to match default',
name: 'Roll back theme',
})
.click()
await expect(page.locator('select[name="app-theme"]')).toHaveValue('system')
@ -2562,7 +2568,7 @@ test.describe('Testing selections', () => {
|> line([-${commonPoints.num2}, 0], %)`)
// deselect line tool
await page.getByRole('button', { name: 'Line' }).click()
await page.getByRole('button', { name: 'Line', exact: true }).click()
await u.closeDebugPanel()
const selectionSequence = async () => {
@ -2587,8 +2593,10 @@ test.describe('Testing selections', () => {
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
await topHorzSegmentClick()
await page.keyboard.down('Shift')
const constrainButton = page.getByRole('button', { name: 'Constraints' })
const absYButton = page.getByRole('button', { name: 'ABS Y' })
const constrainButton = page.getByRole('button', {
name: 'Length: open menu',
})
const absYButton = page.getByRole('button', { name: 'Absolute Y' })
await constrainButton.click()
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
@ -3412,21 +3420,6 @@ const extrude001 = extrude(50, sketch001)
await expect(
page.getByRole('button', { name: 'Edit Sketch' })
).not.toBeVisible()
// selecting an editable sketch but clicking "start sketch" should start a new sketch and not edit the existing one
await page.getByText(selectionsSnippets.extrudeAndEditAllowed).click()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(200)
await page.getByTestId('KCL Code').click()
await page.waitForTimeout(200)
await page.mouse.click(734, 134)
await page.waitForTimeout(100)
await page.getByTestId('KCL Code').click()
// expect main content to contain `sketch005` i.e. started a new sketch
await page.waitForTimeout(300)
await expect(page.locator('.cm-content')).toHaveText(
/sketch001 = startSketchOn\('XZ'\)/
)
})
test('Deselecting line tool should mean nothing happens on click', async ({
@ -3463,7 +3456,7 @@ const extrude001 = extrude(50, sketch001)
let previousCodeContent = await page.locator('.cm-content').innerText()
// deselect the line tool by clicking it
await page.getByRole('button', { name: 'Line' }).click()
await page.getByRole('button', { name: 'Line', exact: true }).click()
await page.mouse.click(700, 200)
await page.waitForTimeout(100)
@ -3476,7 +3469,7 @@ const extrude001 = extrude(50, sketch001)
await expect(page.locator('.cm-content')).toHaveText(previousCodeContent)
// select line tool again
await page.getByRole('button', { name: 'Line' }).click()
await page.getByRole('button', { name: 'Line', exact: true }).click()
await u.closeDebugPanel()
@ -3809,13 +3802,24 @@ const extrude001 = extrude(distance001, sketch001)`.replace(
const sketchButton = page.getByRole('button', { name: 'Start Sketch' })
const cmdBarButton = page.getByRole('button', { name: 'Commands' })
const rectangleToolCommand = page.getByRole('option', {
name: 'Rectangle',
name: 'rectangle',
})
const rectangleToolButton = page.getByRole('button', {
name: 'Corner rectangle',
exact: true,
})
const lineToolCommand = page.getByRole('option', {
name: 'Line',
})
const lineToolButton = page.getByRole('button', {
name: 'Line',
exact: true,
})
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' })
const arcToolButton = page.getByRole('button', {
name: 'Tangential Arc',
exact: true,
})
// Start a sketch
await sketchButton.click()
@ -3862,10 +3866,7 @@ test.describe('Regression tests', () => {
await u.waitForAuthSkipAppStart()
// expand variables section
const variablesTabButton = page.getByRole('tab', {
name: 'Variables',
exact: false,
})
const variablesTabButton = page.getByTestId('variables-pane-button')
await variablesTabButton.click()
// can find sketch001 in the variables summary (pretty-json-container, makes sure we're not looking in the code editor)
@ -3890,10 +3891,7 @@ test.describe('Regression tests', () => {
await u.waitForAuthSkipAppStart()
const variablesTabButton = page.getByRole('tab', {
name: 'Variables',
exact: false,
})
const variablesTabButton = page.getByTestId('variables-pane-button')
await variablesTabButton.click()
// expect to see "myVar:5"
await expect(
@ -4154,7 +4152,7 @@ test.describe('Sketch tests', () => {
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Line' }).click()
await page.getByRole('button', { name: 'Line', exact: true }).click()
await page.waitForTimeout(100)
await page.mouse.click(700, 200)
@ -4181,9 +4179,7 @@ test.describe('Sketch tests', () => {
page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible()
await expect(
page.getByText('click plane or face to sketch on')
).toBeVisible()
await expect(page.getByText('select a plane or face')).toBeVisible()
await page.keyboard.press('Escape')
await expect(
@ -4734,7 +4730,7 @@ test.describe('Sketch tests', () => {
await expect(page.locator('.cm-content')).toHaveText(code)
// Assert the tool was unequipped
await expect(
page.getByRole('button', { name: 'Line' })
page.getByRole('button', { name: 'Line', exact: true })
).not.toHaveAttribute('aria-pressed', 'true')
// exit sketch
@ -4896,8 +4892,7 @@ test.describe('Testing constraints', () => {
await page.mouse.click(834, 244)
await page.keyboard.up('Shift')
await page.getByRole('button', { name: 'Constraints', exact: true }).click()
await page.getByRole('button', { name: 'length', exact: true }).click()
await page.getByRole('button', { name: 'Length', exact: true }).click()
await page.getByText('Add constraining value').click()
await expect(page.locator('.cm-content')).toHaveText(
@ -4951,12 +4946,10 @@ const part002 = startSketchOn('XZ')
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
await page
.getByRole('button', {
name: 'Constraints',
name: 'Length: open menu',
})
.click()
await page
.getByRole('button', { name: 'remove constraints', exact: true })
.click()
await page.getByRole('button', { name: 'remove constraints' }).click()
await page.getByText('line([39.13, 68.63], %)').click()
const activeLinesContent = await page.locator('.cm-activeLine').all()
@ -5017,11 +5010,11 @@ const part002 = startSketchOn('XZ')
await page.keyboard.up('Shift')
await page
.getByRole('button', {
name: 'Constraints',
name: 'Length: open menu',
})
.click()
await page
.getByRole('button', { name: 'perpendicular distance', exact: true })
.getByRole('button', { name: 'Perpendicular Distance' })
.click()
const createNewVariableCheckbox = page.getByTestId(
@ -5116,12 +5109,10 @@ const part002 = startSketchOn('XZ')
await page.keyboard.up('Shift')
await page
.getByRole('button', {
name: 'Constraints',
name: 'Length: open menu',
})
.click()
await page
.getByRole('button', { name: constraint, exact: true })
.click()
await page.getByRole('button', { name: constraint }).click()
const createNewVariableCheckbox = page.getByTestId(
'create-new-variable-checkbox'
@ -5162,25 +5153,25 @@ const part002 = startSketchOn('XZ')
{
testName: 'Add variable',
addVariable: true,
constraint: 'ABS X',
constraint: 'Absolute X',
value: 'xDis001, 61.34',
},
{
testName: 'No variable',
addVariable: false,
constraint: 'ABS X',
constraint: 'Absolute X',
value: '154.9, 61.34',
},
{
testName: 'Add variable',
addVariable: true,
constraint: 'ABS Y',
constraint: 'Absolute Y',
value: '154.9, yDis001',
},
{
testName: 'No variable',
addVariable: false,
constraint: 'ABS Y',
constraint: 'Absolute Y',
value: '154.9, 61.34',
},
] as const
@ -5216,7 +5207,7 @@ const part002 = startSketchOn('XZ')
u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`),
])
if (constraint === 'ABS X') {
if (constraint === 'Absolute X') {
await page.mouse.click(600, 130)
} else {
await page.mouse.click(900, 250)
@ -5227,7 +5218,7 @@ const part002 = startSketchOn('XZ')
await page.keyboard.up('Shift')
await page
.getByRole('button', {
name: 'Constraints',
name: 'Length: open menu',
})
.click()
await page
@ -5335,10 +5326,10 @@ const part002 = startSketchOn('XZ')
await page.keyboard.up('Shift')
await page
.getByRole('button', {
name: 'Constraints',
name: 'Length: open menu',
})
.click()
await page.getByTestId('angle').click()
await page.getByTestId('dropdown-constraint-angle').click()
const createNewVariableCheckbox = page.getByTestId(
'create-new-variable-checkbox'
@ -5436,10 +5427,10 @@ const part002 = startSketchOn('XZ')
await page.mouse.click(line3.x, line3.y)
await page
.getByRole('button', {
name: 'Constraints',
name: 'Length: open menu',
})
.click()
await page.getByTestId(constraint).click()
await page.getByTestId('dropdown-constraint-' + constraint).click()
if (!addVariable) {
await page.getByTestId('create-new-variable-checkbox').click()
@ -5527,7 +5518,7 @@ const part002 = startSketchOn('XZ')
await expect(activeLinesContent).toHaveLength(codeAfter.length)
const constraintMenuButton = page.getByRole('button', {
name: 'Constraints',
name: 'Length: open menu',
})
const constraintButton = page
.getByRole('button', {
@ -5610,7 +5601,7 @@ const part002 = startSketchOn('XZ')
await page.mouse.click(line3.x - 3, line3.y + 20)
await page.keyboard.up('Shift')
const constraintMenuButton = page.getByRole('button', {
name: 'Constraints',
name: 'Length: open menu',
})
const constraintButton = page.getByRole('button', {
name: constraintName,
@ -5687,7 +5678,7 @@ const part002 = startSketchOn('XZ')
await page.mouse.click(axisClick.x, axisClick.y)
await page.keyboard.up('Shift')
const constraintMenuButton = page.getByRole('button', {
name: 'Constraints',
name: 'Length: open menu',
})
const constraintButton = page.getByRole('button', {
name: constraintName,
@ -5746,10 +5737,10 @@ const part002 = startSketchOn('XZ')
await page
.getByRole('button', {
name: 'Constraints',
name: 'Length: open menu',
})
.click()
await page.getByRole('button', { name: 'horizontal', exact: true }).click()
await page.getByRole('button', { name: 'Horizontal', exact: true }).click()
let activeLinesContent = await page.locator('.cm-activeLine').all()
await expect(activeLinesContent[0]).toHaveText(`|> xLine(3.13, %)`)
@ -5770,13 +5761,13 @@ const part002 = startSketchOn('XZ')
await page.waitForTimeout(300)
await page
.getByRole('button', {
name: 'Constraints',
name: 'Length: open menu',
})
.click()
// await expect(page.getByRole('button', { name: 'length', exact: true })).toBeVisible()
await page.waitForTimeout(200)
// await page.getByRole('button', { name: 'length', exact: true }).click()
await page.locator('[data-testid="length"]').click()
await page.getByTestId('dropdown-constraint-length').click()
await page.getByLabel('length Value').fill('10')
await page.getByRole('button', { name: 'Add constraining value' }).click()
@ -7068,6 +7059,8 @@ test.describe('Test network and connection issues', () => {
await u.waitForAuthSkipAppStart()
const networkToggle = page.getByTestId('network-toggle')
// This is how we wait until the stream is online
await expect(
page.getByRole('button', { name: 'Start Sketch' })
@ -7081,7 +7074,7 @@ test.describe('Test network and connection issues', () => {
await expect(networkPopover).not.toBeVisible()
// (First check) Expect the network to be up
await expect(page.getByText('Network Health (Connected)')).toBeVisible()
await expect(networkToggle).toContainText('Connected')
// Click the network widget
await networkWidget.click()
@ -7103,7 +7096,7 @@ test.describe('Test network and connection issues', () => {
})
// Expect the network to be down
await expect(page.getByText('Network Health (Offline)')).toBeVisible()
await expect(networkToggle).toContainText('Offline')
// Click the network widget
await networkWidget.click()
@ -7129,7 +7122,7 @@ test.describe('Test network and connection issues', () => {
).not.toBeDisabled({ timeout: 15000 })
// (Second check) expect the network to be up
await expect(page.getByText('Network Health (Connected)')).toBeVisible()
await expect(networkToggle).toContainText('Connected')
})
test('Engine disconnect & reconnect in sketch mode', async ({
@ -7141,6 +7134,8 @@ test.describe('Test network and connection issues', () => {
browserName === 'webkit',
'Skip on Safari until `window.tearDown` is working there'
)
const networkToggle = page.getByTestId('network-toggle')
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -7183,7 +7178,7 @@ test.describe('Test network and connection issues', () => {
|> line([${commonPoints.num1}, 0], %)`)
// Expect the network to be up
await expect(page.getByText('Network Health (Connected)')).toBeVisible()
await expect(networkToggle).toContainText('Connected')
// simulate network down
await u.emulateNetworkConditions({
@ -7195,7 +7190,7 @@ test.describe('Test network and connection issues', () => {
})
// Expect the network to be down
await expect(page.getByText('Network Health (Offline)')).toBeVisible()
await expect(networkToggle).toContainText('Offline')
// Ensure we are not in sketch mode
await expect(
@ -7220,7 +7215,8 @@ test.describe('Test network and connection issues', () => {
).not.toBeDisabled({ timeout: 15000 })
// Expect the network to be up
await expect(page.getByText('Network Health (Connected)')).toBeVisible()
await expect(networkToggle).toContainText('Connected')
await expect(page.getByTestId('loading-stream')).not.toBeAttached()
// Click off the code pane.
await page.mouse.click(100, 100)
@ -7236,7 +7232,7 @@ test.describe('Test network and connection issues', () => {
await page.waitForTimeout(150)
// Click the line tool
await page.getByRole('button', { name: 'Line' }).click()
await page.getByRole('button', { name: 'Line', exact: true }).click()
await page.waitForTimeout(150)
@ -7263,7 +7259,7 @@ test.describe('Test network and connection issues', () => {
page.getByRole('button', { name: 'Exit Sketch' })
).toBeVisible()
await expect(
page.getByRole('button', { name: 'Line' })
page.getByRole('button', { name: 'Line', exact: true })
).not.toHaveAttribute('aria-pressed', 'true')
// Exit sketch
@ -7668,7 +7664,7 @@ test('Keyboard shortcuts can be viewed through the help menu', async ({
.waitFor({ state: 'visible' })
// Open the help menu
await page.getByRole('button', { name: 'Help', exact: false }).click()
await page.getByRole('button', { name: 'Help and resources' }).click()
// Open the keyboard shortcuts
await page.getByRole('button', { name: 'Keyboard Shortcuts' }).click()
@ -7692,8 +7688,11 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
const lineButton = page.getByRole('button', { name: 'Line' })
const arcButton = page.getByRole('button', { name: 'Tangential Arc' })
const lineButton = page.getByRole('button', { name: 'Line', exact: true })
const arcButton = page.getByRole('button', {
name: 'Tangential Arc',
exact: true,
})
// Test these hotkeys perform actions when
// focus is on the canvas
@ -7705,6 +7704,7 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
await page.mouse.move(800, 300)
await page.mouse.click(800, 300)
await page.waitForTimeout(1000)
await expect(lineButton).toBeVisible()
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
// Draw a line
@ -7774,9 +7774,12 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
await u.closeDebugPanel()
const codePane = page.getByRole('textbox').locator('div')
const codePaneButton = page.getByRole('tab', { name: 'KCL Code' })
const lineButton = page.getByRole('button', { name: 'Line' })
const arcButton = page.getByRole('button', { name: 'Tangential Arc' })
const codePaneButton = page.getByTestId('code-pane-button')
const lineButton = page.getByRole('button', { name: 'Line', exact: true })
const arcButton = page.getByRole('button', {
name: 'Tangential Arc',
exact: true,
})
const extrudeButton = page.getByRole('button', { name: 'Extrude' })
// Test that the hotkeys do nothing when
@ -7797,7 +7800,7 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
await page.mouse.click(600, 250)
// work-around: to stop "keyboard.press('s')" from typing in the editor even when it should be blurred
await page.getByRole('button', { name: 'Commands ⌘K' }).click()
await page.getByRole('button', { name: 'Commands' }).click()
await page.waitForTimeout(100)
await page.keyboard.press('Escape')
await page.waitForTimeout(100)

View File

@ -431,7 +431,9 @@ test('Draft segments should look right', async ({ page, context }) => {
|> line([7.25, 0], %)`
await expect(page.locator('.cm-content')).toHaveText(code)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page
.getByRole('button', { name: 'Tangential Arc', exact: true })
.click()
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
@ -475,8 +477,10 @@ test('Draft rectangles should look right', async ({ page, context }) => {
const startXPx = 600
// Equip the rectangle tool
await page.getByRole('button', { name: 'Line' }).click()
await page.getByRole('button', { name: 'Rectangle' }).click()
await page.getByRole('button', { name: 'Line', exact: true }).click()
await page
.getByRole('button', { name: 'Corner rectangle', exact: true })
.click()
// Draw the rectangle
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 30)
@ -535,7 +539,9 @@ test.describe('Client side scene scale should match engine scale', () => {
|> line([7.25, 0], %)`
await expect(u.codeLocator).toHaveText(code)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page
.getByRole('button', { name: 'Tangential Arc', exact: true })
.click()
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
@ -545,7 +551,9 @@ test.describe('Client side scene scale should match engine scale', () => {
await expect(u.codeLocator).toHaveText(code)
// click tangential arc tool again to unequip it
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page
.getByRole('button', { name: 'Tangential Arc', exact: true })
.click()
await page.waitForTimeout(100)
// screen shot should show the sketch
@ -634,7 +642,9 @@ test.describe('Client side scene scale should match engine scale', () => {
|> line([184.3, 0], %)`
await expect(u.codeLocator).toHaveText(code)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page
.getByRole('button', { name: 'Tangential Arc', exact: true })
.click()
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
@ -643,7 +653,9 @@ test.describe('Client side scene scale should match engine scale', () => {
|> tangentialArcTo([551.2, -62.01], %)`
await expect(u.codeLocator).toHaveText(code)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page
.getByRole('button', { name: 'Tangential Arc', exact: true })
.click()
await page.waitForTimeout(100)
// screen shot should show the sketch

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -16,14 +16,14 @@ export const TEST_COLORS = {
} as const
async function waitForPageLoad(page: Page) {
// wait for 'Loading stream...' spinner
await page.getByTestId('loading-stream').waitFor()
// wait for all spinners to be gone
await page
.getByTestId('loading')
.waitFor({ state: 'detached', timeout: 20_000 })
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
await page.getByTestId('start-sketch').waitFor()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeEnabled({
timeout: 20_000,
})
}
async function removeCurrentCode(page: Page) {
@ -58,44 +58,45 @@ async function waitForDefaultPlanesToBeVisible(page: Page) {
}
async function openKclCodePanel(page: Page) {
const paneLocator = page.getByRole('tab', { name: 'KCL Code', exact: false })
const isOpen = (await paneLocator?.getAttribute('aria-selected')) === 'true'
const paneLocator = page.getByTestId('code-pane-button')
const ariaSelected = await paneLocator?.getAttribute('aria-pressed')
const isOpen = ariaSelected === 'true'
if (!isOpen) {
await paneLocator.click()
await paneLocator.and(page.locator('[aria-selected="true"]')).waitFor()
await expect(paneLocator).toHaveAttribute('aria-pressed', 'true')
}
}
async function closeKclCodePanel(page: Page) {
const paneLocator = page.getByRole('tab', { name: 'KCL Code', exact: false })
const isOpen = (await paneLocator?.getAttribute('aria-selected')) === 'true'
const paneLocator = page.getByTestId('code-pane-button')
const ariaSelected = await paneLocator?.getAttribute('aria-pressed')
const isOpen = ariaSelected === 'true'
if (isOpen) {
await paneLocator.click()
await paneLocator
.and(page.locator(':not([aria-selected="true"])'))
.waitFor()
await expect(paneLocator).not.toHaveAttribute('aria-pressed', 'true')
}
}
async function openDebugPanel(page: Page) {
const debugLocator = page.getByRole('tab', { name: 'Debug', exact: false })
const isOpen = (await debugLocator?.getAttribute('aria-selected')) === 'true'
const debugLocator = page.getByTestId('debug-pane-button')
await expect(debugLocator).toBeVisible()
const isOpen = (await debugLocator?.getAttribute('aria-pressed')) === 'true'
if (!isOpen) {
await debugLocator.click()
await debugLocator.and(page.locator('[aria-selected="true"]')).waitFor()
await expect(debugLocator).toHaveAttribute('aria-pressed', 'true')
}
}
async function closeDebugPanel(page: Page) {
const debugLocator = page.getByRole('tab', { name: 'Debug', exact: false })
const isOpen = (await debugLocator?.getAttribute('aria-selected')) === 'true'
const debugLocator = page.getByTestId('debug-pane-button')
await expect(debugLocator).toBeVisible()
const isOpen = (await debugLocator?.getAttribute('aria-pressed')) === 'true'
if (isOpen) {
await debugLocator.click()
await debugLocator
.and(page.locator(':not([aria-selected="true"])'))
.waitFor()
await expect(debugLocator).not.toHaveAttribute('aria-pressed', 'true')
}
}
@ -471,8 +472,11 @@ export const doExport = async (
page: Page
): Promise<Paths> => {
await page.getByRole('button', { name: APP_NAME }).click()
await expect(page.getByRole('button', { name: 'Export Part' })).toBeVisible()
await page.getByRole('button', { name: 'Export Part' }).click()
const exportMenuButton = page.getByRole('button', {
name: 'Export current part',
})
await expect(exportMenuButton).toBeVisible()
await exportMenuButton.click()
await expect(page.getByTestId('command-bar')).toBeVisible()
// Go through export via command bar

View File

@ -77,7 +77,7 @@ describe('ZMA authorized user flows', () => {
const menuButton = await $('[data-testid="user-sidebar-toggle"]')
await click(menuButton)
const settingsButton = await $('[data-testid="settings-button"]')
const settingsButton = await $('[data-testid="user-settings"]')
await click(settingsButton)
const projectDirInput = await $('[data-testid="project-directory-input"]')

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.24.2",
"version": "0.24.4",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.17.0",

View File

@ -17,6 +17,7 @@ import type {
PluginSpec,
ViewPlugin,
} from '@codemirror/view'
import { setDiagnosticsEffect } from '@codemirror/lint'
import { EditorView, Tooltip } from '@codemirror/view'
import type { PublishDiagnosticsParams } from 'vscode-languageserver-protocol'
@ -214,6 +215,21 @@ export class LanguageServerPlugin implements PluginValue {
}
if (!this.client.ready) return
// TODO(paultag): This is the *wrong* place for this to live.
//
// We need to clear diagnostics before updating the code, because
// if the code shrinks, the errors/diagnostics can go out of range
// of the source code, which cause an exception, breaking all the
// things.
//
// We need some sort of clear diagnostics boolean on the editor
// and we can drop this.
this.view.dispatch({
effects: [setDiagnosticsEffect.of([])],
annotations: [],
})
try {
// Update the state (not the editor) with the new code.
this.client.textDocumentDidChange({

108
src-tauri/Cargo.lock generated
View File

@ -332,7 +332,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -367,7 +367,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -407,7 +407,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -550,7 +550,7 @@ dependencies = [
"proc-macro-crate 3.1.0",
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
"syn_derive",
]
@ -823,7 +823,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1073,7 +1073,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1083,7 +1083,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
dependencies = [
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1107,7 +1107,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1118,7 +1118,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1179,7 +1179,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
"synstructure",
]
@ -1216,7 +1216,7 @@ dependencies = [
"regex",
"serde",
"serde_tokenstream",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1227,7 +1227,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1288,7 +1288,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1320,7 +1320,7 @@ checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1427,7 +1427,7 @@ checksum = "5c785274071b1b420972453b306eeca06acf4633829db4223b58a2a8c5953bc4"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1588,7 +1588,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1704,7 +1704,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -1980,7 +1980,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -2008,7 +2008,7 @@ dependencies = [
"inflections",
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -2083,7 +2083,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -3377,7 +3377,7 @@ dependencies = [
"regex",
"regex-syntax 0.8.3",
"structmeta",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -3496,7 +3496,7 @@ dependencies = [
"phf_shared 0.11.2",
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -3564,7 +3564,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -4438,7 +4438,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -4558,7 +4558,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -4569,7 +4569,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -4602,7 +4602,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -4623,7 +4623,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -4665,7 +4665,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -4933,7 +4933,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta-derive",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -4944,7 +4944,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -4966,7 +4966,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -4999,9 +4999,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.70"
version = "2.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16"
checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462"
dependencies = [
"proc-macro2",
"quote",
@ -5017,7 +5017,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -5034,7 +5034,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -5251,7 +5251,7 @@ dependencies = [
"serde",
"serde_json",
"sha2",
"syn 2.0.70",
"syn 2.0.71",
"tauri-utils",
"thiserror",
"time",
@ -5269,7 +5269,7 @@ dependencies = [
"heck 0.5.0",
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
"tauri-codegen",
"tauri-utils",
]
@ -5627,22 +5627,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
[[package]]
name = "thiserror"
version = "1.0.61"
version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
checksum = "f2675633b1499176c2dff06b0856a27976a8f9d436737b4cf4f312d4d91d8bbb"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.61"
version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -5740,7 +5740,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -5940,7 +5940,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -5969,7 +5969,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -6099,7 +6099,7 @@ checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
"termcolor",
]
@ -6316,7 +6316,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -6415,7 +6415,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
"wasm-bindgen-shared",
]
@ -6449,7 +6449,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -6590,7 +6590,7 @@ checksum = "ac1345798ecd8122468840bcdf1b95e5dc6d2206c5e4b0eafa078d061f59c9bc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -6696,7 +6696,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -6707,7 +6707,7 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]
@ -7159,7 +7159,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.70",
"syn 2.0.71",
]
[[package]]

View File

@ -80,5 +80,5 @@
}
},
"productName": "Zoo Modeling App",
"version": "0.24.2"
"version": "0.24.4"
}

View File

@ -44,7 +44,7 @@ export function App() {
}, [projectName, projectPath])
useHotKeyListener()
const { context } = useModelingContext()
const { context, state } = useModelingContext()
const { auth, settings } = useSettingsAuthContext()
const token = auth?.context?.token
@ -57,7 +57,6 @@ export function App() {
const {
app: { onboardingStatus },
} = settings.context
const { state } = useModelingContext()
useHotkeys('backspace', (e) => {
e.preventDefault()

View File

@ -1,4 +1,4 @@
import { WheelEvent, useRef, useMemo } from 'react'
import { useRef, useMemo, memo } from 'react'
import { isCursorInSketchCommandRange } from 'lang/util'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { useModelingContext } from 'hooks/useModelingContext'
@ -12,11 +12,14 @@ import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
import { useHotkeys } from 'react-hotkeys-hook'
import Tooltip from 'components/Tooltip'
import { useAppState } from 'AppState'
import { CustomIcon } from 'components/CustomIcon'
import {
canRectangleTool,
isEditingExistingSketch,
} from 'machines/modelingMachine'
import { DEV } from 'env'
toolbarConfig,
ToolbarItem,
ToolbarItemCallbackProps,
ToolbarItemResolved,
ToolbarModeName,
} from 'lib/toolbar'
export function Toolbar({
className = '',
@ -25,12 +28,14 @@ export function Toolbar({
const { state, send, context } = useModelingContext()
const { commandBarSend } = useCommandsContext()
const iconClassName =
'group-disabled:text-chalkboard-50 group-enabled:group-hover:!text-primary dark:group-enabled:group-hover:!text-inherit group-pressed:!text-chalkboard-10 group-ui-open:!text-chalkboard-10 dark:group-ui-open:!text-chalkboard-10'
const bgClassName =
'group-disabled:!bg-transparent group-enabled:group-hover:bg-primary/10 dark:group-enabled:group-hover:bg-primary group-pressed:bg-primary group-ui-open:bg-primary'
const buttonClassName =
'bg-chalkboard-10 dark:bg-chalkboard-100 enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!border-primary ui-open:!border-primary'
const pathId = useMemo(() => {
'group-disabled:text-chalkboard-50 !text-inherit dark:group-enabled:group-hover:!text-inherit'
const bgClassName = '!bg-transparent'
const buttonBgClassName =
'bg-chalkboard-transparent dark:bg-transparent disabled:bg-transparent dark:disabled:bg-transparent enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!bg-primary pressed:enabled:hover:!text-chalkboard-10'
const buttonBorderClassName =
'!border-transparent hover:!border-chalkboard-20 dark:enabled:hover:!border-primary pressed:!border-primary ui-open:!border-primary'
const sketchPathId = useMemo(() => {
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
return false
}
@ -51,401 +56,292 @@ export function Toolbar({
isExecuting ||
!isStreamReady
const disableLineButton =
state.matches('Sketch.Rectangle tool.Awaiting second corner') ||
disableAllButtons
useHotkeys(
'l',
() =>
state.matches('Sketch.Line tool')
? send('CancelSketch')
: send({
type: 'change tool',
data: { tool: 'line' },
}),
{ enabled: !disableLineButton, scopes: ['sketch'] }
)
const disableTangentialArc =
(!isEditingExistingSketch(context) &&
!state.matches('Sketch.Tangential arc to')) ||
disableAllButtons
useHotkeys(
'a',
() =>
state.matches('Sketch.Tangential arc to')
? send('CancelSketch')
: send({
type: 'change tool',
data: { tool: 'tangentialArc' },
}),
{ enabled: !disableTangentialArc, scopes: ['sketch'] }
)
const disableRectangle =
(!canRectangleTool(context) && !state.matches('Sketch.Rectangle tool')) ||
disableAllButtons
useHotkeys(
'r',
() =>
state.matches('Sketch.Rectangle tool')
? send('CancelSketch')
: send({
type: 'change tool',
data: { tool: 'rectangle' },
}),
{ enabled: !disableRectangle, scopes: ['sketch'] }
)
useHotkeys(
's',
() =>
state.nextEvents.includes('Enter sketch') && pathId
? send({ type: 'Enter sketch' })
: send({ type: 'Enter sketch', data: { forceNewSketch: true } }),
{ enabled: !disableAllButtons, scopes: ['modeling'] }
)
useHotkeys(
'esc',
() =>
['Sketch no face', 'Sketch.SketchIdle'].some(state.matches)
? send('Cancel')
: send('CancelSketch'),
{ enabled: !disableAllButtons, scopes: ['sketch'] }
)
useHotkeys(
'e',
() =>
commandBarSend({
type: 'Find and select command',
data: { name: 'Extrude', groupId: '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'] }
const currentMode =
(Object.entries(toolbarConfig).find(([_, mode]) =>
mode.check(state)
)?.[0] as ToolbarModeName) || 'modeling'
/** These are the props that will be passed to the callbacks in the toolbar config
* They are memoized to prevent unnecessary re-renders,
* but they still get a lot of churn from the state machine
* so I think there's a lot of room for improvement here
*/
const configCallbackProps: ToolbarItemCallbackProps = useMemo(
() => ({
modelingStateMatches: state.matches,
modelingSend: send,
commandBarSend,
sketchPathId,
}),
[state.matches, send, commandBarSend, sketchPathId]
)
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
const span = toolbarButtonsRef.current
if (!span) {
return
/**
* Resolve all the callbacks and values for the current mode,
* so we don't need to worry about the other modes
*/
const currentModeItems: (
| ToolbarItemResolved
| ToolbarItemResolved[]
| 'break'
)[] = useMemo(() => {
return toolbarConfig[currentMode].items.map((maybeIconConfig) => {
if (maybeIconConfig === 'break') {
return 'break'
} else if (Array.isArray(maybeIconConfig)) {
return maybeIconConfig.map(resolveItemConfig)
} else {
return resolveItemConfig(maybeIconConfig)
}
})
function resolveItemConfig(
maybeIconConfig: ToolbarItem
): ToolbarItemResolved {
return {
...maybeIconConfig,
title:
typeof maybeIconConfig.title === 'string'
? maybeIconConfig.title
: maybeIconConfig.title(configCallbackProps),
description: maybeIconConfig.description,
links: maybeIconConfig.links || [],
isActive: maybeIconConfig.isActive?.(state),
hotkey:
typeof maybeIconConfig.hotkey === 'string'
? maybeIconConfig.hotkey
: maybeIconConfig.hotkey?.(state),
disabled:
disableAllButtons ||
maybeIconConfig.status !== 'available' ||
maybeIconConfig.disabled?.(state) === true,
disableHotkey: maybeIconConfig.disableHotkey?.(state),
status: maybeIconConfig.status,
}
}
}, [currentMode, disableAllButtons, configCallbackProps])
span.scrollLeft = span.scrollLeft += ev.deltaY
}
const nextEvents = useMemo(() => state.nextEvents, [state.nextEvents])
const splitMenuItems = useMemo(
() =>
nextEvents
.filter(
(eventName) =>
eventName.includes('Make segment') ||
eventName.includes('Constrain')
)
.sort((a, b) => {
const aisEnabled = nextEvents
.filter((event) => state.can(event as any))
.includes(a)
const bIsEnabled = nextEvents
.filter((event) => state.can(event as any))
.includes(b)
if (aisEnabled && !bIsEnabled) {
return -1
}
if (!aisEnabled && bIsEnabled) {
return 1
}
return 0
})
.map((eventName) => ({
label: eventName
.replace('Make segment ', '')
.replace('Constrain ', ''),
onClick: () => send(eventName),
disabled:
!nextEvents
.filter((event) => state.can(event as any))
.includes(eventName) || disableAllButtons,
})),
[JSON.stringify(nextEvents), state]
)
return (
<menu className="max-w-full whitespace-nowrap rounded px-1.5 py-0.5 backdrop-blur-sm bg-chalkboard-10/80 dark:bg-chalkboard-110/70 relative">
<menu className="max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-20 dark:border-chalkboard-80 border-t-0 shadow-sm">
<ul
{...props}
ref={toolbarButtonsRef}
onWheel={handleToolbarButtonsWheelEvent}
className={'m-0 py-1 rounded-l-sm flex gap-2 items-center ' + className}
style={{ scrollbarWidth: 'thin' }}
className={
'has-[[aria-expanded=true]]:!pointer-events-none m-0 py-1 rounded-l-sm flex gap-1.5 items-center ' +
className
}
>
{nextEvents.includes('Enter sketch') && (
<li className="contents">
<ActionButton
className={buttonClassName}
Element="button"
onClick={() =>
send({ type: 'Enter sketch', data: { forceNewSketch: true } })
}
iconStart={{
icon: 'sketch',
iconClassName,
bgClassName,
}}
disabled={disableAllButtons}
>
<span data-testid="start-sketch">Start Sketch</span>
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: S
</Tooltip>
</ActionButton>
</li>
)}
{nextEvents.includes('Enter sketch') && pathId && (
<li className="contents">
<ActionButton
className={buttonClassName}
Element="button"
onClick={() => send({ type: 'Enter sketch' })}
iconStart={{
icon: 'sketch',
iconClassName,
bgClassName,
}}
disabled={disableAllButtons}
>
Edit Sketch
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: S
</Tooltip>
</ActionButton>
</li>
)}
{nextEvents.includes('Cancel') && !state.matches('idle') && (
<li className="contents">
<ActionButton
className={buttonClassName}
Element="button"
onClick={() => send({ type: 'Cancel' })}
iconStart={{
icon: 'arrowLeft',
iconClassName,
bgClassName,
}}
disabled={disableAllButtons}
>
Exit Sketch
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: Esc
</Tooltip>
</ActionButton>
</li>
)}
{state.matches('Sketch no face') && (
<li className="contents">
<div className="mx-2 text-sm">click plane or face to sketch on</div>
</li>
)}
{state.matches('Sketch') && !state.matches('idle') && (
<>
<li className="contents" key="line-button">
<ActionButton
className={buttonClassName}
{/* A menu item will either be a vertical line break, a button with a dropdown, or a single button */}
{currentModeItems.map((maybeIconConfig, i) => {
if (maybeIconConfig === 'break') {
return (
<div
key={'break-' + i}
className="h-5 w-[1px] block bg-chalkboard-30 dark:bg-chalkboard-80"
/>
)
} else if (Array.isArray(maybeIconConfig)) {
return (
<ActionButtonDropdown
Element="button"
onClick={() =>
state?.matches('Sketch.Line tool')
? send('CancelSketch')
: send({
type: 'change tool',
data: { tool: 'line' },
})
key={maybeIconConfig[0].id}
data-testid={maybeIconConfig[0].id + '-dropdown'}
id={maybeIconConfig[0].id + '-dropdown'}
name={maybeIconConfig[0].title}
className={
'group/wrapper ' +
buttonBorderClassName +
' !bg-transparent relative group !gap-0'
}
aria-pressed={state?.matches('Sketch.Line tool')}
iconStart={{
icon: 'line',
iconClassName,
bgClassName,
}}
disabled={disableLineButton}
splitMenuItems={maybeIconConfig.map((itemConfig) => ({
id: itemConfig.id,
label: itemConfig.title,
hotkey: itemConfig.hotkey,
onClick: () => itemConfig.onClick(configCallbackProps),
disabled:
disableAllButtons ||
itemConfig.status !== 'available' ||
itemConfig.disabled === true,
status: itemConfig.status,
}))}
>
Line
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
<ActionButton
Element="button"
id={maybeIconConfig[0].id}
data-testid={maybeIconConfig[0].id}
iconStart={{
icon: maybeIconConfig[0].icon,
className: iconClassName,
bgClassName: bgClassName,
}}
className={
'!border-transparent !px-0 pressed:!text-chalkboard-10 pressed:enabled:hovered:!text-chalkboard-10 ' +
buttonBgClassName
}
aria-pressed={maybeIconConfig[0].isActive}
disabled={
disableAllButtons ||
maybeIconConfig[0].status !== 'available' ||
maybeIconConfig[0].disabled
}
name={maybeIconConfig[0].title}
aria-description={maybeIconConfig[0].description}
onClick={() =>
maybeIconConfig[0].onClick(configCallbackProps)
}
>
Shortcut: L
</Tooltip>
</ActionButton>
</li>
<li className="contents" key="tangential-arc-button">
<ActionButton
className={buttonClassName}
Element="button"
onClick={() =>
state.matches('Sketch.Tangential arc to')
? send('CancelSketch')
: send({
type: 'change tool',
data: { tool: 'tangentialArc' },
})
}
aria-pressed={state.matches('Sketch.Tangential arc to')}
iconStart={{
icon: 'arc',
iconClassName,
bgClassName,
}}
disabled={disableTangentialArc}
>
Tangential Arc
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: A
</Tooltip>
</ActionButton>
</li>
<li className="contents" key="rectangle-button">
<ActionButton
className={buttonClassName}
Element="button"
onClick={() =>
state.matches('Sketch.Rectangle tool')
? send('CancelSketch')
: send({
type: 'change tool',
data: { tool: 'rectangle' },
})
}
aria-pressed={state.matches('Sketch.Rectangle tool')}
iconStart={{
icon: 'rectangle',
iconClassName,
bgClassName,
}}
disabled={disableRectangle}
title={
canRectangleTool(context)
? 'Rectangle'
: 'Can only be used when a sketch is empty currently'
}
>
Rectangle
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: R
</Tooltip>
</ActionButton>
</li>
</>
)}
{state.matches('Sketch.SketchIdle') &&
nextEvents.filter(
(eventName) =>
eventName.includes('Make segment') ||
eventName.includes('Constrain')
).length > 0 && (
<ActionButtonDropdown
splitMenuItems={splitMenuItems}
className={buttonClassName}
Element="button"
iconStart={{
icon: 'dimension',
iconClassName,
bgClassName,
}}
>
Constraints
</ActionButtonDropdown>
)}
{state.matches('idle') && (
<li className="contents">
<ToolbarItemContents
itemConfig={maybeIconConfig[0]}
configCallbackProps={configCallbackProps}
/>
</ActionButton>
</ActionButtonDropdown>
)
}
const itemConfig = maybeIconConfig
return (
<ActionButton
className={buttonClassName}
Element="button"
onClick={() =>
commandBarSend({
type: 'Find and select command',
data: { name: 'Extrude', groupId: 'modeling' },
})
}
disabled={!state.can('Extrude') || disableAllButtons}
title={
state.can('Extrude')
? 'extrude'
: 'sketches need to be closed, or not already extruded'
}
key={itemConfig.id}
id={itemConfig.id}
data-testid={itemConfig.id}
iconStart={{
icon: 'extrude',
iconClassName,
bgClassName,
icon: itemConfig.icon,
className: iconClassName,
bgClassName: bgClassName,
}}
>
Extrude
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: E
</Tooltip>
</ActionButton>
</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' },
})
className={
'pressed:!text-chalkboard-10 pressed:enabled:hovered:!text-chalkboard-10 ' +
buttonBorderClassName +
' ' +
buttonBgClassName +
(!itemConfig.showTitle ? ' !px-0' : '')
}
disabled={disableFillet}
title={disableFillet ? 'fillet' : "edge can't be filleted"}
iconStart={{
icon: 'fillet', // todo: add fillet icon
iconClassName,
bgClassName,
}}
name={itemConfig.title}
aria-description={itemConfig.description}
aria-pressed={itemConfig.isActive}
disabled={
disableAllButtons ||
itemConfig.status !== 'available' ||
itemConfig.disabled
}
onClick={() => itemConfig.onClick(configCallbackProps)}
>
Fillet
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: F
</Tooltip>
<ToolbarItemContents
itemConfig={itemConfig}
configCallbackProps={configCallbackProps}
/>
</ActionButton>
</li>
)}
)
})}
</ul>
{state.matches('Sketch no face') && (
<div className="absolute top-full left-1/2 -translate-x-1/2 mt-2 py-1 px-2 bg-chalkboard-10 dark:bg-chalkboard-90 border border-chalkboard-20 dark:border-chalkboard-80 rounded shadow-lg">
<p className="text-xs">Select a plane or face to start sketching</p>
</div>
)}
</menu>
)
}
/**
* The single button and dropdown button share content, so we extract it here
* It contains a tooltip with the title, description, and links
* and a hotkey listener
*/
const ToolbarItemContents = memo(function ToolbarItemContents({
itemConfig,
configCallbackProps,
}: {
itemConfig: ToolbarItemResolved
configCallbackProps: ToolbarItemCallbackProps
}) {
useHotkeys(
itemConfig.hotkey || '',
() => {
itemConfig.onClick(configCallbackProps)
},
{
enabled:
itemConfig.status === 'available' &&
!!itemConfig.hotkey &&
!itemConfig.disabled &&
!itemConfig.disableHotkey,
}
)
return (
<>
<span className={!itemConfig.showTitle ? 'sr-only' : ''}>
{itemConfig.title}
</span>
<Tooltip
position="bottom"
wrapperClassName="!p-4 !pointer-events-auto"
contentClassName="!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch"
>
<div className="rounded-top flex items-center gap-2 pt-3 pb-2 px-2 bg-chalkboard-20/50 dark:bg-chalkboard-80/50">
<span
className={`text-sm flex-1 ${
itemConfig.status !== 'available'
? 'text-chalkboard-70 dark:text-chalkboard-40'
: ''
}`}
>
{itemConfig.title}
</span>
{itemConfig.status === 'available' && itemConfig.hotkey ? (
<kbd className="flex-none hotkey">{itemConfig.hotkey}</kbd>
) : itemConfig.status === 'kcl-only' ? (
<>
<span className="text-wrap font-sans flex-0 text-chalkboard-70 dark:text-chalkboard-40">
KCL code only
</span>
<CustomIcon
name="code"
className="w-5 h-5 text-chalkboard-70 dark:text-chalkboard-40"
/>
</>
) : (
itemConfig.status === 'unavailable' && (
<>
<span className="text-wrap font-sans flex-0 text-chalkboard-70 dark:text-chalkboard-40">
In development
</span>
<CustomIcon
name="lockClosed"
className="w-5 h-5 text-chalkboard-70 dark:text-chalkboard-40"
/>
</>
)
)}
</div>
<p className="px-2 text-ch font-sans">{itemConfig.description}</p>
{itemConfig.links.length > 0 && (
<>
<hr className="border-chalkboard-20 dark:border-chalkboard-80" />
<ul className="p-0 px-1 m-0 flex flex-col">
{itemConfig.links.map((link) => (
<li key={link.label} className="contents">
<a
href={link.url}
target="_blank"
rel="noreferrer"
className="flex items-center rounded-sm p-1 no-underline text-inherit hover:bg-primary/10 hover:text-primary dark:hover:bg-chalkboard-70 dark:hover:text-inherit"
onClickCapture={(e) =>
e.nativeEvent.stopImmediatePropagation()
}
>
<span className="flex-1">Open {link.label}</span>
<CustomIcon name="link" className="w-4 h-4" />
</a>
</li>
))}
</ul>
</>
)}
</Tooltip>
</>
)
})

View File

@ -47,7 +47,6 @@ import {
PipeExpression,
Program,
ProgramMemory,
programMemoryInit,
recast,
SketchGroup,
ExtrudeGroup,
@ -130,7 +129,7 @@ export const HIDE_HOVER_SEGMENT_LENGTH = 60 // in pixels
export class SceneEntities {
engineCommandManager: EngineCommandManager
scene: Scene
sceneProgramMemory: ProgramMemory = { root: {}, return: null }
sceneProgramMemory: ProgramMemory = ProgramMemory.empty()
activeSegments: { [key: string]: Group } = {}
intersectionPlane: Mesh | null = null
axisGroup: Group | null = null
@ -550,9 +549,9 @@ export class SceneEntities {
const variableDeclarationName =
_node1.node?.declarations?.[0]?.id?.name || ''
const sg = kclManager.programMemory.root[
const sg = kclManager.programMemory.get(
variableDeclarationName
] as SketchGroup
) as SketchGroup
const lastSeg = sg.value.slice(-1)[0] || sg.start
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
@ -768,9 +767,9 @@ export class SceneEntities {
programMemoryOverride,
})
this.sceneProgramMemory = programMemory
const sketchGroup = programMemory.root[
const sketchGroup = programMemory.get(
variableDeclarationName
] as SketchGroup
) as SketchGroup
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -820,9 +819,9 @@ export class SceneEntities {
// Prepare to update the THREEjs scene
this.sceneProgramMemory = programMemory
const sketchGroup = programMemory.root[
const sketchGroup = programMemory.get(
variableDeclarationName
] as SketchGroup
) as SketchGroup
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
@ -1081,9 +1080,9 @@ export class SceneEntities {
})
this.sceneProgramMemory = programMemory
const maybeSketchGroup = programMemory.root[variableDeclarationName]
const maybeSketchGroup = programMemory.get(variableDeclarationName)
let sketchGroup = undefined
if (maybeSketchGroup.type === 'SketchGroup') {
if (maybeSketchGroup?.type === 'SketchGroup') {
sketchGroup = maybeSketchGroup
} else if ((maybeSketchGroup as ExtrudeGroup).sketchGroup) {
sketchGroup = (maybeSketchGroup as ExtrudeGroup).sketchGroup
@ -1773,7 +1772,7 @@ function prepareTruncatedMemoryAndAst(
if (err(_node)) return _node
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
const lastSeg = (
programMemory.root[variableDeclarationName] as SketchGroup
programMemory.get(variableDeclarationName) as SketchGroup
).value.slice(-1)[0]
if (draftSegment) {
// truncatedAst needs to setup with another segment at the end
@ -1824,33 +1823,27 @@ function prepareTruncatedMemoryAndAst(
..._ast,
body: [JSON.parse(JSON.stringify(_ast.body[bodyIndex]))],
}
const programMemoryOverride = programMemoryInit()
if (err(programMemoryOverride)) return programMemoryOverride
// Grab all the TagDeclarators and TagIdentifiers from memory.
let start = _node.node.start
for (const key in programMemory.root) {
const value = programMemory.root[key]
if (!('__meta' in value)) {
continue
}
const programMemoryOverride = programMemory.filterVariables(true, (value) => {
if (
!('__meta' in value) ||
value.__meta === undefined ||
value.__meta.length === 0 ||
value.__meta[0].sourceRange === undefined
) {
continue
return false
}
if (value.__meta[0].sourceRange[0] >= start) {
// We only want things before our start point.
continue
return false
}
if (value.type === 'TagIdentifier') {
programMemoryOverride.root[key] = JSON.parse(JSON.stringify(value))
}
}
return value.type === 'TagIdentifier'
})
if (err(programMemoryOverride)) return programMemoryOverride
for (let i = 0; i < bodyIndex; i++) {
const node = _ast.body[i]
@ -1858,12 +1851,15 @@ function prepareTruncatedMemoryAndAst(
continue
}
const name = node.declarations[0].id.name
// const memoryItem = kclManager.programMemory.root[name]
const memoryItem = programMemory.root[name]
const memoryItem = programMemory.get(name)
if (!memoryItem) {
continue
}
programMemoryOverride.root[name] = JSON.parse(JSON.stringify(memoryItem))
const error = programMemoryOverride.set(
name,
JSON.parse(JSON.stringify(memoryItem))
)
if (err(error)) return error
}
return {
truncatedAst,
@ -1900,7 +1896,7 @@ export function sketchGroupFromPathToNode({
)
if (err(_varDec)) return _varDec
const varDec = _varDec.node
const result = programMemory.root[varDec?.id?.name || '']
const result = programMemory.get(varDec?.id?.name || '')
if (result?.type === 'ExtrudeGroup') {
return result.sketchGroup
}
@ -2026,13 +2022,17 @@ export async function getFaceDetails(
entity_id: entityId,
},
})
const faceInfo: Models['GetSketchModePlane_type'] = (
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'get_sketch_mode_plane' },
})
)?.data?.data
const resp = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'get_sketch_mode_plane' },
})
const faceInfo =
resp?.success &&
resp?.resp.type === 'modeling' &&
resp?.resp?.data?.modeling_response?.type === 'get_sketch_mode_plane'
? resp?.resp?.data?.modeling_response.data
: ({} as Models['GetSketchModePlane_type'])
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),

View File

@ -1,56 +1,93 @@
import { Popover } from '@headlessui/react'
import { ActionButton, ActionButtonProps } from './ActionButton'
import { ActionButtonProps } from './ActionButton'
import { CustomIcon } from './CustomIcon'
type ActionButtonSplitProps = Omit<ActionButtonProps, 'iconEnd'> & {
type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & {
name?: string
splitMenuItems: {
id: string
label: string
shortcut?: string
onClick: () => void
disabled?: boolean
status?: 'available' | 'unavailable' | 'kcl-only'
}[]
}
export function ActionButtonDropdown({
splitMenuItems,
className,
children,
...props
}: ActionButtonSplitProps) {
const baseClassNames = `action-button p-0 m-0 group mono text-xs leading-none flex items-center gap-2 rounded-sm border-solid border border-chalkboard-30 hover:border-chalkboard-40 enabled:dark:border-chalkboard-70 dark:hover:border-chalkboard-60 dark:bg-chalkboard-90/50 text-chalkboard-100 dark:text-chalkboard-10`
return (
<Popover className="relative">
<Popover.Button
as={ActionButton}
className={className}
{...props}
Element="button"
iconEnd={{
icon: 'caretDown',
className: 'ui-open:rotate-180',
bgClassName:
'bg-chalkboard-20 dark:bg-chalkboard-80 ui-open:bg-primary ui-open:text-chalkboard-10',
}}
/>
<Popover.Panel
as="ul"
className="absolute z-20 left-1/2 -translate-x-1/2 top-full mt-1 w-fit max-h-[80vh] overflow-y-auto py-2 flex flex-col gap-1 align-stretch text-inherit dark:text-chalkboard-10 bg-chalkboard-10 dark:bg-chalkboard-100 rounded shadow-lg border border-solid border-chalkboard-30 dark:border-chalkboard-80 text-sm m-0 p-0"
>
{splitMenuItems.map((item) => (
<li className="contents" key={item.label}>
<button
onClick={item.onClick}
className="block px-3 py-1 hover:bg-primary/10 dark:hover:bg-chalkboard-80 border-0 m-0 text-sm w-full rounded-none text-left disabled:!bg-transparent dark:disabled:text-chalkboard-60"
disabled={item.disabled}
data-testid={item.label}
>
<span className="capitalize">{item.label}</span>
{item.shortcut && (
<kbd className="bg-primary/10 dark:bg-chalkboard-80 dark:group-hover:bg-primary font-mono rounded-sm dark:text-inherit inline-block px-1 border-primary dark:border-chalkboard-90">
{item.shortcut}
</kbd>
)}
</button>
</li>
))}
</Popover.Panel>
<Popover className={`${baseClassNames} ${className}`}>
{({ close }) => (
<>
{children}
<Popover.Button className="border-transparent dark:border-transparent p-0 m-0 rounded-none !outline-none ui-open:border-primary ui-open:bg-primary">
<CustomIcon
name="caretDown"
className={
'w-3.5 h-5 text-chalkboard-70 dark:text-chalkboard-40 rounded-none ' +
'ui-open:rotate-180 ui-open:!text-chalkboard-10'
}
/>
<span className="sr-only">
{props.name ? props.name + ': ' : ''}open menu
</span>
</Popover.Button>
<Popover.Panel
as="ul"
className="!pointer-events-auto absolute z-20 left-1/2 -translate-x-1/2 top-full mt-4 w-fit max-w-[280px] max-h-[80vh] overflow-y-auto py-2 flex flex-col align-stretch text-inherit dark:text-chalkboard-10 bg-chalkboard-10 dark:bg-chalkboard-100 rounded shadow-lg border border-solid border-chalkboard-30 dark:border-chalkboard-80 text-sm m-0 p-0"
>
{splitMenuItems.map((item) => (
<li className="contents" key={item.label}>
<button
onClick={() => {
item.onClick()
// Close the popover
close()
}}
className="group/button flex items-center gap-6 px-3 py-1 font-sans text-xs hover:bg-primary/10 dark:hover:bg-chalkboard-80 border-0 m-0 w-full rounded-none text-left disabled:!bg-transparent dark:disabled:text-chalkboard-60"
disabled={item.disabled}
data-testid={'dropdown-' + item.id}
>
<span className="capitalize flex-grow text-left">
{item.label}
</span>
{item.status === 'unavailable' ? (
<div className="flex flex-none items-center gap-1">
<span className="text-chalkboard-70 dark:text-chalkboard-40">
In development
</span>
<CustomIcon
name="lockClosed"
className="w-4 h-4 text-chalkboard-70 dark:text-chalkboard-40"
/>
</div>
) : item.status === 'kcl-only' ? (
<div className="flex flex-none items-center gap-1">
<span className="text-chalkboard-70 dark:text-chalkboard-40">
KCL code only
</span>
<CustomIcon
name="code"
className="w-4 h-4 text-chalkboard-70 dark:text-chalkboard-40"
/>
</div>
) : item.shortcut ? (
<kbd className="hotkey flex-none group-disabled/button:text-chalkboard-50 dark:group-disabled/button:text-chalkboard-70 group-disabled/button:border-chalkboard-20 dark:group-disabled/button:border-chalkboard-80">
{item.shortcut}
</kbd>
) : null}
</button>
</li>
))}
</Popover.Panel>
</>
)}
</Popover>
)
}

View File

@ -30,7 +30,7 @@ export const AppHeader = ({
className={
'w-full grid ' +
styles.header +
' overlaid-panes sticky top-0 z-20 px-2 items-center ' +
' overlaid-panes sticky top-0 z-20 px-2 items-start ' +
className
}
>
@ -49,9 +49,9 @@ export const AppHeader = ({
<>
<CommandBarOpenButton />
<RefreshButton />
<UserSidebarMenu user={user} />
</>
)}
<UserSidebarMenu user={user} />
</div>
</header>
)

View File

@ -1,5 +1,5 @@
import { useEffect, useState, useRef } from 'react'
import { parse, BinaryPart, Value } from '../lang/wasm'
import { parse, BinaryPart, Value, ProgramMemory } from '../lang/wasm'
import {
createIdentifier,
createLiteral,
@ -120,8 +120,7 @@ export function useCalc({
}, [])
useEffect(() => {
const allVarNames = Object.keys(programMemory.root)
if (allVarNames.includes(newVariableName)) {
if (programMemory.has(newVariableName)) {
setIsNewVariableNameUnique(false)
} else {
setIsNewVariableNameUnique(true)
@ -143,17 +142,20 @@ export function useCalc({
const code = `const __result__ = ${value}`
const ast = parse(code)
if (trap(ast)) return
const _programMem: any = { root: {}, return: null }
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
})
const _programMem: ProgramMemory = ProgramMemory.empty()
for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, {
type: 'UserVal',
value,
__meta: [],
})
if (trap(error)) return
}
executeAst({
ast,
engineCommandManager,
useFakeExecutor: true,
programMemoryOverride: JSON.parse(
JSON.stringify(kclManager.programMemory)
),
programMemoryOverride: kclManager.programMemory.clone(),
}).then(({ programMemory }) => {
const resultDeclaration = ast.body.find(
(a) =>
@ -163,7 +165,7 @@ export function useCalc({
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init
const result = programMemory?.root?.__result__?.value
const result = programMemory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
})

View File

@ -6,7 +6,7 @@ const CustomIconMap = {
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 1.5C8.60217 1.5 7.22591 1.84474 5.99313 2.50367C4.76035 3.1626 3.70911 4.1154 2.93251 5.27765C2.15592 6.43991 1.67794 7.77575 1.54093 9.16685C1.40392 10.558 1.6121 11.9614 2.14703 13.2528C2.68195 14.5442 3.52712 15.6838 4.60766 16.5706C5.6882 17.4574 6.97076 18.064 8.34173 18.3367C9.71271 18.6094 11.1298 18.5398 12.4674 18.134C13.8051 17.7282 15.022 16.9988 16.0104 16.0104C16.3068 15.714 16.5796 15.3974 16.8273 15.0634L16.0241 14.4677C15.8055 14.7624 15.5648 15.0418 15.3033 15.3033C14.4312 16.1754 13.3574 16.819 12.1771 17.1771C10.9969 17.5351 9.74651 17.5965 8.53682 17.3559C7.32714 17.1153 6.19547 16.58 5.24205 15.7976C4.28863 15.0151 3.5429 14.0096 3.07091 12.8701C2.59891 11.7306 2.41522 10.4923 2.53612 9.26487C2.65701 8.03743 3.07875 6.85874 3.76398 5.83322C4.44921 4.8077 5.37678 3.967 6.46453 3.38559C7.55227 2.80418 8.76662 2.5 10 2.5C10.3699 2.5 10.7376 2.52734 11.1005 2.58117L11.2472 1.59199C10.836 1.53099 10.4192 1.5 10 1.5ZM13.2067 3.22008C13.5383 3.37691 13.8593 3.5585 14.1668 3.76398C14.4743 3.96946 14.7649 4.19652 15.0367 4.44286L15.7083 3.70191C15.4002 3.42271 15.0709 3.16538 14.7223 2.93251C14.3738 2.69964 14.0101 2.49384 13.6342 2.31609L13.2067 3.22008ZM16.433 6.14423C16.6216 6.45886 16.7876 6.78818 16.9291 7.12987C17.0706 7.47157 17.1861 7.82181 17.2752 8.17765L18.2453 7.93467C18.1443 7.53138 18.0134 7.13444 17.853 6.74719C17.6926 6.35995 17.5044 5.98672 17.2907 5.63012L16.433 6.14423ZM17.491 10.368C17.473 10.7344 17.428 11.1004 17.3559 11.4632C17.2837 11.8259 17.1852 12.1813 17.0616 12.5267L18.0031 12.8636C18.1432 12.4721 18.2549 12.0694 18.3367 11.6583C18.4184 11.2472 18.4694 10.8323 18.4898 10.4171L17.491 10.368Z"
d="M10.5 5C11.3284 5 12 4.32843 12 3.5C12 2.67157 11.3284 2 10.5 2C9.82349 2 9.25159 2.44785 9.06458 3.06324C8.24048 3.17471 7.44026 3.43234 6.7024 3.82649C5.68662 4.36911 4.82039 5.15374 4.18045 6.11089C3.54051 7.06805 3.14664 8.16818 3.03373 9.31385C2.92082 10.4595 3.09238 11.6153 3.53318 12.6789C3.97399 13.7424 4.67043 14.6809 5.56079 15.4112C6.45115 16.1414 7.50795 16.6409 8.63758 16.8655C9.7672 17.0901 10.9348 17.0327 12.037 16.6986C12.6805 16.5035 13.2901 16.2176 13.8482 15.8513C14.0453 15.9466 14.2664 16 14.5 16C15.3284 16 16 15.3284 16 14.5C16 13.6716 15.3284 13 14.5 13C13.6716 13 13 13.6716 13 14.5C13 14.7212 13.0479 14.9312 13.1338 15.1202C12.7013 15.3842 12.2355 15.5935 11.7468 15.7416C10.802 16.0281 9.80098 16.0772 8.83255 15.8847C7.86413 15.6922 6.95818 15.264 6.19496 14.638C5.43174 14.012 4.83479 13.2076 4.45698 12.296C4.07917 11.3844 3.93214 10.3938 4.02891 9.41193C4.12568 8.43002 4.46326 7.4871 5.01177 6.66669C5.56028 5.84628 6.3028 5.17369 7.17358 4.70853C7.77986 4.38466 8.43528 4.16831 9.11077 4.06676C9.33434 4.61422 9.87212 5 10.5 5ZM12.571 4.57608C12.8364 4.70157 13.0934 4.84685 13.3396 5.01126C13.5858 5.17563 13.8184 5.35726 14.0359 5.55431L14.7073 4.81315C14.4534 4.58326 14.1821 4.37137 13.895 4.17964C13.6078 3.98786 13.308 3.81837 12.9983 3.67198L12.571 4.57608ZM15.1537 6.9154C15.3046 7.16714 15.4375 7.43061 15.5508 7.704C15.6641 7.97734 15.7566 8.25751 15.8279 8.54215L16.7979 8.29903C16.7147 7.96691 16.6068 7.64002 16.4746 7.32112C16.3425 7.00216 16.1874 6.69476 16.0112 6.40105L15.1537 6.9154ZM16.0006 10.2944C15.9862 10.5875 15.9502 10.8803 15.8925 11.1705C15.8347 11.4607 15.7558 11.745 15.6569 12.0213L16.5983 12.3584C16.7138 12.036 16.8058 11.7043 16.8732 11.3657C16.9406 11.0271 16.9826 10.6855 16.9994 10.3435L16.0006 10.2944Z"
fill="currentColor"
/>
</svg>
@ -71,6 +71,46 @@ const CustomIconMap = {
/>
</svg>
),
booleanExclude: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 5H5V12H8.33325V15.3333H15.3333V8.33333H12V5ZM12 8.33333H8.33325V12H12V8.33333Z"
fill="currentColor"
/>
</svg>
),
booleanIntersect: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6 6H11V8H9H8V9V11H6V6ZM8 12H6H5V11V6V5H6H11H12V6V8H14H15V9V14V15H14H9H8V14V12ZM9 12V14H14V9H12V11V12H12H11H9Z"
fill="currentColor"
/>
</svg>
),
booleanSubtract: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6 6H11V11H6V6ZM5 5H6H11H12V6V8H15V15H7.99998V12H6H5V11V6V5Z"
fill="currentColor"
/>
</svg>
),
booleanUnion: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M12 5H5V12H8V15H15V8H12V5Z"
fill="currentColor"
/>
</svg>
),
bug: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -101,6 +141,26 @@ const CustomIconMap = {
/>
</svg>
),
chamfer3d: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6 6V5H7H9L15 11V13V14H14V15H13H6H5V14V7V6H6ZM13 14H6V7H8.58579L13 11.4142V14Z"
fill="currentColor"
/>
</svg>
),
circle: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 4C9.21207 4 8.43186 4.15519 7.7039 4.45672C6.97595 4.75825 6.31451 5.20021 5.75736 5.75736C5.20021 6.31451 4.75825 6.97594 4.45672 7.7039C4.1552 8.43185 4 9.21207 4 10C4 10.7879 4.15519 11.5681 4.45672 12.2961C4.75825 13.0241 5.20021 13.6855 5.75736 14.2426C6.31451 14.7998 6.97594 15.2417 7.7039 15.5433C8.43185 15.8448 9.21207 16 10 16C10.7879 16 11.5681 15.8448 12.2961 15.5433C13.0241 15.2417 13.6855 14.7998 14.2426 14.2426C14.7998 13.6855 15.2417 13.0241 15.5433 12.2961C15.8448 11.5681 16 10.7879 16 10C16 9.21207 15.8448 8.43185 15.5433 7.7039C15.2417 6.97595 14.7998 6.31451 14.2426 5.75736C13.6855 5.20021 13.0241 4.75825 12.2961 4.45672C11.5681 4.15519 10.7879 4 10 4ZM7.32122 3.53284C8.1705 3.18106 9.08075 3 10 3C10.9193 3 11.8295 3.18106 12.6788 3.53284C13.5281 3.88463 14.2997 4.40024 14.9497 5.05025C15.5998 5.70026 16.1154 6.47194 16.4672 7.32122C16.8189 8.1705 17 9.08075 17 10C17 10.9193 16.8189 11.8295 16.4672 12.6788C16.1154 13.5281 15.5998 14.2997 14.9497 14.9497C14.2997 15.5998 13.5281 16.1154 12.6788 16.4672C11.8295 16.8189 10.9193 17 10 17C9.08074 17 8.17049 16.8189 7.32121 16.4672C6.47193 16.1154 5.70026 15.5998 5.05025 14.9497C4.40024 14.2997 3.88462 13.5281 3.53284 12.6788C3.18106 11.8295 3 10.9192 3 10C3 9.08074 3.18106 8.17049 3.53284 7.32121C3.88463 6.47193 4.40024 5.70026 5.05026 5.05025C5.70027 4.40024 6.47194 3.88462 7.32122 3.53284Z"
fill="currentColor"
/>
</svg>
),
clipboardCheckmark: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -182,7 +242,7 @@ const CustomIconMap = {
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 3L10.3536 3.35355L12.3536 5.35355L11.6465 6.06066L10.5 4.91421V11.5854C11.0826 11.7913 11.5 12.3469 11.5 13C11.5 13.8284 10.8284 14.5 10 14.5C9.17157 14.5 8.5 13.8284 8.5 13C8.5 12.3469 8.91741 11.7913 9.5 11.5854V4.91421L8.35356 6.06066L7.64645 5.35355L9.64645 3.35355L10 3ZM1.95887 12.3282L8 8.63644V9.80838L2.91773 12.9142L10 17.2423L17.0823 12.9142L12 9.80838V8.63644L18.0411 12.3282L19 12.9142L19 14.9683H18V13.5253L10.5 18.1087V19.9683H9.5V18.1087L2 13.5253V14.9683H1L1 12.9142L1.95887 12.3282Z"
d="M10 2.51583L10.3536 2.86938L12.3536 4.86938L11.6465 5.57649L10.5 4.43004V10.1012C11.0826 10.3071 11.5 10.8627 11.5 11.5158C11.5 12.3443 10.8284 13.0158 10 13.0158C9.17157 13.0158 8.5 12.3443 8.5 11.5158C8.5 10.8627 8.9174 10.3071 9.5 10.1012V4.43004L8.35356 5.57649L7.64645 4.86938L9.64645 2.86938L10 2.51583ZM3.95886 10.8441L8.5 8.06893V9.24088L4.91773 11.43L10 14.5359L15.0823 11.43L11.5 9.24087V8.06893L16.0411 10.8441L17 11.43L17 13.4842H16V12.0412L10.5 15.4023V17.4842H9.5V15.4023L4 12.0412V13.4842H3V11.43L3.95886 10.8441Z"
fill="currentColor"
/>
</svg>
@ -203,6 +263,16 @@ const CustomIconMap = {
/>
</svg>
),
fillet3d: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6 6V5H7H10C12.7614 5 15 7.23858 15 10V13V14H14V15H13H6H5V14V7V6H6ZM6 7H10C11.6569 7 13 8.34315 13 10V14H6V7Z"
fill="currentColor"
/>
</svg>
),
file: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -223,6 +293,16 @@ const CustomIconMap = {
/>
</svg>
),
floppyDiskArrow: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4 4H4.5L7 4L13 4L13.2071 4L13.3536 4.14645L15.8536 6.64645L16 6.79289V7V11.3773C15.6992 11.1362 15.3628 10.9376 15 10.7908V7.20711L13.5 5.70711V7V7.5L13 7.5L7 7.5L6.5 7.5V7V5L5 5V15H6.5V10V9.5H7H13H13.5V10V10.5C13.1547 10.5 12.8196 10.5438 12.5 10.626V10.5H7.5V15H9.53095C9.57451 15.3493 9.66311 15.6847 9.79076 16H7H4.5H4V15.5V4.5V4ZM7.5 5V6.5L12.5 6.5V5L7.5 5ZM16.3904 14.1877L14.3904 11.6877L13.6096 12.3123L14.9597 14H11V15H14.9597L13.6096 16.6877L14.3904 17.3123L16.3904 14.8123L16.6403 14.5L16.3904 14.1877Z"
fill="currentColor"
/>
</svg>
),
folder: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -253,6 +333,16 @@ const CustomIconMap = {
/>
</svg>
),
hole: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.44141 3.77773L5.86999 5.29277L12.7003 6.74461L13.0466 6.81819L15.0555 4.88135L15.7415 5.59773L13.6999 7.56607L13.6785 7.54375V7.95252V15.3589L15.0555 14.0313L15.7415 14.7477L13.6999 16.7161L13.6785 16.6938V16.7185L12.7003 16.5105L5.685 15.0194L4.70685 14.8115V13.8115V6.04554V5.04554L4.73466 5.05145L4.71383 5.02969L6.75542 3.06135L7.44141 3.77773ZM5.685 6.25345L12.7003 7.74461V15.5105L5.685 14.0194V6.25345ZM8.63431 8.91669C8.82484 10.2312 9.80376 11.448 11.0461 11.9188C10.8003 12.6735 10.064 13.1027 9.19383 12.9178C8.12261 12.6901 7.25421 11.6177 7.25421 10.5225C7.25421 9.62626 7.83586 8.9925 8.63431 8.91669ZM8.65548 7.88811C7.30121 7.8585 6.27606 8.85522 6.27606 10.3146C6.27606 11.9621 7.58239 13.5753 9.19383 13.9178C10.6117 14.2191 11.7933 13.4364 12.0568 12.1219C12.06 12.1058 12.0631 12.0896 12.066 12.0734C12.096 11.9083 12.1116 11.7352 12.1116 11.555C12.1116 9.90758 10.8053 8.29439 9.19383 7.95187C9.00991 7.91278 8.82997 7.89193 8.65548 7.88811Z"
fill="currentColor"
/>
</svg>
),
horizontal: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -306,7 +396,47 @@ const CustomIconMap = {
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.5 6C16.3284 6 17 5.32843 17 4.5C17 3.67157 16.3284 3 15.5 3C14.6716 3 14 3.67157 14 4.5C14 4.73107 14.0522 4.94993 14.1456 5.14543L5.14543 14.1456C4.94993 14.0522 4.73107 14 4.5 14C3.67157 14 3 14.6716 3 15.5C3 16.3284 3.67157 17 4.5 17C5.32843 17 6 16.3284 6 15.5C6 15.2679 5.94729 15.0482 5.8532 14.852L14.852 5.8532C15.0482 5.94729 15.2679 6 15.5 6Z"
d="M14.75 6.375C15.5784 6.375 16.25 5.70343 16.25 4.875C16.25 4.04657 15.5784 3.375 14.75 3.375C13.9216 3.375 13.25 4.04657 13.25 4.875C13.25 5.16584 13.3328 5.43736 13.4761 5.66726L5.88512 13.7657C5.69226 13.6754 5.47702 13.625 5.25 13.625C4.42157 13.625 3.75 14.2966 3.75 15.125C3.75 15.9534 4.42157 16.625 5.25 16.625C6.07843 16.625 6.75 15.9534 6.75 15.125C6.75 14.889 6.69549 14.6657 6.59837 14.467L14.26 6.29315C14.4136 6.34619 14.5784 6.375 14.75 6.375Z"
fill="currentColor"
/>
</svg>
),
link: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10.5864 4.46513C11.9532 3.09829 14.1693 3.09829 15.5361 4.46513C16.903 5.83196 16.903 8.04804 15.5361 9.41488L13.5364 11.4147C13.5839 10.9639 13.5635 10.5074 13.4752 10.0616L14.829 8.70777C15.8053 7.73146 15.8053 6.14855 14.829 5.17224C13.8527 4.19592 12.2698 4.19592 11.2935 5.17224L9.17217 7.29356C8.19586 8.26987 8.19586 9.85278 9.17217 10.8291C9.53458 11.1915 9.98056 11.4194 10.4481 11.5127C10.3749 11.6902 10.2662 11.8565 10.122 12.0007L9.76392 12.3587C9.28973 12.1899 8.84465 11.9158 8.46507 11.5362C7.09823 10.1694 7.09823 7.95328 8.46507 6.58645L10.5864 4.46513ZM4.46507 10.5864L6.46488 8.58663C6.41734 9.03738 6.43772 9.49394 6.52601 9.93972L5.17217 11.2935C4.19586 12.2699 4.19586 13.8528 5.17217 14.8291C6.14849 15.8054 7.7314 15.8054 8.70771 14.8291L10.829 12.7078C11.8053 11.7315 11.8053 10.1485 10.829 9.17223C10.4666 8.80983 10.0207 8.58195 9.55314 8.48859C9.62635 8.31113 9.73506 8.14487 9.87926 8.00066L10.2373 7.64262C10.7115 7.81138 11.1566 8.08555 11.5361 8.46512C12.903 9.83196 12.903 12.048 11.5361 13.4149L9.41481 15.5362C8.04798 16.903 5.8319 16.903 4.46507 15.5362C3.09823 14.1694 3.09823 11.9533 4.46507 10.5864Z"
fill="currentColor"
/>
</svg>
),
lockClosed: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 5.5C8.61929 5.5 7.5 6.61929 7.5 8V9H12.5V8C12.5 6.61929 11.3807 5.5 10 5.5ZM6.5 8V9H6H5V10V15V16H6H14H15V15V10V9H14H13.5V8C13.5 6.067 11.933 4.5 10 4.5C8.067 4.5 6.5 6.067 6.5 8ZM6 10V15H14V10H6ZM10.5 11V12.1338C10.7989 12.3067 11 12.6299 11 13C11 13.5523 10.5523 14 10 14C9.44772 14 9 13.5523 9 13C9 12.6299 9.2011 12.3067 9.5 12.1338V11H10.5Z"
fill="currentColor"
/>
</svg>
),
lockOpen: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 4.5C8.61929 4.5 7.5 5.61929 7.5 7H6.5C6.5 5.067 8.067 3.5 10 3.5C11.933 3.5 13.5 5.067 13.5 7V9H14H15V10V15V16H14H6H5V15V10V9H6H12.5V7C12.5 5.61929 11.3807 4.5 10 4.5ZM6 10V15H14V10H6ZM10.5 11V12.1338C10.7989 12.3067 11 12.6299 11 13C11 13.5523 10.5523 14 10 14C9.44772 14 9 13.5523 9 13C9 12.6299 9.2011 12.3067 9.5 12.1338V11H10.5Z"
fill="currentColor"
/>
</svg>
),
loft: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M13.7954 6.22689C14.7749 5.73715 15 5.2456 15 5C15 4.7544 14.7749 4.26285 13.7954 3.77311C12.8758 3.31327 11.5353 3 10 3C8.46473 3 7.12424 3.31327 6.20457 3.77311C5.22509 4.26285 5 4.7544 5 5C5 5.2456 5.22509 5.73715 6.20457 6.22689C6.29872 6.27397 6.39728 6.3195 6.5 6.36333L6.5 7.43698C4.98593 6.89239 4 6.00376 4 5C4 3.34315 6.68629 2 10 2C13.3137 2 16 3.34315 16 5C16 6.00376 15.0141 6.89239 13.5 7.43698V6.36333C13.6027 6.3195 13.7013 6.27397 13.7954 6.22689ZM11.5 8.5531V9.72505L15.0823 11.9142L10 15.0201L4.91773 11.9142L8.5 9.72505V8.5531L3.95886 11.3282L3 11.9142V13.9683H4V12.5253L9.5 15.8864V17.9683H10.5V15.8864L16 12.5253V13.9683H17L17 11.9142L16.0411 11.3282L11.5 8.5531ZM10 4.29289L10.3536 4.64645L12.3536 6.64644L11.6465 7.35355L10.5 6.20711V10.5854C11.0826 10.7913 11.5 11.3469 11.5 12C11.5 12.8284 10.8284 13.5 10 13.5C9.17157 13.5 8.5 12.8284 8.5 12C8.5 11.3469 8.91741 10.7913 9.5 10.5854V6.20711L8.35356 7.35355L7.64645 6.64644L9.64645 4.64645L10 4.29289Z"
fill="currentColor"
/>
</svg>
@ -331,6 +461,16 @@ const CustomIconMap = {
/>
</svg>
),
mirror: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.41667 13.8333V17H10.4167V13.7738L12.5 14.5179L12.6682 14.047L12.8363 13.5761L10.4167 12.7119V7.28805L12.8363 6.42388L12.6682 5.95301L12.5 5.48214L10.4167 6.22619V3H9.41667V6.16667L4 4.23214L3 3.875V4.93686V15.0631V16.125L4 15.7679L9.41667 13.8333ZM9.41667 12.7715V7.22853L4 5.294V14.706L9.41667 12.7715ZM16.5 6.0625H17V4.93686V4.40593V3.875L16.5 4.05357L16 4.23214L14.5 4.76786L14.6682 5.23873L14.8363 5.7096L16 5.294V6.0625H16.5ZM16.5 7.8125H17V12.1875H16.5H16V7.8125H16.5ZM16.5 13.9375H17V15.0631V15.5941V16.125L16.5 15.9464L16 15.7679L14.5 15.2321L14.6682 14.7613L14.8363 14.2904L16 14.706V13.9375H16.5Z"
fill="currentColor"
/>
</svg>
),
move: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -381,6 +521,16 @@ const CustomIconMap = {
/>
</svg>
),
plane: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.92871 5.11391L4.43964 5.00995V4.10898V3.60898V3.10898L4.92871 3.21293L5.41778 3.31689L6.29907 3.50421V4.00421V4.50421L5.41778 4.31689V5.21786L4.92871 5.11391ZM11.8774 4.68991L8.1585 3.89945V4.39945V4.89945L11.8774 5.68991V5.18991V4.68991ZM13.7368 5.08515V5.58515V6.08515L14.6181 6.27247V7.17344L15.1071 7.2774L15.5962 7.38135V6.48038V5.98038V5.48038L15.1071 5.37643L14.6181 5.27247L13.7368 5.08515ZM15.5962 9.28233L15.1071 9.17837L14.6181 9.07441V12.8764L15.1071 12.9803L15.5962 13.0843V9.28233ZM15.5962 14.9852L15.1071 14.8813L14.6181 14.7773V15.6783L13.7368 15.491V15.991V16.491L14.6181 16.6783L15.1071 16.7823L15.5962 16.8862V16.3862V15.8862V14.9852ZM11.8774 16.0957V15.5957V15.0957L8.1585 14.3053V14.8053V15.3053L11.8774 16.0957ZM6.29907 14.91V14.41V13.91L5.41778 13.7227V12.8217L4.92871 12.7178L4.43964 12.6138V13.5148V14.0148V14.5148L4.92871 14.6188L5.41778 14.7227L6.29907 14.91ZM4.43964 10.7129L4.92871 10.8168L5.41778 10.9208V7.11883L4.92871 7.01488L4.43964 6.91092V10.7129Z"
fill="currentColor"
/>
</svg>
),
plus: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -391,6 +541,16 @@ const CustomIconMap = {
/>
</svg>
),
polygon: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M9.99291 4.23607L4.03556 8.56434L6.31106 15.5676H13.6748L15.9503 8.56434L9.99291 4.23607ZM17.1258 8.18237L9.99291 3L2.85999 8.18237L5.58452 16.5676H14.4013L17.1258 8.18237Z"
fill="currentColor"
/>
</svg>
),
questionMark: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -419,6 +579,22 @@ const CustomIconMap = {
/>
</svg>
),
revolve: (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M6.89927 4.693C7.36143 4.41381 7.87 4.21672 8.40842 4.10376C7.83595 4.69964 7.47 5.50199 7.47 6.31393C7.47 7.34818 8.06376 8.11443 8.9218 8.3459C8.85859 8.44278 8.80068 8.54347 8.74845 8.64757C8.69108 8.76191 8.6411 8.87927 8.59864 8.99896L9.87934 8.72674C10.0433 8.52252 10.2451 8.35006 10.4752 8.21957L10.4736 8.21676C11.6445 7.74118 12.53 6.5014 12.53 5.2384C12.53 4.20393 11.936 3.43756 11.0776 3.20628L11.0781 3.19471C9.34471 2.75461 7.64421 2.9631 6.28878 3.78191C4.93335 4.60073 4.01395 5.97492 3.69937 7.6522C3.55495 8.4222 3.54245 9.23235 3.65633 10.0495L3.88904 10L4.59283 9.85042C4.53004 9.23247 4.55179 8.62188 4.6613 8.038C4.93318 6.58836 5.7278 5.40068 6.89927 4.693ZM7.47005 15.1799C6.65634 14.4835 5.96657 13.6244 5.46142 12.6658L4.54925 12.8597L4.57626 12.9127C5.40057 14.5194 6.672 15.8914 8.18092 16.8103C8.28057 16.8945 8.38913 16.9684 8.50564 17.0308L9.08339 16.1011C8.87304 15.9884 8.70487 15.8151 8.5944 15.5972C8.48392 15.3793 8.43471 15.1238 8.45129 14.8543C8.46788 14.5848 8.54973 14.31 8.68929 14.0552C8.82885 13.8005 9.02162 13.574 9.24981 13.3967C9.47801 13.2193 9.73427 13.0969 9.99495 13.0406C10.2556 12.9843 10.5123 12.996 10.7413 13.0747C10.9608 13.15 11.1481 13.2845 11.2874 13.4665C11.7756 13.525 12.1412 13.4705 12.4217 13.3627C12.354 13.1483 12.2554 12.9503 12.128 12.7748C11.8981 12.4582 11.5819 12.225 11.2086 12.0968C11.1027 12.0605 10.9931 12.0329 10.8808 12.0141C10.6353 11.9374 10.407 11.816 10.2071 11.6571L9.0562 11.9017C9.09421 11.9513 9.13379 11.9999 9.17491 12.0473C9.2375 12.1194 9.30323 12.1884 9.37183 12.254C9.16516 12.3521 8.9652 12.4755 8.77697 12.6218C8.40494 12.9109 8.09066 13.2801 7.86313 13.6955C7.6356 14.1108 7.50216 14.5588 7.47513 14.9982C7.47137 15.0592 7.46969 15.1198 7.47005 15.1799ZM9.99038 7.3647L10 7.36268C10.8571 7.18051 11.5518 6.32252 11.5518 5.44631C11.5518 4.5701 10.8571 4.00748 10 4.18965C9.14293 4.37183 8.44815 5.22981 8.44815 6.10602C8.44815 6.9614 9.11029 7.51793 9.93915 7.37442C9.95332 7.36608 9.96757 7.35785 9.98189 7.34973L9.99038 7.3647ZM16.6 8.29822L16.1109 8.40218L14.074 8.83515V9.83515L16.1109 9.40218L16.6 9.29822V8.29822ZM7.96305 11.1341L12.037 10.2681V9.26813L7.96305 10.1341V11.1341ZM3.8891 12L5.92607 11.567V10.567L3.8891 11L3.40002 11.104V12.104L3.8891 12ZM15.4172 11.1225L15.9703 10.8085L16.0604 11.4381L16.4532 14.1827L15.5948 14.3056L15.5027 13.662L15.5423 12.9555L15.3598 13.5867C15.1404 13.9788 14.8672 14.3472 14.529 14.662C13.8137 15.3278 12.8466 15.7187 11.5898 15.635C11.3638 16.0194 10.9459 16.2774 10.4678 16.2774C9.74943 16.2774 9.16711 15.6951 9.16711 14.9767C9.16711 14.2584 9.74943 13.6761 10.4678 13.6761C11.1178 13.6761 11.6565 14.153 11.753 14.776C12.7384 14.8173 13.4355 14.4952 13.9382 14.0273C14.4048 13.5929 14.7326 13.0058 14.9513 12.384L13.4607 13.2302L13.0326 12.4761L15.4172 11.1225Z"
fill="currentColor"
/>
</svg>
),
search: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -439,6 +615,16 @@ const CustomIconMap = {
/>
</svg>
),
shell: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7.24585 3.87359L5.67443 5.38863L12.5048 6.84046L12.851 6.91405L14.8599 4.97721L15.5459 5.69359L13.5043 7.66193L13.4829 7.63961V8.04838V15.4548L14.8599 14.1272L15.5459 14.8436L13.5043 16.8119L13.4829 16.7896V16.8143L12.5048 16.6064L5.48944 15.1152L4.51129 14.9073V13.9073V6.14139V5.14139L4.5391 5.14731L4.51827 5.12555L6.55986 3.15721L7.24585 3.87359ZM5.48944 6.34931L12.5048 7.84046V15.6064L5.48944 14.1152V6.34931ZM10.5978 13.3098V11.2576L9.62543 11.0509L7.8814 12.7324L10.5978 13.3098ZM7.19195 12.0176V8.74893L9.13482 9.1619V10.1445L7.19195 12.0176ZM11.576 13.5177V11.4655L11.5761 11.4656V8.6808L11.576 8.68078L10.5978 8.47287L9.13482 8.16189L7.19195 7.74893L6.21381 7.54101L6.2138 7.54102V8.54102V12.3779V13.3779L7.19195 13.5859L10.5978 14.3098L11.576 14.5177L11.576 14.5177V13.5177Z"
fill="currentColor"
/>
</svg>
),
sketch: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -449,6 +635,32 @@ const CustomIconMap = {
/>
</svg>
),
spline: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.0052 6.91281C15.5851 6.70544 16 6.15118 16 5.5C16 4.67157 15.3284 4 14.5 4C13.6716 4 13 4.67157 13 5.5C13 6.15503 13.4199 6.712 14.0051 6.91646C14.0058 7.31235 13.9653 7.7498 13.8414 8.1571C13.6773 8.69654 13.3777 9.1532 12.854 9.42296C12.3204 9.69782 11.4691 9.82282 10.1135 9.5099C8.62632 9.16662 7.50955 9.26015 6.69015 9.68223C5.86086 10.1094 5.42244 10.8281 5.20399 11.546C5.04308 12.0748 4.99567 12.6189 4.99693 13.0864C4.41594 13.2932 4 13.848 4 14.5C4 15.3284 4.67157 16 5.5 16C6.32843 16 7 15.3284 7 14.5C7 13.8458 6.58114 13.2893 5.99696 13.0843C5.99585 12.6867 6.03606 12.2466 6.16068 11.8371C6.32483 11.2976 6.62436 10.841 7.14807 10.5712C7.68167 10.2964 8.53295 10.1714 9.88859 10.4843C11.3758 10.8276 12.4925 10.734 13.3119 10.312C14.1412 9.88478 14.5796 9.16611 14.7981 8.44821C14.9584 7.92128 15.0061 7.3792 15.0052 6.91281Z"
fill="currentColor"
/>
</svg>
),
sweep: (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.4743 3.19282L15.7063 2.49694L14.9738 2.53539L11.7806 2.703L11.833 3.70162L12.2189 3.68137L13.0309 3.47351L12.4065 3.79549C11.5985 4.23813 10.7423 4.7732 10.0381 5.41523C9.28624 6.1008 8.66584 6.94665 8.50598 7.97341C8.36948 8.8502 8.57961 9.78564 9.20597 10.7756C9.07507 10.9983 9 11.2577 9 11.5347C9 12.3631 9.67157 13.0347 10.5 13.0347C11.3284 13.0347 12 12.3631 12 11.5347C12 10.7063 11.3284 10.0347 10.5 10.0347C10.3174 10.0347 10.1425 10.0673 9.98067 10.127C9.51409 9.34655 9.40701 8.68651 9.49408 8.12725C9.60506 7.41442 10.0472 6.76028 10.7119 6.15418C11.3749 5.54964 12.218 5.03092 13.0491 4.58458C13.4267 4.38176 13.797 4.19644 14.1426 4.02567L13.5257 5.87659L14.4743 6.19282L15.4743 3.19282ZM10.4854 8.10284C10.4627 8.2645 10.4579 8.44276 10.4848 8.63934L15.0823 11.4489L10 14.5548L4.91773 11.4489L7.66526 9.76987C7.56422 9.41819 7.50187 9.06536 7.47765 8.71258L3.95886 10.8629L3 11.4489V13.5031H4V12.06L9.5 15.4211V17.5031H10.5V15.4211L16 12.06V13.5031H17L17 11.4489L16.0411 10.8629L10.6351 7.55929C10.5598 7.74567 10.5101 7.92698 10.4854 8.10284Z"
fill="currentColor"
/>
</svg>
),
tangent: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
@ -459,6 +671,14 @@ const CustomIconMap = {
/>
</svg>
),
text: (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.3107 14.9933L11.5524 12.3321H8.37616L7.61786 14.9933H5.98682L8.90553 5.00671H11.0946L14.0133 14.9933H12.3107ZM10.0215 6.62345H9.90705L8.67661 11.0015H11.2519L10.0215 6.62345Z"
fill="currentColor"
/>
</svg>
),
'three-dots': (
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path

View File

@ -177,7 +177,7 @@ const FileTreeItem = ({
codeManager.writeToFile()
kclManager.isFirstRender = true
kclManager.executeCode(true, true).then(() => {
kclManager.executeCode(true).then(() => {
kclManager.isFirstRender = false
})
} else {

View File

@ -23,7 +23,8 @@ export function HelpMenu(props: React.PropsWithChildren) {
name="questionMark"
className="w-7 h-7 rounded-full bg-chalkboard-110 dark:bg-chalkboard-80 text-chalkboard-10"
/>
<Tooltip position="top-right" className="ui-open:hidden">
<span className="sr-only">Help and resources</span>
<Tooltip position="top-right" wrapperClassName="ui-open:hidden">
Help and resources
</Tooltip>
</Popover.Button>

View File

@ -80,7 +80,9 @@ export function LowerRightControls({
name="bug"
className={`w-5 h-5 ${linkOverrideClassName}`}
/>
<Tooltip position="top">Report a bug</Tooltip>
<Tooltip position="top" contentClassName="text-xs">
Report a bug
</Tooltip>
</a>
<Link
to={
@ -93,7 +95,10 @@ export function LowerRightControls({
name="settings"
className={`w-5 h-5 ${linkOverrideClassName}`}
/>
<Tooltip position="top">Settings</Tooltip>
<span className="sr-only">Settings</span>
<Tooltip position="top" contentClassName="text-xs">
Settings
</Tooltip>
</Link>
<NetworkHealthIndicator />
<HelpMenu />

View File

@ -131,19 +131,30 @@ export const ModelingMachineProvider = ({
},
'sketch exit execute': ({ store }) => {
;(async () => {
// blocks entering a sketch until after exit sketch code has run
kclManager.isExecuting = true
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
sceneInfra.camControls.syncDirection = 'engineToClient'
const settings: Models['CameraSettings_type'] = (
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
)?.data?.data?.settings
const resp = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
const settings =
resp &&
resp.success &&
resp.resp.type === 'modeling' &&
resp.resp.data.modeling_response.type ===
'default_camera_get_settings'
? resp.resp.data.modeling_response.data.settings
: ({} as Models['DefaultCameraGetSettings_type']['settings'])
if (settings.up.z !== 1) {
// workaround for gimbal lock situation
await engineCommandManager.sendSceneCommand({
@ -164,7 +175,7 @@ export const ModelingMachineProvider = ({
}
store.videoElement?.pause()
kclManager.executeCode(true).then(() => {
kclManager.executeCode().then(() => {
if (engineCommandManager.engineConnection?.idleMode) return
store.videoElement?.play()

View File

@ -1,5 +1,5 @@
.panel {
@apply relative z-0 rounded-r max-w-full h-full flex-1;
@apply relative z-0 rounded-r max-w-full flex-auto;
display: grid;
grid-template-rows: auto 1fr;
@apply bg-chalkboard-10/50 focus-within:bg-chalkboard-10/90 backdrop-blur-sm border border-chalkboard-20;

View File

@ -1,34 +1,70 @@
import styles from './ModelingPane.module.css'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useModelingContext } from 'hooks/useModelingContext'
import { ActionButton } from 'components/ActionButton'
import Tooltip from 'components/Tooltip'
import { CustomIconName } from 'components/CustomIcon'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { ActionIcon } from 'components/ActionIcon'
export interface ModelingPaneProps
extends React.PropsWithChildren,
React.HTMLAttributes<HTMLDivElement> {
icon?: CustomIconName | IconDefinition
title: string
Menu?: React.ReactNode | React.FC
detailsTestId?: string
onClose: () => void
}
export const ModelingPaneHeader = ({
icon,
title,
Menu,
}: Pick<ModelingPaneProps, 'title' | 'Menu'>) => {
onClose,
}: Pick<ModelingPaneProps, 'icon' | 'title' | 'Menu' | 'onClose'>) => {
return (
<div className={styles.header}>
<div className="flex gap-2 items-center flex-1">{title}</div>
<div className="flex gap-2 items-center flex-1">
{icon && (
<ActionIcon
icon={icon}
className="p-1"
size="sm"
iconClassName="!text-chalkboard-80 dark:!text-chalkboard-30"
bgClassName="!bg-transparent"
/>
)}
<span>{title}</span>
</div>
{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>
)
}
export const ModelingPane = ({
title,
icon,
id,
children,
className,
Menu,
detailsTestId,
onClose,
...props
}: ModelingPaneProps) => {
const { settings } = useSettingsAuthContext()
@ -44,14 +80,19 @@ export const ModelingPane = ({
data-testid={detailsTestId}
id={id}
className={
'group-focus-within:border-primary dark:group-focus-within:border-chalkboard-50 ' +
'focus-within:border-primary dark:focus-within:border-chalkboard-50 ' +
pointerEventsCssClass +
styles.panel +
' group ' +
(className || '')
}
>
<ModelingPaneHeader title={title} Menu={Menu} />
<ModelingPaneHeader
icon={icon}
title={title}
Menu={Menu}
onClose={onClose}
/>
<div className="relative w-full">{children}</div>
</section>
)

View File

@ -163,7 +163,7 @@ export function useCodeMirror(props: UseCodeMirror) {
effects: StateEffect.reconfigure.of(targetExtensions),
})
}
}, [targetExtensions])
}, [targetExtensions, view, isFirstRender])
return { view, setView, container, setContainer, state, setState }
}

Some files were not shown because too many files have changed in this diff Show More