Compare commits
13 Commits
jtran/fix-
...
achalmers/
Author | SHA1 | Date | |
---|---|---|---|
df167c0382 | |||
15857e9191 | |||
1a32f664d0 | |||
5c2dfb8e40 | |||
0e341d7863 | |||
6a03ff9596 | |||
d7bd0c937d | |||
d3b2483f4f | |||
7838b7c9fd | |||
130ecf1f88 | |||
550d8b3753 | |||
696222a070 | |||
edb424988d |
10
.github/workflows/e2e-tests.yml
vendored
10
.github/workflows/e2e-tests.yml
vendored
@ -234,10 +234,16 @@ jobs:
|
|||||||
shardTotal: 8
|
shardTotal: 8
|
||||||
- os: namespace-profile-macos-8-cores
|
- os: namespace-profile-macos-8-cores
|
||||||
shardIndex: 1
|
shardIndex: 1
|
||||||
shardTotal: 1
|
shardTotal: 2
|
||||||
|
- os: namespace-profile-macos-8-cores
|
||||||
|
shardIndex: 2
|
||||||
|
shardTotal: 2
|
||||||
- os: windows-latest-8-cores
|
- os: windows-latest-8-cores
|
||||||
shardIndex: 1
|
shardIndex: 1
|
||||||
shardTotal: 1
|
shardTotal: 2
|
||||||
|
- os: windows-latest-8-cores
|
||||||
|
shardIndex: 2
|
||||||
|
shardTotal: 2
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -13,6 +13,7 @@ sweep(
|
|||||||
path: Sketch | Helix,
|
path: Sketch | Helix,
|
||||||
sectional?: bool,
|
sectional?: bool,
|
||||||
tolerance?: number,
|
tolerance?: number,
|
||||||
|
relativeTo?: string,
|
||||||
tagStart?: TagDeclarator,
|
tagStart?: TagDeclarator,
|
||||||
tagEnd?: TagDeclarator,
|
tagEnd?: TagDeclarator,
|
||||||
): [Solid]
|
): [Solid]
|
||||||
@ -30,6 +31,7 @@ You can provide more than one sketch to sweep, and they will all be swept along
|
|||||||
| `path` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) or [`Helix`](/docs/kcl-std/types/std-types-Helix) | The path to sweep the sketch along | Yes |
|
| `path` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) or [`Helix`](/docs/kcl-std/types/std-types-Helix) | The path to sweep the sketch along | Yes |
|
||||||
| `sectional` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
| `sectional` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
||||||
| `tolerance` | [`number`](/docs/kcl-std/types/std-types-number) | Tolerance for this operation | No |
|
| `tolerance` | [`number`](/docs/kcl-std/types/std-types-number) | Tolerance for this operation | No |
|
||||||
|
| `relativeTo` | [`string`](/docs/kcl-std/types/std-types-string) | What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. Defaults to sketchPlane. | No |
|
||||||
| `tagStart` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the start of the sweep, i.e. the original sketch | No |
|
| `tagStart` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the start of the sweep, i.e. the original sketch | No |
|
||||||
| `tagEnd` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the end of the sweep | No |
|
| `tagEnd` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the end of the sweep | No |
|
||||||
|
|
||||||
|
@ -58,12 +58,6 @@ test(
|
|||||||
await expect(submitButton).toBeVisible()
|
await expect(submitButton).toBeVisible()
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Look out for the toast message
|
|
||||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
|
||||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
|
||||||
await expect(exportingToastMessage).toBeVisible()
|
|
||||||
await expect(alreadyExportingToastMessage).not.toBeVisible()
|
|
||||||
|
|
||||||
// Expect it to succeed
|
// Expect it to succeed
|
||||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||||
@ -72,7 +66,6 @@ test(
|
|||||||
|
|
||||||
const successToastMessage = page.getByText(`Exported successfully`)
|
const successToastMessage = page.getByText(`Exported successfully`)
|
||||||
await expect(successToastMessage).toBeVisible()
|
await expect(successToastMessage).toBeVisible()
|
||||||
await expect(exportingToastMessage).not.toBeVisible()
|
|
||||||
|
|
||||||
// Check for the exported file
|
// Check for the exported file
|
||||||
const firstFileFullPath = path.resolve(
|
const firstFileFullPath = path.resolve(
|
||||||
|
@ -155,6 +155,12 @@ export class CmdBarFixture {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
closeCmdBar = async () => {
|
||||||
|
const cmdBarCloseBtn = this.page.getByTestId('command-bar-close-button')
|
||||||
|
await cmdBarCloseBtn.click()
|
||||||
|
await expect(this.cmdBarElement).not.toBeVisible()
|
||||||
|
}
|
||||||
|
|
||||||
get cmdSearchInput() {
|
get cmdSearchInput() {
|
||||||
return this.page.getByTestId('cmd-bar-search')
|
return this.page.getByTestId('cmd-bar-search')
|
||||||
}
|
}
|
||||||
@ -298,4 +304,27 @@ export class CmdBarFixture {
|
|||||||
`Monitoring text-to-cad API requests. Output will be saved to: ${outputPath}`
|
`Monitoring text-to-cad API requests. Output will be saved to: ${outputPath}`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async toBeOpened() {
|
||||||
|
// Check that the command bar is opened
|
||||||
|
await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 })
|
||||||
|
}
|
||||||
|
|
||||||
|
async expectArgValue(value: string) {
|
||||||
|
// Check the placeholder project name exists
|
||||||
|
const actualArgument = await this.cmdBarElement
|
||||||
|
.getByTestId('cmd-bar-arg-value')
|
||||||
|
.inputValue()
|
||||||
|
const expectedArgument = value
|
||||||
|
expect(actualArgument).toBe(expectedArgument)
|
||||||
|
}
|
||||||
|
|
||||||
|
async expectCommandName(value: string) {
|
||||||
|
// Check the placeholder project name exists
|
||||||
|
const actual = await this.cmdBarElement
|
||||||
|
.getByTestId('command-name')
|
||||||
|
.textContent()
|
||||||
|
const expected = value
|
||||||
|
expect(actual).toBe(expected)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ export class HomePageFixture {
|
|||||||
projectTextName!: Locator
|
projectTextName!: Locator
|
||||||
sortByDateBtn!: Locator
|
sortByDateBtn!: Locator
|
||||||
sortByNameBtn!: Locator
|
sortByNameBtn!: Locator
|
||||||
|
appHeader!: Locator
|
||||||
tutorialBtn!: Locator
|
tutorialBtn!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
@ -44,6 +45,7 @@ export class HomePageFixture {
|
|||||||
|
|
||||||
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
|
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
|
||||||
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
||||||
|
this.appHeader = this.page.getByTestId('app-header')
|
||||||
this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
|
this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,4 +127,11 @@ export class HomePageFixture {
|
|||||||
|
|
||||||
await this.createAndGoToProject(name)
|
await this.createAndGoToProject(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isNativeFileMenuCreated = async () => {
|
||||||
|
await expect(this.appHeader).toHaveAttribute(
|
||||||
|
'data-native-file-menu',
|
||||||
|
'true'
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,7 @@ export class SceneFixture {
|
|||||||
public networkToggleConnected!: Locator
|
public networkToggleConnected!: Locator
|
||||||
public engineConnectionsSpinner!: Locator
|
public engineConnectionsSpinner!: Locator
|
||||||
public startEditSketchBtn!: Locator
|
public startEditSketchBtn!: Locator
|
||||||
|
public appHeader!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
@ -57,6 +58,7 @@ export class SceneFixture {
|
|||||||
this.startEditSketchBtn = page
|
this.startEditSketchBtn = page
|
||||||
.getByRole('button', { name: 'Start Sketch' })
|
.getByRole('button', { name: 'Start Sketch' })
|
||||||
.or(page.getByRole('button', { name: 'Edit Sketch' }))
|
.or(page.getByRole('button', { name: 'Edit Sketch' }))
|
||||||
|
this.appHeader = this.page.getByTestId('app-header')
|
||||||
}
|
}
|
||||||
private _serialiseScene = async (): Promise<SceneSerialised> => {
|
private _serialiseScene = async (): Promise<SceneSerialised> => {
|
||||||
const camera = await this.getCameraInfo()
|
const camera = await this.getCameraInfo()
|
||||||
@ -280,6 +282,13 @@ export class SceneFixture {
|
|||||||
await expect(buttonToTest).toBeVisible()
|
await expect(buttonToTest).toBeVisible()
|
||||||
await buttonToTest.click()
|
await buttonToTest.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isNativeFileMenuCreated = async () => {
|
||||||
|
await expect(this.appHeader).toHaveAttribute(
|
||||||
|
'data-native-file-menu',
|
||||||
|
'true'
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isColourArray(
|
function isColourArray(
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -545,7 +545,8 @@ extrude002 = extrude(profile002, length = 150)
|
|||||||
expect(alreadyExportingToastMessage).not.toBeVisible(),
|
expect(alreadyExportingToastMessage).not.toBeVisible(),
|
||||||
])
|
])
|
||||||
|
|
||||||
await expect(successToastMessage).toHaveCount(2)
|
const count = await successToastMessage.count()
|
||||||
|
await expect(count).toBeGreaterThanOrEqual(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -3496,6 +3496,73 @@ profile001 = startProfile(sketch001, at = [-102.72, 237.44])
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Ensure feature tree is not showing previous file's content when switching to a file with KCL errors.
|
||||||
|
test('Feature tree shows correct sketch count per file', async ({
|
||||||
|
context,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Setup project with files.
|
||||||
|
const GOOD_KCL = `sketch001 = startSketchOn(XZ)
|
||||||
|
profile001 = startProfile(sketch001, at = [220.81, 253.8])
|
||||||
|
|> line(end = [132.84, -151.31])
|
||||||
|
|> line(end = [25.51, 167.15])
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
sketch002 = startSketchOn(XZ)
|
||||||
|
profile002 = startProfile(sketch002, at = [158.35, -70.82])
|
||||||
|
|> line(end = [73.9, -152.19])
|
||||||
|
|> line(end = [85.33, 135.48])
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()`
|
||||||
|
|
||||||
|
const ERROR_KCL = `sketch001 = startSketchOn(XZ)
|
||||||
|
profile001 = startProfile(sketch001, at = [127.56, 179.02])
|
||||||
|
|> line(end = [132.84, -112.6])
|
||||||
|
|> line(end = [85.33, 234.01])
|
||||||
|
|> line(enfd = [-137.23, -54.55])`
|
||||||
|
|
||||||
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
const projectDir = path.join(dir, 'multi-file-sketch-test')
|
||||||
|
await fs.mkdir(projectDir, { recursive: true })
|
||||||
|
await Promise.all([
|
||||||
|
fs.writeFile(path.join(projectDir, 'good.kcl'), GOOD_KCL, 'utf-8'),
|
||||||
|
fs.writeFile(path.join(projectDir, 'error.kcl'), ERROR_KCL, 'utf-8'),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 800 })
|
||||||
|
|
||||||
|
await homePage.openProject('multi-file-sketch-test')
|
||||||
|
await scene.connectionEstablished()
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await toolbar.openFeatureTreePane()
|
||||||
|
await toolbar.openPane('files')
|
||||||
|
|
||||||
|
await toolbar.openFile('good.kcl')
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
|
||||||
|
).toHaveCount(2)
|
||||||
|
|
||||||
|
await toolbar.openFile('error.kcl')
|
||||||
|
|
||||||
|
// Ensure filetree is populated
|
||||||
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
|
||||||
|
).toHaveCount(0)
|
||||||
|
})
|
||||||
|
|
||||||
test('adding a syntax error, recovers after fixing', async ({
|
test('adding a syntax error, recovers after fixing', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
@ -21,6 +21,7 @@ export const token = process.env.token || ''
|
|||||||
|
|
||||||
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
|
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
|
||||||
|
|
||||||
|
import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup'
|
||||||
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
|
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
|
||||||
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
|
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
|
||||||
import { test } from '@e2e/playwright/zoo-test'
|
import { test } from '@e2e/playwright/zoo-test'
|
||||||
@ -1149,3 +1150,77 @@ export function perProjectSettingsToToml(
|
|||||||
// eslint-disable-next-line no-restricted-syntax
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
return TOML.stringify(settings as any)
|
return TOML.stringify(settings as any)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function clickElectronNativeMenuById(
|
||||||
|
tronApp: ElectronZoo,
|
||||||
|
menuId: string
|
||||||
|
) {
|
||||||
|
const clickWasTriggered = await tronApp.electron.evaluate(
|
||||||
|
async ({ app }, menuId) => {
|
||||||
|
if (!app || !app.applicationMenu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(menuId)
|
||||||
|
if (!menu) return false
|
||||||
|
menu.click()
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
menuId
|
||||||
|
)
|
||||||
|
expect(clickWasTriggered).toBe(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function findElectronNativeMenuById(
|
||||||
|
tronApp: ElectronZoo,
|
||||||
|
menuId: string
|
||||||
|
) {
|
||||||
|
const found = await tronApp.electron.evaluate(async ({ app }, menuId) => {
|
||||||
|
if (!app || !app.applicationMenu) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const menu = app.applicationMenu.getMenuItemById(menuId)
|
||||||
|
if (!menu) return false
|
||||||
|
return true
|
||||||
|
}, menuId)
|
||||||
|
expect(found).toBe(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function openSettingsExpectText(page: Page, text: string) {
|
||||||
|
const settings = page.getByTestId('settings-dialog-panel')
|
||||||
|
await expect(settings).toBeVisible()
|
||||||
|
// You are viewing the user tab
|
||||||
|
const actualText = settings.getByText(text)
|
||||||
|
await expect(actualText).toBeVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function openSettingsExpectLocator(page: Page, selector: string) {
|
||||||
|
const settings = page.getByTestId('settings-dialog-panel')
|
||||||
|
await expect(settings).toBeVisible()
|
||||||
|
// You are viewing the keybindings tab
|
||||||
|
const settingsLocator = settings.locator(selector)
|
||||||
|
await expect(settingsLocator).toBeVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A developer helper function to make playwright send all the console logs to stdout
|
||||||
|
* Call this within your E2E test and pass in the page or the tronApp to get as many
|
||||||
|
* logs piped to stdout for debugging
|
||||||
|
*/
|
||||||
|
export async function enableConsoleLogEverything({
|
||||||
|
page,
|
||||||
|
tronApp,
|
||||||
|
}: { page?: Page; tronApp?: ElectronZoo }) {
|
||||||
|
page?.on('console', (msg) => {
|
||||||
|
console.log(`[Page-log]: ${msg.text()}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
tronApp?.electron.on('window', async (electronPage) => {
|
||||||
|
electronPage.on('console', (msg) => {
|
||||||
|
console.log(`[Renderer] ${msg.type()}: ${msg.text()}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
tronApp?.electron.on('console', (msg) => {
|
||||||
|
console.log(`[Main] ${msg.type()}: ${msg.text()}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
18
rust/Cargo.lock
generated
18
rust/Cargo.lock
generated
@ -535,7 +535,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -963,7 +963,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1746,7 +1746,7 @@ checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2080,9 +2080,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad-modeling-cmds"
|
name = "kittycad-modeling-cmds"
|
||||||
version = "0.2.120"
|
version = "0.2.121"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "48b71e06ee5d711d0085864a756fb6a304531246689ea00c6ef5d740670c3701"
|
checksum = "94ba95c22493d79ec8a1faab963d8903f6de0e373efedf2bc3bb76a0ddbab036"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -2987,7 +2987,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tracing",
|
"tracing",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3306,7 +3306,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3900,7 +3900,7 @@ dependencies = [
|
|||||||
"getrandom 0.3.1",
|
"getrandom 0.3.1",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4753,7 +4753,7 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1249,7 +1249,7 @@ secondSketch = startSketchOn(part001, face = '')
|
|||||||
let err = err.as_kcl_error().unwrap();
|
let err = err.as_kcl_error().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
err.message(),
|
err.message(),
|
||||||
"The arg face was given, but it was the wrong type. It should be type FaceTag but it was string (text)"
|
"The arg face was given, but it was the wrong type. It should be type FaceTag but it was string"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1882,7 +1882,7 @@ someFunction('INVALID')
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.err().unwrap().to_string(),
|
result.err().unwrap().to_string(),
|
||||||
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([46, 55, 0]), SourceRange([60, 83, 0])], message: "This function expected the input argument to be Solid or Plane but it's actually of type string (text)" }"#
|
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([46, 55, 0]), SourceRange([60, 83, 0])], message: "This function expected the input argument to be Solid or Plane but it's actually of type string" }"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -913,11 +913,9 @@ impl Node<MemberExpression> {
|
|||||||
}),
|
}),
|
||||||
(being_indexed, _, _) => {
|
(being_indexed, _, _) => {
|
||||||
let t = being_indexed.human_friendly_type();
|
let t = being_indexed.human_friendly_type();
|
||||||
let article = article_for(t);
|
let article = article_for(&t);
|
||||||
Err(KclError::Semantic(KclErrorDetails {
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!(
|
message: format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
|
||||||
"Only arrays and objects can be indexed, but you're trying to index {article} {t}"
|
|
||||||
),
|
|
||||||
source_ranges: vec![self.clone().into()],
|
source_ranges: vec![self.clone().into()],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
@ -1698,8 +1696,9 @@ impl Node<ObjectExpression> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn article_for(s: &str) -> &'static str {
|
fn article_for<S: AsRef<str>>(s: S) -> &'static str {
|
||||||
if s.starts_with(['a', 'e', 'i', 'o', 'u']) {
|
// '[' is included since it's an array.
|
||||||
|
if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
|
||||||
"an"
|
"an"
|
||||||
} else {
|
} else {
|
||||||
"a"
|
"a"
|
||||||
@ -1709,10 +1708,9 @@ fn article_for(s: &str) -> &'static str {
|
|||||||
fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
|
fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
|
||||||
v.as_ty_f64().ok_or_else(|| {
|
v.as_ty_f64().ok_or_else(|| {
|
||||||
let actual_type = v.human_friendly_type();
|
let actual_type = v.human_friendly_type();
|
||||||
let article = article_for(actual_type);
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
KclError::Semantic(KclErrorDetails {
|
||||||
source_ranges: vec![source_range],
|
source_ranges: vec![source_range],
|
||||||
message: format!("Expected a number, but found {article} {actual_type}",),
|
message: format!("Expected a number, but found {actual_type}",),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -2446,19 +2444,23 @@ arr1 = [42]: [number(cm)]
|
|||||||
a = 42: string
|
a = 42: string
|
||||||
"#;
|
"#;
|
||||||
let result = parse_execute(program).await;
|
let result = parse_execute(program).await;
|
||||||
assert!(result
|
let err = result.unwrap_err();
|
||||||
.unwrap_err()
|
assert!(
|
||||||
.to_string()
|
err.to_string()
|
||||||
.contains("could not coerce number value to type string"));
|
.contains("could not coerce number(default units) value to type string"),
|
||||||
|
"Expected error but found {err:?}"
|
||||||
|
);
|
||||||
|
|
||||||
let program = r#"
|
let program = r#"
|
||||||
a = 42: Plane
|
a = 42: Plane
|
||||||
"#;
|
"#;
|
||||||
let result = parse_execute(program).await;
|
let result = parse_execute(program).await;
|
||||||
assert!(result
|
let err = result.unwrap_err();
|
||||||
.unwrap_err()
|
assert!(
|
||||||
.to_string()
|
err.to_string()
|
||||||
.contains("could not coerce number value to type Plane"));
|
.contains("could not coerce number(default units) value to type Plane"),
|
||||||
|
"Expected error but found {err:?}"
|
||||||
|
);
|
||||||
|
|
||||||
let program = r#"
|
let program = r#"
|
||||||
arr = [0]: [string]
|
arr = [0]: [string]
|
||||||
@ -2467,7 +2469,7 @@ arr = [0]: [string]
|
|||||||
let err = result.unwrap_err();
|
let err = result.unwrap_err();
|
||||||
assert!(
|
assert!(
|
||||||
err.to_string()
|
err.to_string()
|
||||||
.contains("could not coerce array (list) value to type [string]"),
|
.contains("could not coerce [any; 1] value to type [string]"),
|
||||||
"Expected error but found {err:?}"
|
"Expected error but found {err:?}"
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -2478,7 +2480,7 @@ mixedArr = [0, "a"]: [number(mm)]
|
|||||||
let err = result.unwrap_err();
|
let err = result.unwrap_err();
|
||||||
assert!(
|
assert!(
|
||||||
err.to_string()
|
err.to_string()
|
||||||
.contains("could not coerce array (list) value to type [number(mm)]"),
|
.contains("could not coerce [any; 2] value to type [number(mm)]"),
|
||||||
"Expected error but found {err:?}"
|
"Expected error but found {err:?}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -280,7 +280,10 @@ impl KclValue {
|
|||||||
|
|
||||||
/// Human readable type name used in error messages. Should not be relied
|
/// Human readable type name used in error messages. Should not be relied
|
||||||
/// on for program logic.
|
/// on for program logic.
|
||||||
pub(crate) fn human_friendly_type(&self) -> &'static str {
|
pub(crate) fn human_friendly_type(&self) -> String {
|
||||||
|
if let Some(t) = self.principal_type() {
|
||||||
|
return t.to_string();
|
||||||
|
}
|
||||||
match self {
|
match self {
|
||||||
KclValue::Uuid { .. } => "Unique ID (uuid)",
|
KclValue::Uuid { .. } => "Unique ID (uuid)",
|
||||||
KclValue::TagDeclarator(_) => "TagDeclarator",
|
KclValue::TagDeclarator(_) => "TagDeclarator",
|
||||||
@ -314,6 +317,7 @@ impl KclValue {
|
|||||||
KclValue::Type { .. } => "type",
|
KclValue::Type { .. } => "type",
|
||||||
KclValue::KclNone { .. } => "None",
|
KclValue::KclNone { .. } => "None",
|
||||||
}
|
}
|
||||||
|
.to_owned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
|
pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
|
||||||
|
@ -1910,13 +1910,13 @@ notNull = !myNull
|
|||||||
"#;
|
"#;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code1).await.unwrap_err().message(),
|
parse_execute(code1).await.unwrap_err().message(),
|
||||||
"Cannot apply unary operator ! to non-boolean value: number",
|
"Cannot apply unary operator ! to non-boolean value: number(default units)",
|
||||||
);
|
);
|
||||||
|
|
||||||
let code2 = "notZero = !0";
|
let code2 = "notZero = !0";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code2).await.unwrap_err().message(),
|
parse_execute(code2).await.unwrap_err().message(),
|
||||||
"Cannot apply unary operator ! to non-boolean value: number",
|
"Cannot apply unary operator ! to non-boolean value: number(default units)",
|
||||||
);
|
);
|
||||||
|
|
||||||
let code3 = r#"
|
let code3 = r#"
|
||||||
@ -1924,7 +1924,7 @@ notEmptyString = !""
|
|||||||
"#;
|
"#;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code3).await.unwrap_err().message(),
|
parse_execute(code3).await.unwrap_err().message(),
|
||||||
"Cannot apply unary operator ! to non-boolean value: string (text)",
|
"Cannot apply unary operator ! to non-boolean value: string",
|
||||||
);
|
);
|
||||||
|
|
||||||
let code4 = r#"
|
let code4 = r#"
|
||||||
@ -1933,7 +1933,7 @@ notMember = !obj.a
|
|||||||
"#;
|
"#;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code4).await.unwrap_err().message(),
|
parse_execute(code4).await.unwrap_err().message(),
|
||||||
"Cannot apply unary operator ! to non-boolean value: number",
|
"Cannot apply unary operator ! to non-boolean value: number(default units)",
|
||||||
);
|
);
|
||||||
|
|
||||||
let code5 = "
|
let code5 = "
|
||||||
@ -1941,7 +1941,7 @@ a = []
|
|||||||
notArray = !a";
|
notArray = !a";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code5).await.unwrap_err().message(),
|
parse_execute(code5).await.unwrap_err().message(),
|
||||||
"Cannot apply unary operator ! to non-boolean value: array (list)",
|
"Cannot apply unary operator ! to non-boolean value: [any; 0]",
|
||||||
);
|
);
|
||||||
|
|
||||||
let code6 = "
|
let code6 = "
|
||||||
@ -1949,7 +1949,7 @@ x = {}
|
|||||||
notObject = !x";
|
notObject = !x";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code6).await.unwrap_err().message(),
|
parse_execute(code6).await.unwrap_err().message(),
|
||||||
"Cannot apply unary operator ! to non-boolean value: object",
|
"Cannot apply unary operator ! to non-boolean value: { }",
|
||||||
);
|
);
|
||||||
|
|
||||||
let code7 = "
|
let code7 = "
|
||||||
@ -1975,7 +1975,7 @@ notTagDeclarator = !myTagDeclarator";
|
|||||||
assert!(
|
assert!(
|
||||||
tag_declarator_err
|
tag_declarator_err
|
||||||
.message()
|
.message()
|
||||||
.starts_with("Cannot apply unary operator ! to non-boolean value: TagDeclarator"),
|
.starts_with("Cannot apply unary operator ! to non-boolean value: tag"),
|
||||||
"Actual error: {:?}",
|
"Actual error: {:?}",
|
||||||
tag_declarator_err
|
tag_declarator_err
|
||||||
);
|
);
|
||||||
@ -1989,7 +1989,7 @@ notTagIdentifier = !myTag";
|
|||||||
assert!(
|
assert!(
|
||||||
tag_identifier_err
|
tag_identifier_err
|
||||||
.message()
|
.message()
|
||||||
.starts_with("Cannot apply unary operator ! to non-boolean value: TagIdentifier"),
|
.starts_with("Cannot apply unary operator ! to non-boolean value: tag"),
|
||||||
"Actual error: {:?}",
|
"Actual error: {:?}",
|
||||||
tag_identifier_err
|
tag_identifier_err
|
||||||
);
|
);
|
||||||
|
@ -257,14 +257,22 @@ impl Args {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
|
let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
|
||||||
let actual_type_name = arg.value.human_friendly_type();
|
let actual_type = arg.value.principal_type();
|
||||||
|
let actual_type_name = actual_type
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| t.to_string())
|
||||||
|
.unwrap_or_else(|| arg.value.human_friendly_type().to_owned());
|
||||||
let msg_base = format!(
|
let msg_base = format!(
|
||||||
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
|
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
|
||||||
ty.human_friendly_type(),
|
ty.human_friendly_type(),
|
||||||
);
|
);
|
||||||
let suggestion = match (ty, actual_type_name) {
|
let suggestion = match (ty, actual_type) {
|
||||||
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER),
|
(RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
|
||||||
(RuntimeType::Array(t, _), "Sketch") if **t == RuntimeType::Primitive(PrimitiveType::Solid) => {
|
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
|
||||||
|
}
|
||||||
|
(RuntimeType::Array(t, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
|
||||||
|
if **t == RuntimeType::Primitive(PrimitiveType::Solid) =>
|
||||||
|
{
|
||||||
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
|
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
@ -381,14 +389,22 @@ impl Args {
|
|||||||
}))?;
|
}))?;
|
||||||
|
|
||||||
let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
|
let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
|
||||||
let actual_type_name = arg.value.human_friendly_type();
|
let actual_type = arg.value.principal_type();
|
||||||
|
let actual_type_name = actual_type
|
||||||
|
.as_ref()
|
||||||
|
.map(|t| t.to_string())
|
||||||
|
.unwrap_or_else(|| arg.value.human_friendly_type().to_owned());
|
||||||
let msg_base = format!(
|
let msg_base = format!(
|
||||||
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
|
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
|
||||||
ty.human_friendly_type(),
|
ty.human_friendly_type(),
|
||||||
);
|
);
|
||||||
let suggestion = match (ty, actual_type_name) {
|
let suggestion = match (ty, actual_type) {
|
||||||
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER),
|
(RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
|
||||||
(RuntimeType::Array(ty, _), "Sketch") if **ty == RuntimeType::Primitive(PrimitiveType::Solid) => {
|
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
|
||||||
|
}
|
||||||
|
(RuntimeType::Array(ty, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
|
||||||
|
if **ty == RuntimeType::Primitive(PrimitiveType::Solid) =>
|
||||||
|
{
|
||||||
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
|
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use kcl_derive_docs::stdlib;
|
use kcl_derive_docs::stdlib;
|
||||||
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
|
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
|
||||||
use kittycad_modeling_cmds::{self as kcmc};
|
use kittycad_modeling_cmds::{self as kcmc, shared::RelativeTo};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@ -37,11 +37,20 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
)?;
|
)?;
|
||||||
let sectional = args.get_kw_arg_opt("sectional")?;
|
let sectional = args.get_kw_arg_opt("sectional")?;
|
||||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
||||||
|
let relative_to: Option<String> = args.get_kw_arg_opt_typed("relativeTo", &RuntimeType::string(), exec_state)?;
|
||||||
let tag_start = args.get_kw_arg_opt("tagStart")?;
|
let tag_start = args.get_kw_arg_opt("tagStart")?;
|
||||||
let tag_end = args.get_kw_arg_opt("tagEnd")?;
|
let tag_end = args.get_kw_arg_opt("tagEnd")?;
|
||||||
|
|
||||||
let value = inner_sweep(
|
let value = inner_sweep(
|
||||||
sketches, path, sectional, tolerance, tag_start, tag_end, exec_state, args,
|
sketches,
|
||||||
|
path,
|
||||||
|
sectional,
|
||||||
|
tolerance,
|
||||||
|
relative_to,
|
||||||
|
tag_start,
|
||||||
|
tag_end,
|
||||||
|
exec_state,
|
||||||
|
args,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(value.into())
|
Ok(value.into())
|
||||||
@ -158,6 +167,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
path = { docs = "The path to sweep the sketch along" },
|
path = { docs = "The path to sweep the sketch along" },
|
||||||
sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." },
|
sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." },
|
||||||
tolerance = { docs = "Tolerance for this operation" },
|
tolerance = { docs = "Tolerance for this operation" },
|
||||||
|
relative_to = { docs = "What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. Defaults to trajectoryCurve."},
|
||||||
tag_start = { docs = "A named tag for the face at the start of the sweep, i.e. the original sketch" },
|
tag_start = { docs = "A named tag for the face at the start of the sweep, i.e. the original sketch" },
|
||||||
tag_end = { docs = "A named tag for the face at the end of the sweep" },
|
tag_end = { docs = "A named tag for the face at the end of the sweep" },
|
||||||
},
|
},
|
||||||
@ -169,6 +179,7 @@ async fn inner_sweep(
|
|||||||
path: SweepPath,
|
path: SweepPath,
|
||||||
sectional: Option<bool>,
|
sectional: Option<bool>,
|
||||||
tolerance: Option<TyF64>,
|
tolerance: Option<TyF64>,
|
||||||
|
relative_to: Option<String>,
|
||||||
tag_start: Option<TagNode>,
|
tag_start: Option<TagNode>,
|
||||||
tag_end: Option<TagNode>,
|
tag_end: Option<TagNode>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
@ -178,6 +189,16 @@ async fn inner_sweep(
|
|||||||
SweepPath::Sketch(sketch) => sketch.id.into(),
|
SweepPath::Sketch(sketch) => sketch.id.into(),
|
||||||
SweepPath::Helix(helix) => helix.value.into(),
|
SweepPath::Helix(helix) => helix.value.into(),
|
||||||
};
|
};
|
||||||
|
let relative_to = match relative_to.as_deref() {
|
||||||
|
Some("sketchPlane") => RelativeTo::SketchPlane,
|
||||||
|
Some("trajectoryCurve") | None => RelativeTo::TrajectoryCurve,
|
||||||
|
Some(_) => {
|
||||||
|
return Err(KclError::Syntax(crate::errors::KclErrorDetails {
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
message: "If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let mut solids = Vec::new();
|
let mut solids = Vec::new();
|
||||||
for sketch in &sketches {
|
for sketch in &sketches {
|
||||||
@ -189,6 +210,7 @@ async fn inner_sweep(
|
|||||||
trajectory,
|
trajectory,
|
||||||
sectional: sectional.unwrap_or(false),
|
sectional: sectional.unwrap_or(false),
|
||||||
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
||||||
|
relative_to,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -4,8 +4,7 @@ description: Error from executing argument_error.kcl
|
|||||||
---
|
---
|
||||||
KCL Semantic error
|
KCL Semantic error
|
||||||
|
|
||||||
× semantic: f requires a value with type `fn(any): any`, but found array
|
× semantic: f requires a value with type `fn(any): any`, but found [any; 2]
|
||||||
│ (list)
|
|
||||||
╭─[5:1]
|
╭─[5:1]
|
||||||
4 │
|
4 │
|
||||||
5 │ map(f, f = [0, 1])
|
5 │ map(f, f = [0, 1])
|
||||||
@ -16,7 +15,7 @@ KCL Semantic error
|
|||||||
╰─▶ KCL Semantic error
|
╰─▶ KCL Semantic error
|
||||||
|
|
||||||
× semantic: f requires a value with type `fn(any): any`, but found
|
× semantic: f requires a value with type `fn(any): any`, but found
|
||||||
│ array (list)
|
│ [any; 2]
|
||||||
╭─[5:12]
|
╭─[5:12]
|
||||||
4 │
|
4 │
|
||||||
5 │ map(f, f = [0, 1])
|
5 │ map(f, f = [0, 1])
|
||||||
|
@ -5,7 +5,7 @@ description: Error from executing array_elem_pop_empty_fail.kcl
|
|||||||
KCL Semantic error
|
KCL Semantic error
|
||||||
|
|
||||||
× semantic: The input argument of `std::array::pop` requires a value with
|
× semantic: The input argument of `std::array::pop` requires a value with
|
||||||
│ type `[any; 1+]`, but found array (list)
|
│ type `[any; 1+]`, but found [any; 0]
|
||||||
╭─[2:8]
|
╭─[2:8]
|
||||||
1 │ arr = []
|
1 │ arr = []
|
||||||
2 │ fail = pop(arr)
|
2 │ fail = pop(arr)
|
||||||
@ -16,7 +16,7 @@ KCL Semantic error
|
|||||||
╰─▶ KCL Semantic error
|
╰─▶ KCL Semantic error
|
||||||
|
|
||||||
× semantic: The input argument of `std::array::pop` requires a value
|
× semantic: The input argument of `std::array::pop` requires a value
|
||||||
│ with type `[any; 1+]`, but found array (list)
|
│ with type `[any; 1+]`, but found [any; 0]
|
||||||
╭─[2:12]
|
╭─[2:12]
|
||||||
1 │ arr = []
|
1 │ arr = []
|
||||||
2 │ fail = pop(arr)
|
2 │ fail = pop(arr)
|
||||||
|
@ -4,7 +4,7 @@ description: Error from executing comparisons_multiple.kcl
|
|||||||
---
|
---
|
||||||
KCL Semantic error
|
KCL Semantic error
|
||||||
|
|
||||||
× semantic: Expected a number, but found a boolean (true/false value)
|
× semantic: Expected a number, but found bool
|
||||||
╭────
|
╭────
|
||||||
1 │ assert(3 == 3 == 3, error = "this should not compile")
|
1 │ assert(3 == 3 == 3, error = "this should not compile")
|
||||||
· ───┬──
|
· ───┬──
|
||||||
|
@ -5,7 +5,7 @@ description: Error from executing error_inside_fn_also_has_source_range_of_call_
|
|||||||
KCL Semantic error
|
KCL Semantic error
|
||||||
|
|
||||||
× semantic: This function expected the input argument to be Solid or Plane
|
× semantic: This function expected the input argument to be Solid or Plane
|
||||||
│ but it's actually of type string (text)
|
│ but it's actually of type string
|
||||||
╭─[3:23]
|
╭─[3:23]
|
||||||
2 │ fn someNestedFunction(@something2) {
|
2 │ fn someNestedFunction(@something2) {
|
||||||
3 │ startSketchOn(something2)
|
3 │ startSketchOn(something2)
|
||||||
@ -25,7 +25,7 @@ KCL Semantic error
|
|||||||
├─▶ KCL Semantic error
|
├─▶ KCL Semantic error
|
||||||
│
|
│
|
||||||
│ × semantic: This function expected the input argument to be Solid or
|
│ × semantic: This function expected the input argument to be Solid or
|
||||||
│ │ Plane but it's actually of type string (text)
|
│ │ Plane but it's actually of type string
|
||||||
│ ╭─[3:23]
|
│ ╭─[3:23]
|
||||||
│ 2 │ fn someNestedFunction(@something2) {
|
│ 2 │ fn someNestedFunction(@something2) {
|
||||||
│ 3 │ startSketchOn(something2)
|
│ 3 │ startSketchOn(something2)
|
||||||
@ -37,7 +37,7 @@ KCL Semantic error
|
|||||||
╰─▶ KCL Semantic error
|
╰─▶ KCL Semantic error
|
||||||
|
|
||||||
× semantic: This function expected the input argument to be Solid or
|
× semantic: This function expected the input argument to be Solid or
|
||||||
│ Plane but it's actually of type string (text)
|
│ Plane but it's actually of type string
|
||||||
╭─[6:5]
|
╭─[6:5]
|
||||||
5 │
|
5 │
|
||||||
6 │ someNestedFunction(something)
|
6 │ someNestedFunction(something)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
source: kcl/src/simulation_tests.rs
|
source: kcl-lib/src/simulation_tests.rs
|
||||||
description: Error from executing invalid_member_object.kcl
|
description: Error from executing invalid_member_object.kcl
|
||||||
---
|
---
|
||||||
KCL Semantic error
|
KCL Semantic error
|
||||||
|
|
||||||
× semantic: Only arrays and objects can be indexed, but you're trying to
|
× semantic: Only arrays can be indexed, but you're trying to index a
|
||||||
│ index a number
|
│ number(default units)
|
||||||
╭─[2:5]
|
╭─[2:5]
|
||||||
1 │ num = 999
|
1 │ num = 999
|
||||||
2 │ x = num[3]
|
2 │ x = num[3]
|
||||||
|
@ -5121,7 +5121,8 @@ description: Artifact commands bench.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -5132,7 +5133,8 @@ description: Artifact commands bench.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -905,7 +905,8 @@ description: Artifact commands cold-plate.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -162,7 +162,7 @@ description: Variables in memory after executing countersunk-plate.kcl
|
|||||||
],
|
],
|
||||||
"tag": null,
|
"tag": null,
|
||||||
"to": [
|
"to": [
|
||||||
2.7737,
|
2.7738,
|
||||||
-0.6983
|
-0.6983
|
||||||
],
|
],
|
||||||
"type": "TangentialArc",
|
"type": "TangentialArc",
|
||||||
@ -176,7 +176,7 @@ description: Variables in memory after executing countersunk-plate.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
},
|
},
|
||||||
"from": [
|
"from": [
|
||||||
2.7737,
|
2.7738,
|
||||||
-0.6983
|
-0.6983
|
||||||
],
|
],
|
||||||
"tag": null,
|
"tag": null,
|
||||||
@ -196,7 +196,7 @@ description: Variables in memory after executing countersunk-plate.kcl
|
|||||||
},
|
},
|
||||||
"ccw": false,
|
"ccw": false,
|
||||||
"center": [
|
"center": [
|
||||||
-0.0,
|
0.0,
|
||||||
-0.0
|
-0.0
|
||||||
],
|
],
|
||||||
"from": [
|
"from": [
|
||||||
|
@ -5575,7 +5575,8 @@ description: Artifact commands cpu-cooler.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -6109,7 +6110,8 @@ description: Artifact commands cpu-cooler.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -9466,7 +9468,8 @@ description: Artifact commands cpu-cooler.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -9597,7 +9600,8 @@ description: Artifact commands cpu-cooler.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -10115,7 +10119,8 @@ description: Artifact commands cpu-cooler.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -10246,7 +10251,8 @@ description: Artifact commands cpu-cooler.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -1597,7 +1597,8 @@ description: Artifact commands exhaust-manifold.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1608,7 +1609,8 @@ description: Artifact commands exhaust-manifold.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1619,7 +1621,8 @@ description: Artifact commands exhaust-manifold.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1630,7 +1633,8 @@ description: Artifact commands exhaust-manifold.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -4490,7 +4490,8 @@ description: Artifact commands utility-sink.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -5,7 +5,7 @@ description: Error from executing panic_repro_cube.kcl
|
|||||||
KCL Semantic error
|
KCL Semantic error
|
||||||
|
|
||||||
× semantic: This function expected the input argument to be tag identifier
|
× semantic: This function expected the input argument to be tag identifier
|
||||||
│ but it's actually of type Unique ID (uuid)
|
│ but it's actually of type tag
|
||||||
╭─[43:25]
|
╭─[43:25]
|
||||||
42 │ // these double wrapped functions are the point of this test
|
42 │ // these double wrapped functions are the point of this test
|
||||||
43 │ getNextAdjacentEdge(getNextAdjacentEdge(seg01)),
|
43 │ getNextAdjacentEdge(getNextAdjacentEdge(seg01)),
|
||||||
|
@ -417,7 +417,8 @@ description: Artifact commands subtract_regression03.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -394,7 +394,8 @@ description: Artifact commands subtract_regression05.kcl
|
|||||||
"target": "[uuid]",
|
"target": "[uuid]",
|
||||||
"trajectory": "[uuid]",
|
"trajectory": "[uuid]",
|
||||||
"sectional": false,
|
"sectional": false,
|
||||||
"tolerance": 0.0000001
|
"tolerance": 0.0000001,
|
||||||
|
"relative_to": "trajectory_curve"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
17
src/App.tsx
17
src/App.tsx
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import ModalContainer from 'react-modal-promise'
|
import ModalContainer from 'react-modal-promise'
|
||||||
@ -42,6 +42,7 @@ import {
|
|||||||
ONBOARDING_TOAST_ID,
|
ONBOARDING_TOAST_ID,
|
||||||
TutorialRequestToast,
|
TutorialRequestToast,
|
||||||
} from '@src/routes/Onboarding/utils'
|
} from '@src/routes/Onboarding/utils'
|
||||||
|
import { reportRejection } from '@src/lib/trap'
|
||||||
|
|
||||||
// CYCLIC REF
|
// CYCLIC REF
|
||||||
sceneInfra.camControls.engineStreamActor = engineStreamActor
|
sceneInfra.camControls.engineStreamActor = engineStreamActor
|
||||||
@ -52,6 +53,7 @@ maybeWriteToDisk()
|
|||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const { project, file } = useLoaderData() as IndexLoaderData
|
const { project, file } = useLoaderData() as IndexLoaderData
|
||||||
|
const [nativeFileMenuCreated, setNativeFileMenuCreated] = useState(false)
|
||||||
|
|
||||||
// Keep a lookout for a URL query string that invokes the 'import file from URL' command
|
// Keep a lookout for a URL query string that invokes the 'import file from URL' command
|
||||||
useCreateFileLinkQuery((argDefaultValues) => {
|
useCreateFileLinkQuery((argDefaultValues) => {
|
||||||
@ -145,12 +147,25 @@ export function App() {
|
|||||||
}
|
}
|
||||||
}, [location, settings.app.onboardingStatus, navigate])
|
}, [location, settings.app.onboardingStatus, navigate])
|
||||||
|
|
||||||
|
// Only create the native file menus on desktop
|
||||||
|
useEffect(() => {
|
||||||
|
if (isDesktop()) {
|
||||||
|
window.electron
|
||||||
|
.createModelingPageMenu()
|
||||||
|
.then(() => {
|
||||||
|
setNativeFileMenuCreated(true)
|
||||||
|
})
|
||||||
|
.catch(reportRejection)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full flex flex-col" ref={ref}>
|
<div className="relative h-full flex flex-col" ref={ref}>
|
||||||
<AppHeader
|
<AppHeader
|
||||||
className="transition-opacity transition-duration-75"
|
className="transition-opacity transition-duration-75"
|
||||||
project={{ project, file }}
|
project={{ project, file }}
|
||||||
enableMenu={true}
|
enableMenu={true}
|
||||||
|
nativeFileMenuCreated={nativeFileMenuCreated}
|
||||||
>
|
>
|
||||||
<CommandBarOpenButton />
|
<CommandBarOpenButton />
|
||||||
<ShareButton />
|
<ShareButton />
|
||||||
|
@ -14,6 +14,7 @@ interface AppHeaderProps extends React.PropsWithChildren {
|
|||||||
className?: string
|
className?: string
|
||||||
enableMenu?: boolean
|
enableMenu?: boolean
|
||||||
style?: React.CSSProperties
|
style?: React.CSSProperties
|
||||||
|
nativeFileMenuCreated: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AppHeader = ({
|
export const AppHeader = ({
|
||||||
@ -23,12 +24,14 @@ export const AppHeader = ({
|
|||||||
className = '',
|
className = '',
|
||||||
style,
|
style,
|
||||||
enableMenu = false,
|
enableMenu = false,
|
||||||
|
nativeFileMenuCreated,
|
||||||
}: AppHeaderProps) => {
|
}: AppHeaderProps) => {
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
id="app-header"
|
id="app-header"
|
||||||
|
data-testid="app-header"
|
||||||
className={
|
className={
|
||||||
'w-full grid ' +
|
'w-full grid ' +
|
||||||
styles.header +
|
styles.header +
|
||||||
@ -37,6 +40,7 @@ export const AppHeader = ({
|
|||||||
}overlaid-panes sticky top-0 z-20 px-2 items-start ` +
|
}overlaid-panes sticky top-0 z-20 px-2 items-start ` +
|
||||||
className
|
className
|
||||||
}
|
}
|
||||||
|
data-native-file-menu={nativeFileMenuCreated}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
<ProjectSidebarMenu
|
<ProjectSidebarMenu
|
||||||
|
@ -169,6 +169,7 @@ export const CommandBar = () => {
|
|||||||
)}
|
)}
|
||||||
<div className="flex flex-col gap-2 !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent">
|
<div className="flex flex-col gap-2 !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent">
|
||||||
<button
|
<button
|
||||||
|
data-testid="command-bar-close-button"
|
||||||
onClick={() => commandBarActor.send({ type: 'Close' })}
|
onClick={() => commandBarActor.send({ type: 'Close' })}
|
||||||
className="group m-0 p-0 border-none bg-transparent hover:bg-transparent"
|
className="group m-0 p-0 border-none bg-transparent hover:bg-transparent"
|
||||||
>
|
>
|
||||||
|
@ -29,7 +29,7 @@ import { kclCommands } from '@src/lib/kclCommands'
|
|||||||
import { BROWSER_PATH, PATHS } from '@src/lib/paths'
|
import { BROWSER_PATH, PATHS } from '@src/lib/paths'
|
||||||
import { markOnce } from '@src/lib/performance'
|
import { markOnce } from '@src/lib/performance'
|
||||||
import { codeManager, kclManager } from '@src/lib/singletons'
|
import { codeManager, kclManager } from '@src/lib/singletons'
|
||||||
import { err, reportRejection } from '@src/lib/trap'
|
import { err } from '@src/lib/trap'
|
||||||
import { type IndexLoaderData } from '@src/lib/types'
|
import { type IndexLoaderData } from '@src/lib/types'
|
||||||
import { useSettings, useToken } from '@src/lib/singletons'
|
import { useSettings, useToken } from '@src/lib/singletons'
|
||||||
import { commandBarActor } from '@src/lib/singletons'
|
import { commandBarActor } from '@src/lib/singletons'
|
||||||
@ -59,12 +59,6 @@ export const FileMachineProvider = ({
|
|||||||
const { project, file } = projectData
|
const { project, file } = projectData
|
||||||
|
|
||||||
const filePath = useAbsoluteFilePath()
|
const filePath = useAbsoluteFilePath()
|
||||||
// Only create the native file menus on desktop
|
|
||||||
useEffect(() => {
|
|
||||||
if (isDesktop()) {
|
|
||||||
window.electron.createModelingPageMenu().catch(reportRejection)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const {
|
const {
|
||||||
|
@ -147,6 +147,14 @@ export class KclManager {
|
|||||||
|
|
||||||
set switchedFiles(switchedFiles: boolean) {
|
set switchedFiles(switchedFiles: boolean) {
|
||||||
this._switchedFiles = switchedFiles
|
this._switchedFiles = switchedFiles
|
||||||
|
|
||||||
|
// These belonged to the previous file
|
||||||
|
this.lastSuccessfulOperations = []
|
||||||
|
this.lastSuccessfulVariables = {}
|
||||||
|
|
||||||
|
// Without this, when leaving a project which has errors and opening another project which doesn't,
|
||||||
|
// you'd see the errors from the previous project for a short time until the new code is executed.
|
||||||
|
this._errors = []
|
||||||
}
|
}
|
||||||
|
|
||||||
get variables() {
|
get variables() {
|
||||||
|
@ -31,7 +31,7 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
|
|||||||
)
|
)
|
||||||
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS + ' [TEST]', {
|
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS + ' [TEST]', {
|
||||||
id: toastId,
|
id: toastId,
|
||||||
duration: 5_000,
|
duration: 10_000,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -139,18 +139,20 @@ export const settingsMachine = setup({
|
|||||||
return () => darkModeMatcher?.removeEventListener('change', listener)
|
return () => darkModeMatcher?.removeEventListener('change', listener)
|
||||||
}),
|
}),
|
||||||
registerCommands: fromCallback<
|
registerCommands: fromCallback<
|
||||||
{ type: 'update' },
|
{ type: 'update'; settings: SettingsType },
|
||||||
{ settings: SettingsType; actor: AnyActorRef }
|
{ settings: SettingsType; actor: AnyActorRef }
|
||||||
>(({ input, receive, system }) => {
|
>(({ input, receive, system }) => {
|
||||||
// This assumes this actor is running in a system with a command palette
|
// This assumes this actor is running in a system with a command palette
|
||||||
const commandBarActor = system.get(ACTOR_IDS.COMMAND_BAR)
|
const commandBarActor = system.get(ACTOR_IDS.COMMAND_BAR)
|
||||||
// If the user wants to hide the settings commands
|
// If the user wants to hide the settings commands
|
||||||
//from the command bar don't add them.
|
//from the command bar don't add them.
|
||||||
if (settings.commandBar.includeSettings.current === false) return
|
if (settings.commandBar.includeSettings.current === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
let commands: Command[] = []
|
let commands: Command[] = []
|
||||||
|
|
||||||
const updateCommands = () =>
|
const updateCommands = (newSettings: SettingsType) =>
|
||||||
settingsWithCommandConfigs(input.settings)
|
settingsWithCommandConfigs(newSettings)
|
||||||
.map((type) =>
|
.map((type) =>
|
||||||
createSettingsCommand({
|
createSettingsCommand({
|
||||||
type,
|
type,
|
||||||
@ -175,14 +177,19 @@ export const settingsMachine = setup({
|
|||||||
data: { commands: commands },
|
data: { commands: commands },
|
||||||
})
|
})
|
||||||
|
|
||||||
receive((event) => {
|
receive(({ type, settings: newSettings }) => {
|
||||||
if (event.type !== 'update') return
|
if (type !== 'update') {
|
||||||
|
return
|
||||||
|
}
|
||||||
removeCommands()
|
removeCommands()
|
||||||
commands = updateCommands()
|
commands =
|
||||||
|
newSettings.commandBar.includeSettings.current === false
|
||||||
|
? []
|
||||||
|
: updateCommands(newSettings)
|
||||||
addCommands()
|
addCommands()
|
||||||
})
|
})
|
||||||
|
|
||||||
commands = updateCommands()
|
commands = updateCommands(settings)
|
||||||
addCommands()
|
addCommands()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -205,7 +212,9 @@ export const settingsMachine = setup({
|
|||||||
const sceneInfra = rootContext.sceneInfra
|
const sceneInfra = rootContext.sceneInfra
|
||||||
const sceneEntitiesManager = rootContext.sceneEntitiesManager
|
const sceneEntitiesManager = rootContext.sceneEntitiesManager
|
||||||
|
|
||||||
if (!sceneInfra || !sceneEntitiesManager) return
|
if (!sceneInfra || !sceneEntitiesManager) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const opposingTheme = getOppositeTheme(context.app.theme.current)
|
const opposingTheme = getOppositeTheme(context.app.theme.current)
|
||||||
sceneInfra.theme = opposingTheme
|
sceneInfra.theme = opposingTheme
|
||||||
sceneEntitiesManager.updateSegmentBaseColor(opposingTheme)
|
sceneEntitiesManager.updateSegmentBaseColor(opposingTheme)
|
||||||
@ -213,13 +222,17 @@ export const settingsMachine = setup({
|
|||||||
setAllowOrbitInSketchMode: ({ context, self }) => {
|
setAllowOrbitInSketchMode: ({ context, self }) => {
|
||||||
const rootContext = self.system.get('root').getSnapshot().context
|
const rootContext = self.system.get('root').getSnapshot().context
|
||||||
const sceneInfra = rootContext.sceneInfra
|
const sceneInfra = rootContext.sceneInfra
|
||||||
if (!sceneInfra.camControls) return
|
if (!sceneInfra.camControls) {
|
||||||
|
return
|
||||||
|
}
|
||||||
sceneInfra.camControls._setting_allowOrbitInSketchMode =
|
sceneInfra.camControls._setting_allowOrbitInSketchMode =
|
||||||
context.app.allowOrbitInSketchMode.current
|
context.app.allowOrbitInSketchMode.current
|
||||||
// ModelingMachineProvider will do a use effect to trigger the camera engine sync
|
// ModelingMachineProvider will do a use effect to trigger the camera engine sync
|
||||||
},
|
},
|
||||||
toastSuccess: ({ event }) => {
|
toastSuccess: ({ event }) => {
|
||||||
if (!('data' in event)) return
|
if (!('data' in event)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const eventParts = event.type.replace(/^set./, '').split('.') as [
|
const eventParts = event.type.replace(/^set./, '').split('.') as [
|
||||||
keyof typeof settings,
|
keyof typeof settings,
|
||||||
string,
|
string,
|
||||||
@ -435,6 +448,22 @@ export const settingsMachine = setup({
|
|||||||
actions: ['setSettingAtLevel', 'setThemeColor'],
|
actions: ['setSettingAtLevel', 'setThemeColor'],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'set.commandBar.includeSettings': {
|
||||||
|
target: 'persisting settings',
|
||||||
|
|
||||||
|
actions: [
|
||||||
|
'setSettingAtLevel',
|
||||||
|
'toastSuccess',
|
||||||
|
sendTo(
|
||||||
|
'registerCommands',
|
||||||
|
({ context: { currentProject: _, ...settings } }) => ({
|
||||||
|
type: 'update',
|
||||||
|
settings,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
'set.modeling.defaultUnit': {
|
'set.modeling.defaultUnit': {
|
||||||
target: 'persisting settings',
|
target: 'persisting settings',
|
||||||
|
|
||||||
@ -497,6 +526,13 @@ export const settingsMachine = setup({
|
|||||||
'setClientTheme',
|
'setClientTheme',
|
||||||
'setAllowOrbitInSketchMode',
|
'setAllowOrbitInSketchMode',
|
||||||
'sendThemeToWatcher',
|
'sendThemeToWatcher',
|
||||||
|
sendTo(
|
||||||
|
'registerCommands',
|
||||||
|
({ context: { currentProject: _, ...settings } }) => ({
|
||||||
|
type: 'update',
|
||||||
|
settings,
|
||||||
|
})
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -510,6 +546,13 @@ export const settingsMachine = setup({
|
|||||||
'setClientTheme',
|
'setClientTheme',
|
||||||
'setAllowOrbitInSketchMode',
|
'setAllowOrbitInSketchMode',
|
||||||
'sendThemeToWatcher',
|
'sendThemeToWatcher',
|
||||||
|
sendTo(
|
||||||
|
'registerCommands',
|
||||||
|
({ context: { currentProject: _, ...settings } }) => ({
|
||||||
|
type: 'update',
|
||||||
|
settings,
|
||||||
|
})
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -529,7 +572,13 @@ export const settingsMachine = setup({
|
|||||||
'clearProjectSettings',
|
'clearProjectSettings',
|
||||||
'clearCurrentProject',
|
'clearCurrentProject',
|
||||||
'setThemeColor',
|
'setThemeColor',
|
||||||
sendTo('registerCommands', { type: 'update' }),
|
sendTo(
|
||||||
|
'registerCommands',
|
||||||
|
({ context: { currentProject: _, ...settings } }) => ({
|
||||||
|
type: 'update',
|
||||||
|
settings,
|
||||||
|
})
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -582,6 +631,13 @@ export const settingsMachine = setup({
|
|||||||
'setClientTheme',
|
'setClientTheme',
|
||||||
'setAllowOrbitInSketchMode',
|
'setAllowOrbitInSketchMode',
|
||||||
'sendThemeToWatcher',
|
'sendThemeToWatcher',
|
||||||
|
sendTo(
|
||||||
|
'registerCommands',
|
||||||
|
({ context: { currentProject: _, ...settings } }) => ({
|
||||||
|
type: 'update',
|
||||||
|
settings,
|
||||||
|
})
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
onError: {
|
onError: {
|
||||||
@ -612,7 +668,13 @@ export const settingsMachine = setup({
|
|||||||
'setClientTheme',
|
'setClientTheme',
|
||||||
'setAllowOrbitInSketchMode',
|
'setAllowOrbitInSketchMode',
|
||||||
'sendThemeToWatcher',
|
'sendThemeToWatcher',
|
||||||
sendTo('registerCommands', { type: 'update' }),
|
sendTo(
|
||||||
|
'registerCommands',
|
||||||
|
({ context: { currentProject: _, ...settings } }) => ({
|
||||||
|
type: 'update',
|
||||||
|
settings,
|
||||||
|
})
|
||||||
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
onError: 'idle',
|
onError: 'idle',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { FormEvent, HTMLProps } from 'react'
|
import type { FormEvent, HTMLProps } from 'react'
|
||||||
import { useEffect } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import {
|
import {
|
||||||
@ -70,13 +70,20 @@ type ReadWriteProjectState = {
|
|||||||
// This route only opens in the desktop context for now,
|
// This route only opens in the desktop context for now,
|
||||||
// as defined in Router.tsx, so we can use the desktop APIs and types.
|
// as defined in Router.tsx, so we can use the desktop APIs and types.
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
|
const navigate = useNavigate()
|
||||||
const readWriteProjectDir = useCanReadWriteProjectDirectory()
|
const readWriteProjectDir = useCanReadWriteProjectDirectory()
|
||||||
|
const [nativeFileMenuCreated, setNativeFileMenuCreated] = useState(false)
|
||||||
const apiToken = useToken()
|
const apiToken = useToken()
|
||||||
|
|
||||||
// Only create the native file menus on desktop
|
// Only create the native file menus on desktop
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isDesktop()) {
|
if (isDesktop()) {
|
||||||
window.electron.createHomePageMenu().catch(reportRejection)
|
window.electron
|
||||||
|
.createHomePageMenu()
|
||||||
|
.then(() => {
|
||||||
|
setNativeFileMenuCreated(true)
|
||||||
|
})
|
||||||
|
.catch(reportRejection)
|
||||||
}
|
}
|
||||||
billingActor.send({ type: BillingTransition.Update, apiToken })
|
billingActor.send({ type: BillingTransition.Update, apiToken })
|
||||||
}, [])
|
}, [])
|
||||||
@ -94,7 +101,6 @@ const Home = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
|
||||||
const settings = useSettings()
|
const settings = useSettings()
|
||||||
const onboardingStatus = settings.app.onboardingStatus.current
|
const onboardingStatus = settings.app.onboardingStatus.current
|
||||||
|
|
||||||
@ -217,7 +223,10 @@ const Home = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col items-stretch h-screen w-screen overflow-hidden">
|
<div className="relative flex flex-col items-stretch h-screen w-screen overflow-hidden">
|
||||||
<AppHeader showToolbar={false} />
|
<AppHeader
|
||||||
|
nativeFileMenuCreated={nativeFileMenuCreated}
|
||||||
|
showToolbar={false}
|
||||||
|
/>
|
||||||
<div className="overflow-hidden self-stretch w-full flex-1 home-layout max-w-4xl lg:max-w-5xl xl:max-w-7xl mb-12 px-4 mx-auto mt-8 lg:mt-24 lg:px-0">
|
<div className="overflow-hidden self-stretch w-full flex-1 home-layout max-w-4xl lg:max-w-5xl xl:max-w-7xl mb-12 px-4 mx-auto mt-8 lg:mt-24 lg:px-0">
|
||||||
<HomeHeader
|
<HomeHeader
|
||||||
setQuery={setQuery}
|
setQuery={setQuery}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Dialog, Transition } from '@headlessui/react'
|
import { Dialog, Transition } from '@headlessui/react'
|
||||||
import { Fragment, useEffect, useRef } from 'react'
|
import { Fragment, useEffect, useRef } from 'react'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
|
||||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
|
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
|
|
||||||
import { CustomIcon } from '@src/components/CustomIcon'
|
import { CustomIcon } from '@src/components/CustomIcon'
|
||||||
@ -10,14 +9,19 @@ import { KeybindingsSectionsList } from '@src/components/Settings/KeybindingsSec
|
|||||||
import { SettingsSearchBar } from '@src/components/Settings/SettingsSearchBar'
|
import { SettingsSearchBar } from '@src/components/Settings/SettingsSearchBar'
|
||||||
import { SettingsSectionsList } from '@src/components/Settings/SettingsSectionsList'
|
import { SettingsSectionsList } from '@src/components/Settings/SettingsSectionsList'
|
||||||
import { SettingsTabs } from '@src/components/Settings/SettingsTabs'
|
import { SettingsTabs } from '@src/components/Settings/SettingsTabs'
|
||||||
import { useDotDotSlash } from '@src/hooks/useDotDotSlash'
|
|
||||||
import { PATHS } from '@src/lib/paths'
|
import { PATHS } from '@src/lib/paths'
|
||||||
import type { SettingsLevel } from '@src/lib/settings/settingsTypes'
|
import type { SettingsLevel } from '@src/lib/settings/settingsTypes'
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const [searchParams, setSearchParams] = useSearchParams()
|
const [searchParams, setSearchParams] = useSearchParams()
|
||||||
const close = () => navigate(location.pathname.replace(PATHS.SETTINGS, ''))
|
const close = () => {
|
||||||
|
// This makes sure input texts are saved before closing the dialog (eg. default project name).
|
||||||
|
if (document.activeElement instanceof HTMLInputElement) {
|
||||||
|
document.activeElement.blur()
|
||||||
|
}
|
||||||
|
navigate(location.pathname.replace(PATHS.SETTINGS, ''))
|
||||||
|
}
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const isFileSettings = location.pathname.includes(PATHS.FILE)
|
const isFileSettings = location.pathname.includes(PATHS.FILE)
|
||||||
const searchParamTab =
|
const searchParamTab =
|
||||||
@ -25,13 +29,13 @@ export const Settings = () => {
|
|||||||
(isFileSettings ? 'project' : 'user')
|
(isFileSettings ? 'project' : 'user')
|
||||||
|
|
||||||
const scrollRef = useRef<HTMLDivElement>(null)
|
const scrollRef = useRef<HTMLDivElement>(null)
|
||||||
const dotDotSlash = useDotDotSlash()
|
|
||||||
useHotkeys('esc', () => navigate(dotDotSlash()))
|
|
||||||
|
|
||||||
// Scroll to the hash on load if it exists
|
// Scroll to the hash on load if it exists
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('hash', location.hash)
|
console.log('hash', location.hash)
|
||||||
if (location.hash) {
|
if (location.hash) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// GOTCHA: Next tick required, you can instantly navigate to a path and this code will find a null element and not scroll into view.
|
||||||
const element = document.getElementById(location.hash.slice(1))
|
const element = document.getElementById(location.hash.slice(1))
|
||||||
if (element) {
|
if (element) {
|
||||||
element.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
element.scrollIntoView({ block: 'center', behavior: 'smooth' })
|
||||||
@ -39,6 +43,7 @@ export const Settings = () => {
|
|||||||
element.querySelector('input, select, textarea') as HTMLInputElement
|
element.querySelector('input, select, textarea') as HTMLInputElement
|
||||||
)?.focus()
|
)?.focus()
|
||||||
}
|
}
|
||||||
|
}, 0)
|
||||||
}
|
}
|
||||||
}, [location.hash])
|
}, [location.hash])
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user