Compare commits

...

14 Commits

Author SHA1 Message Date
53a646d12a Merge branch 'main' into nadro/adhoc/feature-tree-error-css-nits 2025-07-01 09:45:58 -05:00
85c721fb49 Add display of units for calculated KCL values (#7619)
* Add display of units in UI modals with calculated KCL values

* Fix command bar display to handle units

* Add display of units in the command bar

* Fix more cases of NaN from units

* Fix to support explicit plus for exponent in scientific notation

* Fix display in autocomplete

* Change to parseFloat to be more resilient

* Add e2e test for command bar

* Change an existing test to use explicit inline units

* Fix case when input string can't be parsed
2025-06-30 15:26:45 -04:00
27af2d08a3 Bump the patch group in /rust with 3 updates (#7575)
* Bump the patch group in /rust with 3 updates

Bumps the patch group in /rust with 3 updates: [toml_edit](https://github.com/toml-rs/toml), [syn](https://github.com/dtolnay/syn) and [toml](https://github.com/toml-rs/toml).


Updates `toml_edit` from 0.22.26 to 0.22.27
- [Commits](https://github.com/toml-rs/toml/compare/v0.22.26...v0.22.27)

Updates `syn` from 2.0.103 to 2.0.104
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.103...2.0.104)

Updates `toml` from 0.8.22 to 0.8.23
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.22...toml-v0.8.23)

---
updated-dependencies:
- dependency-name: toml_edit
  dependency-version: 0.22.27
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: syn
  dependency-version: 2.0.104
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
- dependency-name: toml
  dependency-version: 0.8.23
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Trigger CI

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-06-30 12:09:43 -04:00
fb8b975b5e Bump esbuild from 0.25.2 to 0.25.3 in the security group across 1 directory (#6681)
Bump esbuild in the security group across 1 directory

Bumps the security group with 1 update in the / directory: [esbuild](https://github.com/evanw/esbuild).


Updates `esbuild` from 0.25.2 to 0.25.3
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.25.2...v0.25.3)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.25.3
  dependency-type: direct:development
  dependency-group: security
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-30 15:40:33 +00:00
62d8d45a58 Bump the major group across 1 directory with 4 updates (#7572)
* Bump the major group across 1 directory with 4 updates

Bumps the major group with 4 updates in the / directory: [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact), [runs-on/action](https://github.com/runs-on/action), [actions/create-github-app-token](https://github.com/actions/create-github-app-token) and [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv).


Updates `dawidd6/action-download-artifact` from 7 to 11
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](https://github.com/dawidd6/action-download-artifact/compare/v7...v11)

Updates `runs-on/action` from 1 to 2
- [Release notes](https://github.com/runs-on/action/releases)
- [Commits](https://github.com/runs-on/action/compare/v1...v2)

Updates `actions/create-github-app-token` from 1 to 2
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Commits](https://github.com/actions/create-github-app-token/compare/v1...v2)

Updates `astral-sh/setup-uv` from 5 to 6
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/v5...v6)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-version: '11'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major
- dependency-name: runs-on/action
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major
- dependency-name: actions/create-github-app-token
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major
- dependency-name: astral-sh/setup-uv
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: major
...

Signed-off-by: dependabot[bot] <support@github.com>

* bump

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Max Ammann <max.ammann@zoo.dev>
2025-06-30 10:07:54 -04:00
ae3440df0a Use proper envs for Rust functions (#7623) 2025-06-29 07:03:36 -05:00
af658c909d Enterprise plans should not have the upgrade button (#7628)
* Enterprise plans should not have the upgrade button
Fixes #7627

* Move the check to BillingDialog

* Hide home box and change bool check

* Add component tests

* Clean up
2025-06-28 12:03:41 -04:00
7ec11d23c8 Capitalize labels in the native file menu (#7639) 2025-06-28 12:00:47 -04:00
30000a1eac Fix the vertical alignment on the temporary workspace label (#7638) 2025-06-28 13:23:47 +00:00
e35bcf2f11 Merge branch 'main' into nadro/adhoc/feature-tree-error-css-nits 2025-06-18 10:45:59 -04:00
939d5ef3f7 fix: reverted my testing code, needed to use this to check the colors 2025-05-15 13:07:43 -05:00
8d19a955af fix: minor css nits, cannot figure out how to make this button cleaner 2025-05-15 13:00:13 -05:00
625394d587 fix: show the errored background in the feature tree if there is a a KCL error, not ust if the operation is present 2025-05-15 12:26:50 -05:00
e6dd628736 fix: red error overlay fills container, does not have rounded edges anymore 2025-05-15 12:19:41 -05:00
44 changed files with 1243 additions and 916 deletions

View File

@ -43,7 +43,7 @@ jobs:
- name: Download Wasm Cache
id: download-wasm
if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7
uses: dawidd6/action-download-artifact@v11
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}

View File

@ -25,8 +25,8 @@ jobs:
- runner=8cpu-linux-x64
- extras=s3-cache
steps:
- uses: runs-on/action@v1
- uses: actions/create-github-app-token@v1
- uses: runs-on/action@v2
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
@ -149,8 +149,8 @@ jobs:
partitionIndex: [1, 2, 3, 4, 5, 6]
partitionTotal: [6]
steps:
- uses: runs-on/action@v1
- uses: actions/create-github-app-token@v1
- uses: runs-on/action@v2
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
@ -207,8 +207,8 @@ jobs:
- runner=32cpu-linux-x64
- extras=s3-cache
steps:
- uses: runs-on/action@v1
- uses: actions/create-github-app-token@v1
- uses: runs-on/action@v2
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}

View File

@ -46,7 +46,7 @@ jobs:
- name: Download Wasm cache
id: download-wasm
if: ${{ github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7
uses: dawidd6/action-download-artifact@v11
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
@ -110,7 +110,7 @@ jobs:
steps:
- uses: actions/create-github-app-token@v1
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
@ -230,7 +230,7 @@ jobs:
steps:
- uses: actions/create-github-app-token@v1
- uses: actions/create-github-app-token@v2
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}

View File

@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/create-github-app-token@v1
- uses: actions/create-github-app-token@v2
id: app-token
with:
# required

View File

@ -113,7 +113,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1
- uses: taiki-e/install-action@just
- name: Run tests
@ -130,7 +130,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v6
- name: Install codespell
run: |
uv venv .venv
@ -161,7 +161,7 @@ jobs:
with:
path: rust/kcl-python-bindings
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v5
uses: astral-sh/setup-uv@v6
- name: do uv things
run: |
cd rust/kcl-python-bindings

View File

@ -525,7 +525,9 @@ test.describe('Command bar tests', () => {
const projectName = 'test'
const beforeKclCode = `a = 5
b = a * a
c = 3 + a`
c = 3 + a
theta = 45deg
`
await context.folderSetupFn(async (dir) => {
const testProject = join(dir, projectName)
await fsp.mkdir(testProject, { recursive: true })
@ -615,9 +617,45 @@ c = 3 + a`
stage: 'commandBarClosed',
})
})
await test.step(`Edit a parameter with explicit units via command bar`, async () => {
await cmdBar.cmdBarOpenBtn.click()
await cmdBar.chooseCommand('edit parameter')
await cmdBar
.selectOption({
name: 'theta',
})
.click()
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Edit parameter',
currentArgKey: 'value',
currentArgValue: '45deg',
headerArguments: {
Name: 'theta',
Value: '',
},
highlightedHeaderArg: 'value',
})
await cmdBar.argumentInput
.locator('[contenteditable]')
.fill('45deg + 1deg')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
commandName: 'Edit parameter',
headerArguments: {
Name: 'theta',
Value: '46deg',
},
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'commandBarClosed',
})
})
await editor.expectEditor.toContain(
`a = 5b = a * amyParameter001 = ${newValue}c = 3 + a`
`a = 5b = a * amyParameter001 = ${newValue}c = 3 + atheta = 45deg + 1deg`
)
})

View File

@ -136,17 +136,17 @@ test.describe('Point-and-click tests', () => {
highlightedHeaderArg: 'length',
commandName: 'Extrude',
})
await page.keyboard.insertText('width - 0.001')
await page.keyboard.insertText('width - 0.001in')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Length: '4.999',
Length: '4.999in',
},
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
await editor.expectEditor.toContain('extrude(length = width - 0.001)')
await editor.expectEditor.toContain('extrude(length = width - 0.001in)')
})
await test.step(`Edit second extrude via feature tree`, async () => {

View File

@ -125,18 +125,57 @@ test('Shows a loading spinner when uninitialized credit count', async () => {
await expect(queryByTestId('spinner')).toBeVisible()
})
test('Shows the total credits for Unknown subscription', async () => {
const data = {
balance: {
monthlyApiCreditsRemaining: 10,
stableApiCreditsRemaining: 25,
},
subscriptions: {
monthlyPayAsYouGoApiCreditsTotal: 20,
name: "unknown",
}
const unKnownTierData = {
balance: {
monthlyApiCreditsRemaining: 10,
stableApiCreditsRemaining: 25,
},
subscriptions: {
monthlyPayAsYouGoApiCreditsTotal: 20,
name: "unknown",
}
}
const freeTierData = {
balance: {
monthlyApiCreditsRemaining: 10,
stableApiCreditsRemaining: 0,
},
subscriptions: {
monthlyPayAsYouGoApiCreditsTotal: 20,
name: "free",
}
}
const proTierData = {
// These are all ignored
balance: {
monthlyApiCreditsRemaining: 10,
stableApiCreditsRemaining: 0,
},
subscriptions: {
// This should be ignored because it's Pro tier.
monthlyPayAsYouGoApiCreditsTotal: 20,
name: "pro",
}
}
const enterpriseTierData = {
// These are all ignored, user is part of an org.
balance: {
monthlyApiCreditsRemaining: 10,
stableApiCreditsRemaining: 0,
},
subscriptions: {
// This should be ignored because it's Pro tier.
monthlyPayAsYouGoApiCreditsTotal: 20,
// This should be ignored because the user is part of an Org.
name: "free",
}
}
test('Shows the total credits for Unknown subscription', async () => {
const data = unKnownTierData
server.use(
http.get('*/user/payment/balance', (req, res, ctx) => {
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
@ -166,17 +205,7 @@ test('Shows the total credits for Unknown subscription', async () => {
})
test('Progress bar reflects ratio left of Free subscription', async () => {
const data = {
balance: {
monthlyApiCreditsRemaining: 10,
stableApiCreditsRemaining: 0,
},
subscriptions: {
monthlyPayAsYouGoApiCreditsTotal: 20,
name: "free",
}
}
const data = freeTierData
server.use(
http.get('*/user/payment/balance', (req, res, ctx) => {
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
@ -212,19 +241,7 @@ test('Progress bar reflects ratio left of Free subscription', async () => {
})
})
test('Shows infinite credits for Pro subscription', async () => {
const data = {
// These are all ignored
balance: {
monthlyApiCreditsRemaining: 10,
stableApiCreditsRemaining: 0,
},
subscriptions: {
// This should be ignored because it's Pro tier.
monthlyPayAsYouGoApiCreditsTotal: 20,
name: "pro",
}
}
const data = proTierData
server.use(
http.get('*/user/payment/balance', (req, res, ctx) => {
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
@ -255,19 +272,7 @@ test('Shows infinite credits for Pro subscription', async () => {
await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe(null)
})
test('Shows infinite credits for Enterprise subscription', async () => {
const data = {
// These are all ignored, user is part of an org.
balance: {
monthlyApiCreditsRemaining: 10,
stableApiCreditsRemaining: 0,
},
subscriptions: {
// This should be ignored because it's Pro tier.
monthlyPayAsYouGoApiCreditsTotal: 20,
// This should be ignored because the user is part of an Org.
name: "free",
}
}
const data = enterpriseTierData
server.use(
http.get('*/user/payment/balance', (req, res, ctx) => {
@ -297,3 +302,58 @@ test('Shows infinite credits for Enterprise subscription', async () => {
await expect(queryByTestId('infinity')).toBeVisible()
await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe(null)
})
test('Show upgrade button if credits are not infinite', async () => {
const data = freeTierData
server.use(
http.get('*/user/payment/balance', (req, res, ctx) => {
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
}),
http.get('*/user/payment/subscriptions', (req, res, ctx) => {
return HttpResponse.json(createUserPaymentSubscriptionsResponse(data.subscriptions))
}),
http.get('*/org', (req, res, ctx) => {
return new HttpResponse(403)
}),
)
const billingActor = createActor(billingMachine, { input: BILLING_CONTEXT_DEFAULTS }).start()
const { queryByTestId } = render(<BillingDialog
billingActor={billingActor}
/>)
await act(() => {
billingActor.send({ type: BillingTransition.Update, apiToken: "it doesn't matter wtf this is :)" })
})
await expect(queryByTestId('billing-upgrade-button')).toBeVisible()
})
test('Hide upgrade button if credits are infinite', async () => {
const data = enterpriseTierData
server.use(
http.get('*/user/payment/balance', (req, res, ctx) => {
return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
}),
http.get('*/user/payment/subscriptions', (req, res, ctx) => {
return HttpResponse.json(createUserPaymentSubscriptionsResponse(data.subscriptions))
}),
// Ok finally the first use of an org lol
http.get('*/org', (req, res, ctx) => {
return HttpResponse.json(createOrgResponse())
}),
)
const billingActor = createActor(billingMachine, { input: BILLING_CONTEXT_DEFAULTS }).start()
const { queryByTestId } = render(<BillingDialog
billingActor={billingActor}
/>)
await act(() => {
billingActor.send({ type: BillingTransition.Update, apiToken: "it doesn't matter wtf this is :)" })
})
await expect(queryByTestId('billing-upgrade-button')).toBe(null)
})

1118
package-lock.json generated

File diff suppressed because it is too large Load Diff

120
rust/Cargo.lock generated
View File

@ -178,7 +178,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -189,7 +189,7 @@ checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -211,7 +211,7 @@ checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -514,7 +514,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -740,7 +740,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -751,7 +751,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
dependencies = [
"darling_core",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -810,7 +810,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -831,7 +831,7 @@ dependencies = [
"darling",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -841,7 +841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
dependencies = [
"derive_builder_core",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -906,7 +906,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -944,7 +944,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -1119,7 +1119,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -1223,7 +1223,7 @@ dependencies = [
"inflections",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -1599,7 +1599,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -1829,7 +1829,7 @@ version = "0.1.83"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -1839,7 +1839,7 @@ dependencies = [
"convert_case",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -2104,7 +2104,7 @@ dependencies = [
"kittycad-modeling-cmds-macros-impl",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -2115,7 +2115,7 @@ checksum = "fdb4ee23cc996aa2dca7584d410e8826e08161e1ac4335bb646d5ede33f37cb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -2311,7 +2311,7 @@ checksum = "db5b29714e950dbb20d5e6f74f9dcec4edbcc1067bb7f8ed198c097b8c1a818b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -2640,7 +2640,7 @@ dependencies = [
"regex",
"regex-syntax 0.8.5",
"structmeta",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -2654,7 +2654,7 @@ dependencies = [
"regex",
"regex-syntax 0.8.5",
"structmeta",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -2710,7 +2710,7 @@ dependencies = [
"pest_meta",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -2754,7 +2754,7 @@ dependencies = [
"phf_shared",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -2809,7 +2809,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -2921,7 +2921,7 @@ dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -2981,7 +2981,7 @@ dependencies = [
"proc-macro2",
"pyo3-macros-backend",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -2994,7 +2994,7 @@ dependencies = [
"proc-macro2",
"pyo3-build-config",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -3492,7 +3492,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde_derive_internals",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -3556,7 +3556,7 @@ checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -3567,7 +3567,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -3591,14 +3591,14 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
name = "serde_spanned"
version = "0.6.8"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
@ -3815,7 +3815,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta-derive",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -3826,7 +3826,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -3848,7 +3848,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -3891,9 +3891,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.103"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
@ -3917,7 +3917,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -3941,7 +3941,7 @@ dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -4050,7 +4050,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -4061,7 +4061,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -4173,7 +4173,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -4217,9 +4217,9 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.22"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned",
@ -4238,9 +4238,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.22.26"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap 2.9.0",
"serde",
@ -4341,7 +4341,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -4369,7 +4369,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -4449,7 +4449,7 @@ checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
"termcolor",
]
@ -4635,7 +4635,7 @@ dependencies = [
"proc-macro-error2",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -4706,7 +4706,7 @@ dependencies = [
"log",
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
"wasm-bindgen-shared",
]
@ -4742,7 +4742,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -4777,7 +4777,7 @@ checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -5067,7 +5067,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
"synstructure",
]
@ -5112,7 +5112,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -5123,7 +5123,7 @@ checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -5143,7 +5143,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
"synstructure",
]
@ -5164,7 +5164,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]
@ -5186,7 +5186,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.103",
"syn 2.0.104",
]
[[package]]

View File

@ -19,7 +19,7 @@ anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
semver = "1.0.25"
serde = { workspace = true }
toml_edit = "0.22.26"
toml_edit = "0.22.27"
[lints]
workspace = true

View File

@ -14,7 +14,7 @@ bench = false
[dependencies]
proc-macro2 = "1"
quote = "1"
syn = { version = "2.0.103", features = ["full"] }
syn = { version = "2.0.104", features = ["full"] }
[lints]
workspace = true

View File

@ -14,7 +14,7 @@ bench = false
convert_case = "0.8.0"
proc-macro2 = "1"
quote = "1"
syn = { version = "2.0.103", features = ["full"] }
syn = { version = "2.0.104", features = ["full"] }
[lints]
workspace = true

View File

@ -123,7 +123,7 @@
"@vscode/test-electron": "^2.4.1",
"@vscode/vsce": "^3.3.2",
"cross-env": "^7.0.3",
"esbuild": "^0.25.2",
"esbuild": "^0.25.3",
"glob": "^11.0.1",
"mocha": "^11.1.0",
"typescript": "^5.8.3"

View File

@ -74,7 +74,7 @@ sha2 = "0.10.9"
tabled = { version = "0.20.0", optional = true }
tempfile = "3.20"
thiserror = "2.0.0"
toml = "0.8.22"
toml = "0.8.23"
ts-rs = { version = "11.0.1", features = [
"uuid-impl",
"url-impl",

View File

@ -401,7 +401,7 @@ impl FunctionDefinition<'_> {
impl FunctionBody<'_> {
fn prep_mem(&self, exec_state: &mut ExecState) {
match self {
FunctionBody::Rust(_) => exec_state.mut_stack().push_new_env_for_rust_call(),
FunctionBody::Rust(_) => exec_state.mut_stack().push_new_root_env(true),
FunctionBody::Kcl(_, memory) => exec_state.mut_stack().push_new_env_for_call(*memory),
}
}

View File

@ -541,22 +541,6 @@ impl Stack {
self.push_new_env_for_call(snapshot);
}
/// Push a new stack frame on to the call stack for callees which should not read or write
/// from memory.
///
/// This is suitable for calling standard library functions or other functions written in Rust
/// which will use 'Rust memory' rather than KCL's memory and cannot reach into the wider
/// environment.
///
/// Trying to read or write from this environment will panic with an index out of bounds.
pub fn push_new_env_for_rust_call(&mut self) {
self.call_stack.push(self.current_env);
// Rust functions shouldn't try to set or access anything in their environment, so don't
// waste time and space on a new env. Using usize::MAX means we'll get an overflow if we
// try to access anything rather than a silent error.
self.current_env = EnvironmentRef(usize::MAX, 0);
}
/// Push a new stack frame on to the call stack with no connection to a parent environment.
///
/// Suitable for executing a separate module.
@ -683,7 +667,7 @@ impl Stack {
env.contains_key(var)
}
/// Get a key from the first KCL (i.e., non-Rust) stack frame on the call stack.
/// Get a key from the first stack frame on the call stack.
pub fn get_from_call_stack(&self, key: &str, source_range: SourceRange) -> Result<(usize, &KclValue), KclError> {
if !self.current_env.skip_env() {
return Ok((self.current_env.1, self.get(key, source_range)?));
@ -695,7 +679,7 @@ impl Stack {
}
}
unreachable!("It can't be Rust frames all the way down");
unreachable!("No frames on the stack?");
}
/// Iterate over all keys in the current environment which satisfy the provided predicate.
@ -1217,24 +1201,6 @@ mod test {
assert_get_from(mem, "c", 5, callee);
}
#[test]
fn rust_env() {
let mem = &mut Stack::new_for_tests();
mem.add("a".to_owned(), val(1), sr()).unwrap();
mem.add("b".to_owned(), val(3), sr()).unwrap();
let sn = mem.snapshot();
mem.push_new_env_for_rust_call();
mem.push_new_env_for_call(sn);
assert_get(mem, "b", 3);
mem.add("b".to_owned(), val(4), sr()).unwrap();
assert_get(mem, "b", 4);
mem.pop_env();
mem.pop_env();
assert_get(mem, "b", 3);
}
#[test]
fn deep_call_env() {
let mem = &mut Stack::new_for_tests();

View File

@ -1920,6 +1920,22 @@ shape = layer() |> patternTransform(instances = 10, transform = transform)
);
}
#[tokio::test(flavor = "multi_thread")]
async fn pass_std_to_std() {
let ast = r#"sketch001 = startSketchOn(XY)
profile001 = circle(sketch001, center = [0, 0], radius = 2)
extrude001 = extrude(profile001, length = 5)
extrudes = patternLinear3d(
extrude001,
instances = 3,
distance = 5,
axis = [1, 1, 0],
)
clone001 = map(extrudes, f = clone)
"#;
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn test_zero_param_fn() {
let ast = r#"sigmaAllow = 35000 // psi

View File

@ -840,6 +840,18 @@ pub enum UnitType {
Angle(UnitAngle),
}
impl UnitType {
pub(crate) fn to_suffix(self) -> Option<String> {
match self {
UnitType::Count => Some("_".to_owned()),
UnitType::Length(UnitLen::Unknown) => None,
UnitType::Angle(UnitAngle::Unknown) => None,
UnitType::Length(l) => Some(l.to_string()),
UnitType::Angle(a) => Some(a.to_string()),
}
}
}
impl std::fmt::Display for UnitType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {

View File

@ -45,6 +45,31 @@ pub fn format_number_literal(value: f64, suffix: NumericSuffix) -> Result<String
}
}
#[derive(Debug, Clone, PartialEq, Serialize, thiserror::Error)]
#[serde(tag = "type")]
pub enum FormatNumericTypeError {
#[error("Invalid numeric type: {0:?}")]
Invalid(NumericType),
}
/// For UI code generation, format a number value with a suffix such that the
/// result can parse as a literal. If it can't be done, returns an error.
///
/// This is used by TS.
pub fn format_number_value(value: f64, ty: NumericType) -> Result<String, FormatNumericTypeError> {
match ty {
NumericType::Default { .. } => Ok(value.to_string()),
// There isn't a syntactic suffix for these. For unknown, we don't want
// to ever generate the unknown suffix. We currently warn on it, and we
// may remove it in the future.
NumericType::Unknown | NumericType::Any => Err(FormatNumericTypeError::Invalid(ty)),
NumericType::Known(unit_type) => unit_type
.to_suffix()
.map(|suffix| format!("{value}{suffix}"))
.ok_or(FormatNumericTypeError::Invalid(ty)),
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
@ -134,4 +159,74 @@ mod tests {
Err(FormatNumericSuffixError::Invalid(NumericSuffix::Unknown))
);
}
#[test]
fn test_format_number_value() {
assert_eq!(
format_number_value(
1.0,
NumericType::Default {
len: Default::default(),
angle: Default::default()
}
),
Ok("1".to_owned())
);
assert_eq!(
format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLen::Unknown))),
Err(FormatNumericTypeError::Invalid(NumericType::Known(UnitType::Length(
UnitLen::Unknown
))))
);
assert_eq!(
format_number_value(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Unknown))),
Err(FormatNumericTypeError::Invalid(NumericType::Known(UnitType::Angle(
UnitAngle::Unknown
))))
);
assert_eq!(
format_number_value(1.0, NumericType::Known(UnitType::Count)),
Ok("1_".to_owned())
);
assert_eq!(
format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLen::Mm))),
Ok("1mm".to_owned())
);
assert_eq!(
format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLen::Cm))),
Ok("1cm".to_owned())
);
assert_eq!(
format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLen::M))),
Ok("1m".to_owned())
);
assert_eq!(
format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLen::Inches))),
Ok("1in".to_owned())
);
assert_eq!(
format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLen::Feet))),
Ok("1ft".to_owned())
);
assert_eq!(
format_number_value(1.0, NumericType::Known(UnitType::Length(UnitLen::Yards))),
Ok("1yd".to_owned())
);
assert_eq!(
format_number_value(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Degrees))),
Ok("1deg".to_owned())
);
assert_eq!(
format_number_value(1.0, NumericType::Known(UnitType::Angle(UnitAngle::Radians))),
Ok("1rad".to_owned())
);
assert_eq!(
format_number_value(1.0, NumericType::Unknown),
Err(FormatNumericTypeError::Invalid(NumericType::Unknown))
);
assert_eq!(
format_number_value(1.0, NumericType::Any),
Err(FormatNumericTypeError::Invalid(NumericType::Any))
);
}
}

View File

@ -140,7 +140,7 @@ pub mod std_utils {
pub mod pretty {
pub use crate::{
fmt::{format_number_literal, human_display_number},
fmt::{format_number_literal, format_number_value, human_display_number},
parsing::token::NumericSuffix,
};
}

View File

@ -25,7 +25,7 @@ kittycad = { workspace = true }
kittycad-modeling-cmds = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true, features = ["sync", "rt"] }
toml = "0.8.22"
toml = "0.8.23"
tower-lsp = { workspace = true, features = ["runtime-agnostic"] }
uuid = { workspace = true, features = ["v4", "js", "serde"] }
wasm-bindgen = "0.2.99"

View File

@ -61,6 +61,37 @@ pub fn format_number_literal(value: f64, suffix_json: &str) -> Result<String, Js
kcl_lib::pretty::format_number_literal(value, suffix).map_err(JsError::from)
}
#[wasm_bindgen]
pub fn format_number_value(value: f64, numeric_type_json: &str) -> Result<String, String> {
console_error_panic_hook::set_once();
// ts-rs can't handle tuple types, so it mashes all of these types together.
if let Ok(ty) = serde_json::from_str::<NumericType>(numeric_type_json) {
if let Ok(formatted) = kcl_lib::pretty::format_number_value(value, ty) {
return Ok(formatted);
}
}
if let Ok(unit_type) = serde_json::from_str::<UnitType>(numeric_type_json) {
let ty = NumericType::Known(unit_type);
if let Ok(formatted) = kcl_lib::pretty::format_number_value(value, ty) {
return Ok(formatted);
}
}
if let Ok(unit_len) = serde_json::from_str::<UnitLen>(numeric_type_json) {
let ty = NumericType::Known(UnitType::Length(unit_len));
if let Ok(formatted) = kcl_lib::pretty::format_number_value(value, ty) {
return Ok(formatted);
}
}
if let Ok(unit_angle) = serde_json::from_str::<UnitAngle>(numeric_type_json) {
let ty = NumericType::Known(UnitType::Angle(unit_angle));
if let Ok(formatted) = kcl_lib::pretty::format_number_value(value, ty) {
return Ok(formatted);
}
}
Err(format!("Invalid type: {numeric_type_json}"))
}
#[wasm_bindgen]
pub fn human_display_number(value: f64, ty_json: &str) -> Result<String, String> {
console_error_panic_hook::set_once();

View File

@ -394,7 +394,7 @@ export function Toolbar({
<div className="flex flex-col items-center absolute top-full left-1/2 -translate-x-1/2">
{isInTemporaryWorkspace && (
<div className="flex flex-row gap-2 justify-center">
<div className="mt-2 py-1 animate-pulse w-fit uppercase text-xs rounded-full ml-2 px-2 py-1 border border-chalkboard-40 dark:text-chalkboard-40 bg-chalkboard-10 dark:bg-chalkboard-90 shadow-lg">
<div className="mt-2 animate-pulse w-fit uppercase text-xs rounded-full ml-2 px-2 py-1 border border-chalkboard-40 dark:text-chalkboard-40 bg-chalkboard-10 dark:bg-chalkboard-90 shadow-lg flex items-center">
Temporary workspace
</div>
<button

View File

@ -6,7 +6,7 @@ import {
BillingRemainingMode,
} from '@src/components/BillingRemaining'
import type { BillingActor } from '@src/machines/billingMachine'
import { type BillingActor } from '@src/machines/billingMachine'
export const BillingDialog = (props: { billingActor: BillingActor }) => {
const billingContext = useSelector(
@ -39,15 +39,18 @@ export const BillingDialog = (props: { billingActor: BillingActor }) => {
mode={BillingRemainingMode.ProgressBarStretch}
billingActor={props.billingActor}
/>
<a
className="bg-ml-black text-ml-white rounded-lg text-center p-1 cursor-pointer"
href="https://zoo.dev/design-studio-pricing"
target="_blank"
rel="noopener noreferrer"
onClick={openExternalBrowserIfDesktop()}
>
Upgrade
</a>
{!hasUnlimited && (
<a
className="bg-ml-black text-ml-white rounded-lg text-center p-1 cursor-pointer"
href="https://zoo.dev/design-studio-pricing"
target="_blank"
rel="noopener noreferrer"
data-testid="billing-upgrade-button"
onClick={openExternalBrowserIfDesktop()}
>
Upgrade
</a>
)}
</div>
</div>
)

View File

@ -12,7 +12,7 @@ import type {
} from '@src/lib/commandTypes'
import type { Selections } from '@src/lib/selections'
import { getSelectionTypeDisplayText } from '@src/lib/selections'
import { roundOff } from '@src/lib/utils'
import { roundOffWithUnits } from '@src/lib/utils'
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
function CommandBarHeaderFooter({
@ -163,10 +163,8 @@ function CommandBarHeaderFooter({
arg.inputType === 'selectionMixed' ? (
getSelectionTypeDisplayText(argValue as Selections)
) : arg.inputType === 'kcl' ? (
roundOff(
Number(
(argValue as KclCommandValue).valueCalculated
),
roundOffWithUnits(
(argValue as KclCommandValue).valueCalculated,
4
)
) : arg.inputType === 'text' &&

View File

@ -21,13 +21,13 @@ import { Spinner } from '@src/components/Spinner'
import { createLocalName, createVariableDeclaration } from '@src/lang/create'
import { getNodeFromPath } from '@src/lang/queryAst'
import type { SourceRange, VariableDeclarator } from '@src/lang/wasm'
import { isPathToNode } from '@src/lang/wasm'
import { formatNumberValue, isPathToNode } from '@src/lang/wasm'
import type { CommandArgument, KclCommandValue } from '@src/lib/commandTypes'
import { kclManager } from '@src/lib/singletons'
import { getSystemTheme } from '@src/lib/theme'
import { err } from '@src/lib/trap'
import { useCalculateKclExpression } from '@src/lib/useCalculateKclExpression'
import { roundOff } from '@src/lib/utils'
import { roundOff, roundOffWithUnits } from '@src/lib/utils'
import { varMentions } from '@src/lib/varCompletionExtension'
import { useSettings } from '@src/lib/singletons'
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
@ -128,10 +128,22 @@ function CommandBarKclInput({
sourceRange: sourceRangeForPrevVariables,
})
const varMentionData: Completion[] = prevVariables.map((v) => ({
label: v.key,
detail: String(roundOff(Number(v.value))),
}))
const varMentionData: Completion[] = prevVariables.map((v) => {
const roundedWithUnits = (() => {
if (typeof v.value !== 'number' || !v.ty) {
return undefined
}
const numWithUnits = formatNumberValue(v.value, v.ty)
if (err(numWithUnits)) {
return undefined
}
return roundOffWithUnits(numWithUnits)
})()
return {
label: v.key,
detail: roundedWithUnits ?? String(roundOff(Number(v.value))),
}
})
const varMentionsExtension = varMentions(varMentionData)
const { setContainer, view } = useCodeMirror({
@ -282,7 +294,7 @@ function CommandBarKclInput({
) : calcResult === 'NAN' ? (
"Can't calculate"
) : (
roundOff(Number(calcResult), 4)
roundOffWithUnits(calcResult, 4)
)}
</span>
</label>

View File

@ -110,6 +110,7 @@ export const FeatureTreePane = () => {
// devTools: true,
}
)
// If there are parse errors we show the last successful operations
// and overlay a message on top of the pane
const parseErrors = kclManager.errors.filter((e) => e.kind !== 'engine')
@ -163,12 +164,13 @@ export const FeatureTreePane = () => {
{!modelingState.matches('Sketch') && <DefaultPlanes />}
{parseErrors.length > 0 && (
<div
className={`absolute inset-0 rounded-lg p-2 ${
operationList.length &&
`bg-destroy-10/40 dark:bg-destroy-80/40`
className={`absolute inset-0 p-2 ${
operationList.length ||
(parseErrors.length > 0 &&
`bg-destroy-10/40 dark:bg-destroy-80/40`)
}`}
>
<div className="text-sm bg-destroy-80 text-chalkboard-10 py-1 px-2 rounded flex gap-2 items-center">
<div className="text-base font-sans font-normal text-destroy-80 dark:text-destroy-10 bg-destroy-10 dark:bg-destroy-80 py-1 px-2 rounded flex gap-2 items-center">
<p className="flex-1">
Errors found in KCL code.
<br />
@ -176,7 +178,7 @@ export const FeatureTreePane = () => {
</p>
<button
onClick={goToError}
className="bg-chalkboard-10 text-destroy-80 p-1 rounded-sm flex-none hover:bg-chalkboard-10 hover:border-destroy-70 hover:text-destroy-80 border-transparent"
className="border bg-chalkboard-10 text-destroy-80 p-1 rounded flex-none hover:bg-chalkboard-10 hover:border-destroy-70 hover:text-destroy-80 border-transparent"
>
View error
</button>

View File

@ -63,11 +63,36 @@ variableBelowShouldNotBeIncluded = 3
execState.variables,
topLevelRange(rangeStart, rangeStart)
)
const defaultTy = {
type: 'Default',
angle: {
type: 'Degrees',
},
len: {
type: 'Mm',
},
}
expect(variables).toEqual([
{ key: 'baseThick', value: 1 },
{ key: 'armAngle', value: 60 },
{ key: 'baseThickHalf', value: 0.5 },
{ key: 'halfArmAngle', value: 30 },
{
key: 'baseThick',
value: 1,
ty: defaultTy,
},
{
key: 'armAngle',
value: 60,
ty: defaultTy,
},
{
key: 'baseThickHalf',
value: 0.5,
ty: defaultTy,
},
{
key: 'halfArmAngle',
value: 30,
ty: defaultTy,
},
// no arrExpShouldNotBeIncluded, variableBelowShouldNotBeIncluded etc
])
// there are 4 number variables and 2 non-number variables before the sketch var

View File

@ -55,6 +55,7 @@ import type { OpKclValue, Operation } from '@rust/kcl-lib/bindings/Operation'
import { ARG_INDEX_FIELD, LABELED_ARG_FIELD } from '@src/lang/queryAstConstants'
import type { KclCommandValue } from '@src/lib/commandTypes'
import type { UnaryExpression } from 'typescript'
import type { NumericType } from '@rust/kcl-lib/bindings/NumericType'
/**
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
@ -306,6 +307,7 @@ export function traverse(
export interface PrevVariable<T> {
key: string
value: T
ty: NumericType | undefined
}
export function findAllPreviousVariablesPath(
@ -353,6 +355,7 @@ export function findAllPreviousVariablesPath(
variables.push({
key: varName,
value: varValue.value,
ty: varValue.type === 'Number' ? varValue.ty : undefined,
})
})

View File

@ -45,6 +45,7 @@ import {
default_app_settings,
default_project_settings,
format_number_literal,
format_number_value,
get_kcl_version,
get_tangential_arc_to_info,
human_display_number,
@ -448,6 +449,23 @@ export function formatNumberLiteral(
}
}
/**
* Format a number from a KclValue such that it could be parsed as KCL.
*/
export function formatNumberValue(
value: number,
numericType: NumericType
): string | Error {
try {
return format_number_value(value, JSON.stringify(numericType))
} catch (e) {
return new Error(
`Error formatting number value: value=${value}, numericType=${numericType}`,
{ cause: e }
)
}
}
/**
* Debug display a number with suffix, for human consumption only.
*/

View File

@ -2,13 +2,20 @@ import type { ParseResult } from '@src/lang/wasm'
import { getCalculatedKclExpressionValue } from '@src/lib/kclHelpers'
describe('KCL expression calculations', () => {
it('calculates a simple expression', async () => {
it('calculates a simple expression without units', async () => {
const actual = await getCalculatedKclExpressionValue('1 + 2')
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual).not.toHaveProperty('errors')
expect(coercedActual.valueAsString).toEqual('3')
expect(coercedActual?.astNode).toBeDefined()
})
it('calculates a simple expression with units', async () => {
const actual = await getCalculatedKclExpressionValue('1deg + 30deg')
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>
expect(coercedActual).not.toHaveProperty('errors')
expect(coercedActual.valueAsString).toEqual('31deg')
expect(coercedActual?.astNode).toBeDefined()
})
it('returns NAN for an invalid expression', async () => {
const actual = await getCalculatedKclExpressionValue('1 + x')
const coercedActual = actual as Exclude<typeof actual, Error | ParseResult>

View File

@ -1,5 +1,10 @@
import { executeAstMock } from '@src/lang/langHelpers'
import { type CallExpressionKw, parse, resultIsOk } from '@src/lang/wasm'
import {
type CallExpressionKw,
formatNumberValue,
parse,
resultIsOk,
} from '@src/lang/wasm'
import type { KclCommandValue, KclExpression } from '@src/lib/commandTypes'
import { rustContext } from '@src/lib/singletons'
import { err } from '@src/lib/trap'
@ -32,12 +37,27 @@ export async function getCalculatedKclExpressionValue(value: string) {
const variableDeclaratorAstNode =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declaration.init
const resultRawValue = execState.variables[DUMMY_VARIABLE_NAME]?.value
const varValue = execState.variables[DUMMY_VARIABLE_NAME]
// If the value is a number, attempt to format it with units.
const resultValueWithUnits = (() => {
if (!varValue || varValue.type !== 'Number') {
return undefined
}
const formatted = formatNumberValue(varValue.value, varValue.ty)
if (err(formatted)) return undefined
return formatted
})()
// Prefer the formatted value with units. Fallback to the raw value.
const resultRawValue = varValue?.value
const valueAsString = resultValueWithUnits
? resultValueWithUnits
: typeof resultRawValue === 'number'
? String(resultRawValue)
: 'NAN'
return {
astNode: variableDeclaratorAstNode,
valueAsString:
typeof resultRawValue === 'number' ? String(resultRawValue) : 'NAN',
valueAsString,
}
}

View File

@ -79,7 +79,7 @@ export function useCalculateKclExpression({
isValueParsable = false
}
const initialCalcResult: number | string =
Number.isNaN(Number(value)) || !isValueParsable ? 'NAN' : value
Number.isNaN(parseFloat(value)) || !isValueParsable ? 'NAN' : value
const [calcResult, setCalcResult] = useState(initialCalcResult)
const [newVariableName, _setNewVariableName] = useState('')
const [isNewVariableNameUnique, setIsNewVariableNameUnique] = useState(true)

View File

@ -8,6 +8,7 @@ import {
isOverlap,
onDragNumberCalculation,
roundOff,
roundOffWithUnits,
simulateOnMouseDragMatch,
} from '@src/lib/utils'
@ -43,6 +44,48 @@ describe('testing roundOff', () => {
})
})
describe('roundOffWithUnits', () => {
it('works with no units', () => {
expect(roundOffWithUnits('1.23456789')).toBe('1.23')
expect(roundOffWithUnits('1.23456789', 3)).toBe('1.235')
expect(roundOffWithUnits('1.', 3)).toBe('1')
expect(roundOffWithUnits('-1.23456789')).toBe('-1.23')
expect(roundOffWithUnits('-1.23456789', 3)).toBe('-1.235')
expect(roundOffWithUnits('-1.', 3)).toBe('-1')
})
it('works with standard units', () => {
expect(roundOffWithUnits('1.23456789mm', 3)).toBe('1.235mm')
expect(roundOffWithUnits('1.23456789m', 3)).toBe('1.235m')
expect(roundOffWithUnits('1.23456789in', 3)).toBe('1.235in')
expect(roundOffWithUnits('1.23456789_', 3)).toBe('1.235_')
expect(roundOffWithUnits('1._', 3)).toBe('1_')
expect(roundOffWithUnits('-1.23456789mm', 3)).toBe('-1.235mm')
expect(roundOffWithUnits('-1.23456789m', 3)).toBe('-1.235m')
expect(roundOffWithUnits('-1.23456789in', 3)).toBe('-1.235in')
expect(roundOffWithUnits('-1.23456789_', 3)).toBe('-1.235_')
expect(roundOffWithUnits('-1._', 3)).toBe('-1_')
expect(roundOffWithUnits('1.23456789e3mm', 3)).toBe('1234.568mm')
expect(roundOffWithUnits('1.23456789e3m', 3)).toBe('1234.568m')
expect(roundOffWithUnits('1.23456789e3in', 3)).toBe('1234.568in')
expect(roundOffWithUnits('1.23456789e3_', 3)).toBe('1234.568_')
expect(roundOffWithUnits('1.e3_', 3)).toBe('1000_')
expect(roundOffWithUnits('1e3_', 3)).toBe('1000_')
expect(roundOffWithUnits('1.23456789e-3mm', 3)).toBe('0.001mm')
expect(roundOffWithUnits('1.23456789e-3m', 3)).toBe('0.001m')
expect(roundOffWithUnits('1.23456789e-3in', 3)).toBe('0.001in')
expect(roundOffWithUnits('1.23456789e-3_', 3)).toBe('0.001_')
expect(roundOffWithUnits('1.e-3_', 3)).toBe('0.001_')
expect(roundOffWithUnits('1e-3_', 3)).toBe('0.001_')
})
it('works with weird units', () => {
expect(roundOffWithUnits('1.23456789_?', 3)).toBe('1.235_?')
expect(roundOffWithUnits('-1.23456789_?', 3)).toBe('-1.235_?')
})
it('returns the original string when used with something not parsable as a number', () => {
expect(roundOffWithUnits('foo', 3)).toBe('foo')
})
})
describe('testing hasLeadingZero', () => {
it('.1 should have no leading zero', () => {
const actual = hasLeadingZero('.1')

View File

@ -328,6 +328,32 @@ export function roundOff(num: number, precision: number = 2): number {
return Math.round(num * x) / x
}
export function roundOffWithUnits(
numWithUnits: string,
precision: number = 2
): string {
const match = numWithUnits.match(
/^([+-]?[\d.]+(?:[eE][+-]?\d+)?)([a-zA-Z_?]+)$/
)
let num: string
let suffix: string
if (match) {
num = match[1]
suffix = match[2] ?? ''
} else {
// If no match, assume it's just a number with no units.
num = numWithUnits
suffix = ''
}
const parsedNum = parseFloat(num)
if (Number.isNaN(parsedNum)) {
// If parsing fails, return the original string.
return numWithUnits
}
const roundedNum = roundOff(parsedNum, precision)
return `${roundedNum}${suffix}`
}
/**
* Determine if the number as a string has any precision in the decimal places
* '1' -> 0

View File

@ -13,6 +13,7 @@ import type {
default_app_settings as DefaultAppSettings,
default_project_settings as DefaultProjectSettings,
format_number_literal as FormatNumberLiteral,
format_number_value as FormatNumberValue,
human_display_number as HumanDisplayNumber,
get_kcl_version as GetKclVersion,
get_tangential_arc_to_info as GetTangentialArcToInfo,
@ -59,6 +60,9 @@ export const recast_wasm: typeof RecastWasm = (...args) => {
export const format_number_literal: typeof FormatNumberLiteral = (...args) => {
return getModule().format_number_literal(...args)
}
export const format_number_value: typeof FormatNumberValue = (...args) => {
return getModule().format_number_value(...args)
}
export const human_display_number: typeof HumanDisplayNumber = (...args) => {
return getModule().human_display_number(...args)
}

View File

@ -9,7 +9,7 @@ export const modelingDesignRole = (
label: 'Design',
submenu: [
{
label: 'Start sketch',
label: 'Start Sketch',
id: 'Design.Start sketch',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -19,7 +19,7 @@ export const modelingDesignRole = (
},
{ type: 'separator' },
{
label: 'Create an offset plane',
label: 'Create an Offset Plane',
id: 'Design.Create an offset plane',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -28,7 +28,7 @@ export const modelingDesignRole = (
},
},
{
label: 'Create a helix',
label: 'Create a Helix',
id: 'Design.Create a helix',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -37,7 +37,7 @@ export const modelingDesignRole = (
},
},
{
label: 'Create a parameter',
label: 'Create a Parameter',
id: 'Design.Create a parameter',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -47,7 +47,7 @@ export const modelingDesignRole = (
},
{ type: 'separator' },
{
label: 'Create an additive feature',
label: 'Create an Additive Feature',
id: 'Design.Create an additive feature',
submenu: [
{
@ -89,7 +89,7 @@ export const modelingDesignRole = (
],
},
{
label: 'Apply modification feature',
label: 'Apply Modification Feature',
id: 'Design.Apply modification feature',
submenu: [
{
@ -123,7 +123,7 @@ export const modelingDesignRole = (
},
{ type: 'separator' },
{
label: 'Insert from project file',
label: 'Insert from Project File',
id: 'Design.Insert from project file',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {

View File

@ -30,7 +30,7 @@ export const projectEditRole = (
label: 'Edit',
submenu: [
{
label: 'Rename project',
label: 'Rename Project',
id: 'Edit.Rename project',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -39,7 +39,7 @@ export const projectEditRole = (
},
},
{
label: 'Delete project',
label: 'Delete Project',
id: 'Edit.Delete project',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -49,7 +49,7 @@ export const projectEditRole = (
},
{ type: 'separator' },
{
label: 'Change project directory',
label: 'Change Project Directory',
id: 'Edit.Change project directory',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -102,7 +102,7 @@ export const modelingEditRole = (
},
},
{
label: 'Edit parameter',
label: 'Edit Parameter',
id: 'Edit.Edit parameter',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -111,7 +111,7 @@ export const modelingEditRole = (
},
},
{
label: 'Format code',
label: 'Format Code',
id: 'Edit.Format code',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -121,7 +121,7 @@ export const modelingEditRole = (
},
{ type: 'separator' },
{
label: 'Rename project',
label: 'Rename Project',
id: 'Edit.Rename project',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -130,7 +130,7 @@ export const modelingEditRole = (
},
},
{
label: 'Delete project',
label: 'Delete Project',
id: 'Edit.Delete project',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -140,7 +140,7 @@ export const modelingEditRole = (
},
{ type: 'separator' },
{
label: 'Change project directory',
label: 'Change Project Directory',
id: 'Edit.Change project directory',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {

View File

@ -13,7 +13,7 @@ export const projectFileRole = (
label: 'File',
submenu: [
{
label: 'Create project',
label: 'Create Project',
id: 'File.Create project',
accelerator: 'CommandOrControl+N',
click: () => {
@ -23,7 +23,7 @@ export const projectFileRole = (
},
},
{
label: 'Open project',
label: 'Open Project',
id: 'File.Open project',
accelerator: 'CommandOrControl+P',
click: () => {
@ -36,7 +36,7 @@ export const projectFileRole = (
// Appears to be only Windows and Mac OS specific. Linux does not have support
{ type: 'separator' },
{
label: 'Add file to project',
label: 'Add File to Project',
id: 'File.Add file to project',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -58,7 +58,7 @@ export const projectFileRole = (
label: 'Preferences',
submenu: [
{
label: 'User settings',
label: 'User Settings',
id: 'File.Preferences.User settings',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -76,7 +76,7 @@ export const projectFileRole = (
},
},
{
label: 'User default units',
label: 'User Default Units',
id: 'File.Preferences.User default units',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -94,7 +94,7 @@ export const projectFileRole = (
},
},
{
label: 'Theme color',
label: 'Theme Color',
id: 'File.Preferences.Theme color',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -107,7 +107,7 @@ export const projectFileRole = (
{ type: 'separator' },
// Last in list
{
label: 'Sign out',
label: 'Sign Out',
id: 'File.Sign out',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -128,7 +128,7 @@ export const modelingFileRole = (
submenu: [
// TODO: Once a safe command bar create new file and folder is implemented we can turn these on
// {
// label: 'Create new file',
// label: 'Create New File',
// click: () => {
// typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
// menuLabel: 'File.Create new file',
@ -136,7 +136,7 @@ export const modelingFileRole = (
// },
// },
// {
// label: 'Create new folder',
// label: 'Create New Folder',
// click: () => {
// typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
// menuLabel: 'File.Create new folder',
@ -144,7 +144,7 @@ export const modelingFileRole = (
// },
// },
{
label: 'Create project',
label: 'Create Project',
id: 'File.Create project',
accelerator: 'CommandOrControl+N',
click: () => {
@ -154,7 +154,7 @@ export const modelingFileRole = (
},
},
{
label: 'Open project',
label: 'Open Project',
id: 'File.Open project',
accelerator: 'CommandOrControl+P',
click: () => {
@ -167,7 +167,7 @@ export const modelingFileRole = (
// Appears to be only Windows and Mac OS specific. Linux does not have support
{ type: 'separator' },
{
label: 'Add file to project',
label: 'Add File to Project',
id: 'File.Add file to project',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -176,7 +176,7 @@ export const modelingFileRole = (
},
},
{
label: 'Export current part',
label: 'Export Current Part',
id: 'File.Export current part',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -189,7 +189,7 @@ export const modelingFileRole = (
label: 'Preferences',
submenu: [
{
label: 'Project settings',
label: 'Project Settings',
id: 'File.Preferences.Project settings',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -198,7 +198,7 @@ export const modelingFileRole = (
},
},
{
label: 'User settings',
label: 'User Settings',
id: 'File.Preferences.User settings',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -216,7 +216,7 @@ export const modelingFileRole = (
},
},
{
label: 'User default units',
label: 'User Default Units',
id: 'File.Preferences.User default units',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -234,7 +234,7 @@ export const modelingFileRole = (
},
},
{
label: 'Theme color',
label: 'Theme Color',
id: 'File.Preferences.Theme color',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -247,7 +247,7 @@ export const modelingFileRole = (
{ type: 'separator' },
// Last in list
{
label: 'Sign out',
label: 'Sign Out',
id: 'File.Sign out',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {

View File

@ -14,7 +14,7 @@ export const helpRole = (
submenu: [
{
id: 'Help.Show all commands',
label: 'Show all commands',
label: 'Show All Commands',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
menuLabel: 'Help.Command Palette...',
@ -22,7 +22,7 @@ export const helpRole = (
},
},
{
label: 'KCL code samples',
label: 'KCL Code Samples',
id: 'Help.KCL code samples',
click: () => {
shell
@ -31,13 +31,13 @@ export const helpRole = (
},
},
{
label: 'KCL docs',
label: 'KCL Docs',
click: () => {
shell.openExternal('https://zoo.dev/docs/kcl').catch(reportRejection)
},
},
{
label: 'Get started with Text-to-CAD',
label: 'Get Started with Text-to-CAD',
click: () => {
shell
.openExternal('https://text-to-cad.zoo.dev/dashboard')
@ -46,7 +46,7 @@ export const helpRole = (
},
{ type: 'separator' },
{
label: 'Ask the community discord',
label: 'Ask the Community Discord',
click: () => {
shell
.openExternal('https://discord.gg/JQEpHR7Nt2')
@ -54,7 +54,7 @@ export const helpRole = (
},
},
{
label: 'Ask the community discourse',
label: 'Ask the Community Discourse',
click: () => {
shell
.openExternal('https://community.zoo.dev/')
@ -63,7 +63,7 @@ export const helpRole = (
},
{ type: 'separator' },
{
label: 'Report a bug',
label: 'Report a Bug',
id: 'Help.Report a bug',
click: () => {
shell
@ -74,7 +74,7 @@ export const helpRole = (
},
},
{
label: 'Request a feature',
label: 'Request a Feature',
click: () => {
shell
.openExternal(
@ -86,7 +86,7 @@ export const helpRole = (
{ type: 'separator' },
{
id: 'Help.Replay onboarding tutorial',
label: 'Replay onboarding tutorial',
label: 'Replay Onboarding Tutorial',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
menuLabel: 'Help.Replay onboarding tutorial',
@ -99,7 +99,7 @@ export const helpRole = (
{ role: 'forceReload' },
{ type: 'separator' },
{
label: 'Show release notes',
label: 'Show Release Notes',
click: () => {
shell
.openExternal('https://github.com/KittyCAD/modeling-app/releases')
@ -107,14 +107,14 @@ export const helpRole = (
},
},
{
label: 'Check for updates',
label: 'Check for Updates',
click: () => {
getAutoUpdater().checkForUpdates().catch(reportRejection)
},
},
{ type: 'separator' },
{
label: 'Manage account',
label: 'Manage Account',
click: () => {
shell.openExternal('https://zoo.dev/account').catch(reportRejection)
},

View File

@ -12,47 +12,47 @@ type HeaderLabel =
| 'View'
type FileRoleLabel =
| 'Open project'
| 'Create project'
| 'Import file from URL'
| 'Open Project'
| 'Create Project'
| 'Import File from URL'
| 'Preferences'
| 'User settings'
| 'User Settings'
| 'Keybindings'
| 'Sign out'
| 'Sign Out'
| 'Theme'
| 'Theme color'
| 'Export current part'
| 'Create new file'
| 'Create new folder'
| 'Share part via Zoo link'
| 'Project settings'
| 'Add file to project'
| 'User default units'
| 'Theme Color'
| 'Export Current Part'
| 'Create New File'
| 'Create New Folder'
| 'Share Part via Zoo Link'
| 'Project Settings'
| 'Add File to Project'
| 'User Default Units'
type EditRoleLabel =
| 'Rename project'
| 'Delete project'
| 'Change project directory'
| 'Rename Project'
| 'Delete Project'
| 'Change Project Directory'
| 'Undo'
| 'Redo'
| 'Speech'
| 'Edit parameter'
| 'Edit Parameter'
| 'Modify with Zoo Text-To-CAD'
| 'Format code'
| 'Format Code'
type HelpRoleLabel =
| 'Report a bug'
| 'Request a feature'
| 'Ask the community discord'
| 'Ask the community discourse'
| 'KCL code samples'
| 'KCL docs'
| 'Replay onboarding tutorial'
| 'Show release notes'
| 'Check for updates'
| 'Manage account'
| 'Get started with Text-to-CAD'
| 'Show all commands'
| 'Report a Bug'
| 'Request a Feature'
| 'Ask the Community Discord'
| 'Ask the Community Discourse'
| 'KCL Code Samples'
| 'KCL Docs'
| 'Replay Onboarding Tutorial'
| 'Show Release Notes'
| 'Check for Updates'
| 'Manage Account'
| 'Get Started with Text-to-CAD'
| 'Show All Commands'
type ViewRoleLabel =
| 'Command Palette...'
@ -64,37 +64,37 @@ type ViewRoleLabel =
| 'Variables'
| 'Logs'
| 'Debug'
| 'Standard views'
| 'Orthographic view'
| 'Perspective view'
| 'Right view'
| 'Back view'
| 'Top view'
| 'Left view'
| 'Front view'
| 'Bottom view'
| 'Reset view'
| 'Center view on selection'
| 'Standard Views'
| 'Orthographic View'
| 'Perspective View'
| 'Right View'
| 'Back View'
| 'Top View'
| 'Left View'
| 'Front View'
| 'Bottom View'
| 'Reset View'
| 'Center View on Selection'
| 'Refresh'
| 'Named views'
| 'Create named view'
| 'Load named view'
| 'Delete named view'
| 'Named Views'
| 'Create Named View'
| 'Load Named View'
| 'Delete Named View'
type DesignRoleLabel =
| 'Design'
| 'Create a parameter'
| 'Insert from project file'
| 'Create a Parameter'
| 'Insert from Project File'
| 'Create with Zoo Text-To-CAD'
| 'Start sketch'
| 'Create an offset plane'
| 'Create a helix'
| 'Create an additive feature'
| 'Start Sketch'
| 'Create an Offset Plane'
| 'Create a Helix'
| 'Create an Additive Feature'
| 'Extrude'
| 'Revolve'
| 'Sweep'
| 'Loft'
| 'Apply modification feature'
| 'Apply Modification Feature'
| 'Fillet'
| 'Chamfer'
| 'Shell'

View File

@ -74,7 +74,7 @@ export const modelingViewRole = (
},
{ type: 'separator' },
{
label: 'Orthographic view',
label: 'Orthographic View',
id: 'View.Orthographic view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -83,7 +83,7 @@ export const modelingViewRole = (
},
},
{
label: 'Perspective view',
label: 'Perspective View',
id: 'View.Perspective view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -93,11 +93,11 @@ export const modelingViewRole = (
},
{ type: 'separator' },
{
label: 'Standard views',
label: 'Standard Views',
id: 'View.Standard views',
submenu: [
{
label: 'Right view',
label: 'Right View',
id: 'View.Standard views.Right view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -106,7 +106,7 @@ export const modelingViewRole = (
},
},
{
label: 'Back view',
label: 'Back View',
id: 'View.Standard views.Back view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -114,9 +114,8 @@ export const modelingViewRole = (
})
},
},
{
label: 'Top view',
label: 'Top View',
id: 'View.Standard views.Top view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -124,9 +123,8 @@ export const modelingViewRole = (
})
},
},
{
label: 'Left view',
label: 'Left View',
id: 'View.Standard views.Left view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -134,9 +132,8 @@ export const modelingViewRole = (
})
},
},
{
label: 'Front view',
label: 'Front View',
id: 'View.Standard views.Front view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -144,9 +141,8 @@ export const modelingViewRole = (
})
},
},
{
label: 'Bottom view',
label: 'Bottom View',
id: 'View.Standard views.Bottom view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -156,7 +152,7 @@ export const modelingViewRole = (
},
{ type: 'separator' },
{
label: 'Reset view',
label: 'Reset View',
id: 'View.Standard views.Reset view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -164,9 +160,8 @@ export const modelingViewRole = (
})
},
},
{
label: 'Center view on selection',
label: 'Center View on Selection',
id: 'View.Standard views.Center view on selection',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -186,11 +181,11 @@ export const modelingViewRole = (
],
},
{
label: 'Named views',
label: 'Named Views',
id: 'View.Named views',
submenu: [
{
label: 'Create named view',
label: 'Create Named View',
id: 'View.Named views.Create named view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -198,9 +193,8 @@ export const modelingViewRole = (
})
},
},
{
label: 'Load named view',
label: 'Load Named View',
id: 'View.Named views.Load named view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {
@ -208,9 +202,8 @@ export const modelingViewRole = (
})
},
},
{
label: 'Delete named view',
label: 'Delete Named View',
id: 'View.Named views.Delete named view',
click: () => {
typeSafeWebContentsSend(mainWindow, 'menu-action-clicked', {

View File

@ -66,6 +66,7 @@ import {
defaultLocalStatusBarItems,
defaultGlobalStatusBarItems,
} from '@src/components/StatusBar/defaultStatusBarItems'
import { useSelector } from '@xstate/react'
type ReadWriteProjectState = {
value: boolean
@ -81,6 +82,8 @@ const Home = () => {
const [nativeFileMenuCreated, setNativeFileMenuCreated] = useState(false)
const apiToken = useToken()
const networkMachineStatus = useNetworkMachineStatus()
const billingContext = useSelector(billingActor, ({ context }) => context)
const hasUnlimitedCredits = billingContext.credits === Infinity
// Only create the native file menus on desktop
useEffect(() => {
@ -354,11 +357,13 @@ const Home = () => {
</li>
</ul>
<ul className="flex flex-col">
<li className="contents">
<div className="my-2">
<BillingDialog billingActor={billingActor} />
</div>
</li>
{!hasUnlimitedCredits && (
<li className="contents">
<div className="my-2">
<BillingDialog billingActor={billingActor} />
</div>
</li>
)}
<li className="contents">
<ActionButton
Element="externalLink"