Compare commits
34 Commits
jtran/disa
...
jtran/requ
Author | SHA1 | Date | |
---|---|---|---|
7b46656c0f | |||
6c79b15adf | |||
b45aa89d16 | |||
834472e0a6 | |||
bcdf6e314f | |||
55e9845ade | |||
d61cf882c1 | |||
874d19cbfd | |||
9dcc955760 | |||
9b594efe53 | |||
7b9f40c4cb | |||
81b79da90f | |||
2ad5a880fa | |||
b57a9ba54c | |||
b32f5c1d4e | |||
b6d4cc7a4e | |||
43a34b191e | |||
19a93e8deb | |||
b8c623e1ec | |||
4006c28479 | |||
8c932fdb8d | |||
a74c715c01 | |||
1ac39d95f2 | |||
41b1ec94fa | |||
525c803888 | |||
2ee1c78aad | |||
dc21034b86 | |||
1684786659 | |||
12505b4398 | |||
115f2fdea2 | |||
0df28abc4b | |||
1e07ea4986 | |||
f34c23d203 | |||
5295f0ae7d |
@ -15,7 +15,7 @@ close(sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
|
|||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
```js
|
```js
|
||||||
startSketchOn('XZ')
|
const exampleSketch = startSketchOn('XZ')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([10, 10], %)
|
|> line([10, 10], %)
|
||||||
|> line([10, 0], %)
|
|> line([10, 0], %)
|
||||||
|
@ -81459,7 +81459,7 @@
|
|||||||
"unpublished": false,
|
"unpublished": false,
|
||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"examples": [
|
"examples": [
|
||||||
"startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 10], %)\n |> line([10, 0], %)\n |> close(%)\n |> extrude(10, %)",
|
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 10], %)\n |> line([10, 0], %)\n |> close(%)\n |> extrude(10, %)",
|
||||||
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
|
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -4179,12 +4179,15 @@ test.describe('Sketch tests', () => {
|
|||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
|
|
||||||
|
|
||||||
await expect(
|
await expect(async () => {
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
await page.mouse.click(700, 200)
|
||||||
).toBeEnabled()
|
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).toBeEnabled({ timeout: 1000 })
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||||
|
|
||||||
await page.waitForTimeout(600) // wait for animation
|
await page.waitForTimeout(600) // wait for animation
|
||||||
|
|
||||||
@ -7290,15 +7293,15 @@ test.describe('Test network and connection issues', () => {
|
|||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([-9.16, 8.81], %)`)
|
|> line([-8.84, 8.75], %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> line([${commonPoints.num1}, 0], %)
|
||||||
|> line([-9.16, 8.81], %)
|
|> line([-8.84, 8.75], %)
|
||||||
|> line([-5.28, 0], %)`)
|
|> line([-5.6, 0], %)`)
|
||||||
|
|
||||||
// Unequip line tool
|
// Unequip line tool
|
||||||
await page.keyboard.press('Escape')
|
await page.keyboard.press('Escape')
|
||||||
@ -8094,3 +8097,34 @@ test('Sketch on face', async ({ page }) => {
|
|||||||
const sketch002 = extrude(${[5, 5]} + 7, sketch002)`
|
const sketch002 = extrude(${[5, 5]} + 7, sketch002)`
|
||||||
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
|
await expect(page.locator('.cm-content')).toHaveText(result2.regExp)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Typing KCL errors induces a badge on the error logs pane button', async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
// Load the app with the working starter code
|
||||||
|
await page.addInitScript((code) => {
|
||||||
|
localStorage.setItem('persistCode', code)
|
||||||
|
}, bracket)
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
// wait for execution done
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// Ensure no badge is present
|
||||||
|
const errorLogsButton = page.getByRole('button', { name: 'KCL Code pane' })
|
||||||
|
await expect(errorLogsButton).not.toContainText('notification')
|
||||||
|
|
||||||
|
// Delete a character to break the KCL
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await page.getByText('extrude(').click()
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
|
// Ensure that a badge appears on the button
|
||||||
|
await expect(errorLogsButton).toContainText('notification')
|
||||||
|
})
|
||||||
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
@ -266,7 +266,7 @@ export async function getUtils(page: Page) {
|
|||||||
getSegmentBodyCoords: async (locator: string, px = 30) => {
|
getSegmentBodyCoords: async (locator: string, px = 30) => {
|
||||||
const overlay = page.locator(locator)
|
const overlay = page.locator(locator)
|
||||||
const bbox = await overlay
|
const bbox = await overlay
|
||||||
.boundingBox()
|
.boundingBox({ timeout: 5000 })
|
||||||
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 }))
|
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 }))
|
||||||
const angle = Number(await overlay.getAttribute('data-overlay-angle'))
|
const angle = Number(await overlay.getAttribute('data-overlay-angle'))
|
||||||
const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px
|
const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px
|
||||||
|
1439
openapi/machine-api.json
Normal file
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.24.4",
|
"version": "0.24.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.17.0",
|
"@codemirror/autocomplete": "^6.17.0",
|
||||||
|
@ -23,6 +23,7 @@ export default defineConfig({
|
|||||||
reporter: [
|
reporter: [
|
||||||
[process.env.CI ? 'dot' : 'list'],
|
[process.env.CI ? 'dot' : 'list'],
|
||||||
['json', { outputFile: './test-results/report.json' }],
|
['json', { outputFile: './test-results/report.json' }],
|
||||||
|
['html'],
|
||||||
],
|
],
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
use: {
|
use: {
|
||||||
|
80
src-tauri/Cargo.lock
generated
@ -188,7 +188,7 @@ dependencies = [
|
|||||||
"tauri-plugin-shell",
|
"tauri-plugin-shell",
|
||||||
"tauri-plugin-updater",
|
"tauri-plugin-updater",
|
||||||
"tokio",
|
"tokio",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -648,6 +648,12 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.6.0"
|
version = "1.6.0"
|
||||||
@ -721,7 +727,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719"
|
checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -792,9 +798,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.11"
|
version = "4.5.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3"
|
checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@ -802,9 +808,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.11"
|
version = "4.5.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa"
|
checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@ -816,9 +822,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.11"
|
version = "4.5.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e"
|
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -1383,7 +1389,7 @@ dependencies = [
|
|||||||
"cc",
|
"cc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"vswhom",
|
"vswhom",
|
||||||
"winreg 0.52.0",
|
"winreg 0.52.0",
|
||||||
]
|
]
|
||||||
@ -2399,6 +2405,18 @@ dependencies = [
|
|||||||
"unicode-normalization",
|
"unicode-normalization",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.25.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder-lite",
|
||||||
|
"num-traits",
|
||||||
|
"png",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
@ -2571,7 +2589,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.2.2"
|
version = "0.2.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
@ -2589,6 +2607,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"git_rev",
|
"git_rev",
|
||||||
"gltf-json",
|
"gltf-json",
|
||||||
|
"image",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"kittycad",
|
"kittycad",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@ -2603,10 +2622,11 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-tungstenite",
|
"tokio-tungstenite",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"tower-lsp",
|
"tower-lsp",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
"url",
|
"url",
|
||||||
|
"urlencoding",
|
||||||
"uuid",
|
"uuid",
|
||||||
"validator",
|
"validator",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@ -3520,9 +3540,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phonenumber"
|
name = "phonenumber"
|
||||||
version = "0.3.5+8.13.36"
|
version = "0.3.6+8.13.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f174c8db59b620032bd52b655fc97000458850fec0db35fcd4e802b668517ec0"
|
checksum = "11756237b57b8cc5e97dc8b1e70ea436324d30e7075de63b14fd15073a8f692a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"either",
|
"either",
|
||||||
@ -5069,7 +5089,7 @@ dependencies = [
|
|||||||
"cfg-expr",
|
"cfg-expr",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"version-compare",
|
"version-compare",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -5222,7 +5242,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"tauri-winres",
|
"tauri-winres",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -5280,7 +5300,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tauri-utils",
|
"tauri-utils",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -5564,7 +5584,7 @@ dependencies = [
|
|||||||
"serde_with",
|
"serde_with",
|
||||||
"swift-rs",
|
"swift-rs",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"toml 0.8.16",
|
"toml 0.8.19",
|
||||||
"url",
|
"url",
|
||||||
"urlpattern",
|
"urlpattern",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
@ -5811,21 +5831,21 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.16"
|
version = "0.8.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c"
|
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"toml_edit 0.22.17",
|
"toml_edit 0.22.20",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.7"
|
version = "0.6.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db"
|
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -5867,15 +5887,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.17"
|
version = "0.22.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16"
|
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.6",
|
"indexmap 2.2.6",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"winnow 0.6.6",
|
"winnow 0.6.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -6239,6 +6259,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "urlpattern"
|
name = "urlpattern"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@ -6946,9 +6972,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.6"
|
version = "0.6.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
|
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -80,5 +80,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"version": "0.24.4"
|
"version": "0.24.7"
|
||||||
}
|
}
|
||||||
|
20
src/App.tsx
@ -95,16 +95,16 @@ export function App() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const newCmdId = uuidv4()
|
const newCmdId = uuidv4()
|
||||||
if (context.store?.buttonDownInStream === undefined) {
|
if (state.matches('idle.showPlanes')) return
|
||||||
debounceSocketSend({
|
if (context.store?.buttonDownInStream !== undefined) return
|
||||||
type: 'modeling_cmd_req',
|
debounceSocketSend({
|
||||||
cmd: {
|
type: 'modeling_cmd_req',
|
||||||
type: 'highlight_set_entity',
|
cmd: {
|
||||||
selected_at_window: { x, y },
|
type: 'highlight_set_entity',
|
||||||
},
|
selected_at_window: { x, y },
|
||||||
cmd_id: newCmdId,
|
},
|
||||||
})
|
cmd_id: newCmdId,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
194
src/Toolbar.tsx
@ -190,49 +190,59 @@ export function Toolbar({
|
|||||||
maybeIconConfig[0].onClick(configCallbackProps)
|
maybeIconConfig[0].onClick(configCallbackProps)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ToolbarItemContents
|
<span
|
||||||
itemConfig={maybeIconConfig[0]}
|
className={!maybeIconConfig[0].showTitle ? 'sr-only' : ''}
|
||||||
configCallbackProps={configCallbackProps}
|
>
|
||||||
/>
|
{maybeIconConfig[0].title}
|
||||||
|
</span>
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
<ToolbarItemTooltip
|
||||||
|
itemConfig={maybeIconConfig[0]}
|
||||||
|
configCallbackProps={configCallbackProps}
|
||||||
|
/>
|
||||||
</ActionButtonDropdown>
|
</ActionButtonDropdown>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const itemConfig = maybeIconConfig
|
const itemConfig = maybeIconConfig
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionButton
|
<div className="relative" key={itemConfig.id}>
|
||||||
Element="button"
|
<ActionButton
|
||||||
key={itemConfig.id}
|
Element="button"
|
||||||
id={itemConfig.id}
|
key={itemConfig.id}
|
||||||
data-testid={itemConfig.id}
|
id={itemConfig.id}
|
||||||
iconStart={{
|
data-testid={itemConfig.id}
|
||||||
icon: itemConfig.icon,
|
iconStart={{
|
||||||
className: iconClassName,
|
icon: itemConfig.icon,
|
||||||
bgClassName: bgClassName,
|
className: iconClassName,
|
||||||
}}
|
bgClassName: bgClassName,
|
||||||
className={
|
}}
|
||||||
'pressed:!text-chalkboard-10 pressed:enabled:hovered:!text-chalkboard-10 ' +
|
className={
|
||||||
buttonBorderClassName +
|
'pressed:!text-chalkboard-10 pressed:enabled:hovered:!text-chalkboard-10 ' +
|
||||||
' ' +
|
buttonBorderClassName +
|
||||||
buttonBgClassName +
|
' ' +
|
||||||
(!itemConfig.showTitle ? ' !px-0' : '')
|
buttonBgClassName +
|
||||||
}
|
(!itemConfig.showTitle ? ' !px-0' : '')
|
||||||
name={itemConfig.title}
|
}
|
||||||
aria-description={itemConfig.description}
|
name={itemConfig.title}
|
||||||
aria-pressed={itemConfig.isActive}
|
aria-description={itemConfig.description}
|
||||||
disabled={
|
aria-pressed={itemConfig.isActive}
|
||||||
disableAllButtons ||
|
disabled={
|
||||||
itemConfig.status !== 'available' ||
|
disableAllButtons ||
|
||||||
itemConfig.disabled
|
itemConfig.status !== 'available' ||
|
||||||
}
|
itemConfig.disabled
|
||||||
onClick={() => itemConfig.onClick(configCallbackProps)}
|
}
|
||||||
>
|
onClick={() => itemConfig.onClick(configCallbackProps)}
|
||||||
<ToolbarItemContents
|
>
|
||||||
|
<span className={!itemConfig.showTitle ? 'sr-only' : ''}>
|
||||||
|
{itemConfig.title}
|
||||||
|
</span>
|
||||||
|
</ActionButton>
|
||||||
|
<ToolbarItemTooltip
|
||||||
itemConfig={itemConfig}
|
itemConfig={itemConfig}
|
||||||
configCallbackProps={configCallbackProps}
|
configCallbackProps={configCallbackProps}
|
||||||
/>
|
/>
|
||||||
</ActionButton>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
@ -250,7 +260,7 @@ export function Toolbar({
|
|||||||
* It contains a tooltip with the title, description, and links
|
* It contains a tooltip with the title, description, and links
|
||||||
* and a hotkey listener
|
* and a hotkey listener
|
||||||
*/
|
*/
|
||||||
const ToolbarItemContents = memo(function ToolbarItemContents({
|
const ToolbarItemTooltip = memo(function ToolbarItemContents({
|
||||||
itemConfig,
|
itemConfig,
|
||||||
configCallbackProps,
|
configCallbackProps,
|
||||||
}: {
|
}: {
|
||||||
@ -272,73 +282,69 @@ const ToolbarItemContents = memo(function ToolbarItemContents({
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Tooltip
|
||||||
<span className={!itemConfig.showTitle ? 'sr-only' : ''}>
|
inert={false}
|
||||||
{itemConfig.title}
|
position="bottom"
|
||||||
</span>
|
wrapperClassName="!p-4 !pointer-events-auto"
|
||||||
<Tooltip
|
contentClassName="!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch"
|
||||||
position="bottom"
|
>
|
||||||
wrapperClassName="!p-4 !pointer-events-auto"
|
<div className="rounded-top flex items-center gap-2 pt-3 pb-2 px-2 bg-chalkboard-20/50 dark:bg-chalkboard-80/50">
|
||||||
contentClassName="!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch"
|
<span
|
||||||
>
|
className={`text-sm flex-1 ${
|
||||||
<div className="rounded-top flex items-center gap-2 pt-3 pb-2 px-2 bg-chalkboard-20/50 dark:bg-chalkboard-80/50">
|
itemConfig.status !== 'available'
|
||||||
<span
|
? 'text-chalkboard-70 dark:text-chalkboard-40'
|
||||||
className={`text-sm flex-1 ${
|
: ''
|
||||||
itemConfig.status !== 'available'
|
}`}
|
||||||
? 'text-chalkboard-70 dark:text-chalkboard-40'
|
>
|
||||||
: ''
|
{itemConfig.title}
|
||||||
}`}
|
</span>
|
||||||
>
|
{itemConfig.status === 'available' && itemConfig.hotkey ? (
|
||||||
{itemConfig.title}
|
<kbd className="flex-none hotkey">{itemConfig.hotkey}</kbd>
|
||||||
</span>
|
) : itemConfig.status === 'kcl-only' ? (
|
||||||
{itemConfig.status === 'available' && itemConfig.hotkey ? (
|
<>
|
||||||
<kbd className="flex-none hotkey">{itemConfig.hotkey}</kbd>
|
<span className="text-wrap font-sans flex-0 text-chalkboard-70 dark:text-chalkboard-40">
|
||||||
) : itemConfig.status === 'kcl-only' ? (
|
KCL code only
|
||||||
|
</span>
|
||||||
|
<CustomIcon
|
||||||
|
name="code"
|
||||||
|
className="w-5 h-5 text-chalkboard-70 dark:text-chalkboard-40"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
itemConfig.status === 'unavailable' && (
|
||||||
<>
|
<>
|
||||||
<span className="text-wrap font-sans flex-0 text-chalkboard-70 dark:text-chalkboard-40">
|
<span className="text-wrap font-sans flex-0 text-chalkboard-70 dark:text-chalkboard-40">
|
||||||
KCL code only
|
In development
|
||||||
</span>
|
</span>
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
name="code"
|
name="lockClosed"
|
||||||
className="w-5 h-5 text-chalkboard-70 dark:text-chalkboard-40"
|
className="w-5 h-5 text-chalkboard-70 dark:text-chalkboard-40"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
)
|
||||||
itemConfig.status === 'unavailable' && (
|
|
||||||
<>
|
|
||||||
<span className="text-wrap font-sans flex-0 text-chalkboard-70 dark:text-chalkboard-40">
|
|
||||||
In development
|
|
||||||
</span>
|
|
||||||
<CustomIcon
|
|
||||||
name="lockClosed"
|
|
||||||
className="w-5 h-5 text-chalkboard-70 dark:text-chalkboard-40"
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<p className="px-2 text-ch font-sans">{itemConfig.description}</p>
|
|
||||||
{itemConfig.links.length > 0 && (
|
|
||||||
<>
|
|
||||||
<hr className="border-chalkboard-20 dark:border-chalkboard-80" />
|
|
||||||
<ul className="p-0 px-1 m-0 flex flex-col">
|
|
||||||
{itemConfig.links.map((link) => (
|
|
||||||
<li key={link.label} className="contents">
|
|
||||||
<a
|
|
||||||
href={link.url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
className="flex items-center rounded-sm p-1 no-underline text-inherit hover:bg-primary/10 hover:text-primary dark:hover:bg-chalkboard-70 dark:hover:text-inherit"
|
|
||||||
>
|
|
||||||
<span className="flex-1">Open {link.label}</span>
|
|
||||||
<CustomIcon name="link" className="w-4 h-4" />
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</div>
|
||||||
</>
|
<p className="px-2 text-ch font-sans">{itemConfig.description}</p>
|
||||||
|
{itemConfig.links.length > 0 && (
|
||||||
|
<>
|
||||||
|
<hr className="border-chalkboard-20 dark:border-chalkboard-80" />
|
||||||
|
<ul className="p-0 px-1 m-0 flex flex-col">
|
||||||
|
{itemConfig.links.map((link) => (
|
||||||
|
<li key={link.label} className="contents">
|
||||||
|
<a
|
||||||
|
href={link.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="flex items-center rounded-sm p-1 no-underline text-inherit hover:bg-primary/10 hover:text-primary dark:hover:bg-chalkboard-70 dark:hover:text-inherit"
|
||||||
|
>
|
||||||
|
<span className="flex-1">Open {link.label}</span>
|
||||||
|
<CustomIcon name="link" className="w-4 h-4" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -102,6 +102,7 @@ export const ClientSideScene = ({
|
|||||||
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
|
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
|
||||||
canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false)
|
canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false)
|
||||||
sceneInfra.setSend(send)
|
sceneInfra.setSend(send)
|
||||||
|
engineCommandManager.modelingSend = send
|
||||||
return () => {
|
return () => {
|
||||||
canvas?.removeEventListener('mousemove', sceneInfra.onMouseMove)
|
canvas?.removeEventListener('mousemove', sceneInfra.onMouseMove)
|
||||||
canvas?.removeEventListener('mousedown', sceneInfra.onMouseDown)
|
canvas?.removeEventListener('mousedown', sceneInfra.onMouseDown)
|
||||||
|
@ -22,11 +22,9 @@ import {
|
|||||||
import {
|
import {
|
||||||
ARROWHEAD,
|
ARROWHEAD,
|
||||||
AXIS_GROUP,
|
AXIS_GROUP,
|
||||||
DEFAULT_PLANES,
|
|
||||||
DefaultPlane,
|
|
||||||
defaultPlaneColor,
|
|
||||||
getSceneScale,
|
getSceneScale,
|
||||||
INTERSECTION_PLANE_LAYER,
|
INTERSECTION_PLANE_LAYER,
|
||||||
|
OnClickCallbackArgs,
|
||||||
OnMouseEnterLeaveArgs,
|
OnMouseEnterLeaveArgs,
|
||||||
RAYCASTABLE_PLANE,
|
RAYCASTABLE_PLANE,
|
||||||
SEGMENT_LENGTH_LABEL,
|
SEGMENT_LENGTH_LABEL,
|
||||||
@ -78,6 +76,7 @@ import {
|
|||||||
} from 'lang/std/sketch'
|
} from 'lang/std/sketch'
|
||||||
import { isOverlap, normaliseAngle, roundOff, throttle } from 'lib/utils'
|
import { isOverlap, normaliseAngle, roundOff, throttle } from 'lib/utils'
|
||||||
import {
|
import {
|
||||||
|
addStartProfileAt,
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
createCallExpressionStdLib,
|
createCallExpressionStdLib,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
@ -200,6 +199,7 @@ export class SceneEntities {
|
|||||||
|
|
||||||
createIntersectionPlane() {
|
createIntersectionPlane() {
|
||||||
if (sceneInfra.scene.getObjectByName(RAYCASTABLE_PLANE)) {
|
if (sceneInfra.scene.getObjectByName(RAYCASTABLE_PLANE)) {
|
||||||
|
// this.removeIntersectionPlane()
|
||||||
console.warn('createIntersectionPlane called when it already exists')
|
console.warn('createIntersectionPlane called when it already exists')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -296,6 +296,51 @@ export class SceneEntities {
|
|||||||
if (intersectionPlane) this.scene.remove(intersectionPlane)
|
if (intersectionPlane) this.scene.remove(intersectionPlane)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupNoPointsListener({
|
||||||
|
sketchDetails,
|
||||||
|
afterClick,
|
||||||
|
}: {
|
||||||
|
sketchDetails: SketchDetails
|
||||||
|
afterClick: (args: OnClickCallbackArgs) => void
|
||||||
|
}) {
|
||||||
|
// Create a THREEjs plane to raycast clicks onto
|
||||||
|
this.createIntersectionPlane()
|
||||||
|
const quaternion = quaternionFromUpNForward(
|
||||||
|
new Vector3(...sketchDetails.yAxis),
|
||||||
|
new Vector3(...sketchDetails.zAxis)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Position the click raycast plane
|
||||||
|
if (this.intersectionPlane) {
|
||||||
|
this.intersectionPlane.setRotationFromQuaternion(quaternion)
|
||||||
|
this.intersectionPlane.position.copy(
|
||||||
|
new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
sceneInfra.setCallbacks({
|
||||||
|
onClick: async (args) => {
|
||||||
|
if (!args) return
|
||||||
|
if (args.mouseEvent.which !== 1) return
|
||||||
|
const { intersectionPoint } = args
|
||||||
|
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) return
|
||||||
|
const addStartProfileAtRes = addStartProfileAt(
|
||||||
|
kclManager.ast,
|
||||||
|
sketchDetails.sketchPathToNode,
|
||||||
|
[intersectionPoint.twoD.x, intersectionPoint.twoD.y]
|
||||||
|
)
|
||||||
|
|
||||||
|
if (trap(addStartProfileAtRes)) return
|
||||||
|
const { modifiedAst } = addStartProfileAtRes
|
||||||
|
|
||||||
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
|
this.removeIntersectionPlane()
|
||||||
|
|
||||||
|
// Now perform the caller-specified action
|
||||||
|
afterClick(args)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async setupSketch({
|
async setupSketch({
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
forward,
|
forward,
|
||||||
@ -672,21 +717,6 @@ export class SceneEntities {
|
|||||||
...this.mouseEnterLeaveCallbacks(),
|
...this.mouseEnterLeaveCallbacks(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setupRectangleOriginListener = () => {
|
|
||||||
sceneInfra.setCallbacks({
|
|
||||||
onClick: (args) => {
|
|
||||||
const twoD = args.intersectionPoint?.twoD
|
|
||||||
if (!twoD) {
|
|
||||||
console.warn(`This click didn't have a 2D intersection`, args)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sceneInfra.modelingSend({
|
|
||||||
type: 'Add rectangle origin',
|
|
||||||
data: [twoD.x, twoD.y],
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
setupDraftRectangle = async (
|
setupDraftRectangle = async (
|
||||||
sketchPathToNode: PathToNode,
|
sketchPathToNode: PathToNode,
|
||||||
forward: [number, number, number],
|
forward: [number, number, number],
|
||||||
@ -704,6 +734,8 @@ export class SceneEntities {
|
|||||||
if (trap(_node1)) return Promise.reject(_node1)
|
if (trap(_node1)) return Promise.reject(_node1)
|
||||||
const variableDeclarationName =
|
const variableDeclarationName =
|
||||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||||
|
const startSketchOn = _node1.node?.declarations
|
||||||
|
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||||
|
|
||||||
const tags: [string, string, string] = [
|
const tags: [string, string, string] = [
|
||||||
findUniqueName(_ast, 'rectangleSegmentA'),
|
findUniqueName(_ast, 'rectangleSegmentA'),
|
||||||
@ -711,15 +743,6 @@ export class SceneEntities {
|
|||||||
findUniqueName(_ast, 'rectangleSegmentC'),
|
findUniqueName(_ast, 'rectangleSegmentC'),
|
||||||
]
|
]
|
||||||
|
|
||||||
const _node2 = getNodeFromPath<VariableDeclaration>(
|
|
||||||
_ast,
|
|
||||||
sketchPathToNode || [],
|
|
||||||
'VariableDeclaration'
|
|
||||||
)
|
|
||||||
if (trap(_node2)) return Promise.reject(_node2)
|
|
||||||
const startSketchOn = _node2.node?.declarations
|
|
||||||
|
|
||||||
const startSketchOnInit = startSketchOn?.[0]?.init
|
|
||||||
startSketchOn[0].init = createPipeExpression([
|
startSketchOn[0].init = createPipeExpression([
|
||||||
startSketchOnInit,
|
startSketchOnInit,
|
||||||
...getRectangleCallExpressions(rectangleOrigin, tags),
|
...getRectangleCallExpressions(rectangleOrigin, tags),
|
||||||
@ -1477,146 +1500,6 @@ export class SceneEntities {
|
|||||||
this._tearDownSketch(0, resolve, reject, { removeAxis })
|
this._tearDownSketch(0, resolve, reject, { removeAxis })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
setupDefaultPlaneHover() {
|
|
||||||
sceneInfra.setCallbacks({
|
|
||||||
onMouseEnter: ({ selected }) => {
|
|
||||||
if (!(selected instanceof Mesh && selected.parent)) return
|
|
||||||
if (selected.parent.userData.type !== DEFAULT_PLANES) return
|
|
||||||
const type: DefaultPlane = selected.userData.type
|
|
||||||
selected.material.color = defaultPlaneColor(type, 0.5, 1)
|
|
||||||
},
|
|
||||||
onMouseLeave: ({ selected }) => {
|
|
||||||
if (!(selected instanceof Mesh && selected.parent)) return
|
|
||||||
if (selected.parent.userData.type !== DEFAULT_PLANES) return
|
|
||||||
const type: DefaultPlane = selected.userData.type
|
|
||||||
selected.material.color = defaultPlaneColor(type)
|
|
||||||
},
|
|
||||||
onClick: async (args) => {
|
|
||||||
const { entity_id } = await sendSelectEventToEngine(
|
|
||||||
args?.mouseEvent,
|
|
||||||
document.getElementById('video-stream') as HTMLVideoElement,
|
|
||||||
sceneInfra._streamDimensions
|
|
||||||
)
|
|
||||||
|
|
||||||
let _entity_id = entity_id
|
|
||||||
if (!_entity_id) return
|
|
||||||
if (
|
|
||||||
engineCommandManager.defaultPlanes?.xy === _entity_id ||
|
|
||||||
engineCommandManager.defaultPlanes?.xz === _entity_id ||
|
|
||||||
engineCommandManager.defaultPlanes?.yz === _entity_id ||
|
|
||||||
engineCommandManager.defaultPlanes?.negXy === _entity_id ||
|
|
||||||
engineCommandManager.defaultPlanes?.negXz === _entity_id ||
|
|
||||||
engineCommandManager.defaultPlanes?.negYz === _entity_id
|
|
||||||
) {
|
|
||||||
const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = {
|
|
||||||
[engineCommandManager.defaultPlanes.xy]: 'XY',
|
|
||||||
[engineCommandManager.defaultPlanes.xz]: 'XZ',
|
|
||||||
[engineCommandManager.defaultPlanes.yz]: 'YZ',
|
|
||||||
[engineCommandManager.defaultPlanes.negXy]: '-XY',
|
|
||||||
[engineCommandManager.defaultPlanes.negXz]: '-XZ',
|
|
||||||
[engineCommandManager.defaultPlanes.negYz]: '-YZ',
|
|
||||||
}
|
|
||||||
// TODO can we get this information from rust land when it creates the default planes?
|
|
||||||
// maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs)
|
|
||||||
let zAxis: [number, number, number] = [0, 0, 1]
|
|
||||||
let yAxis: [number, number, number] = [0, 1, 0]
|
|
||||||
|
|
||||||
// get unit vector from camera position to target
|
|
||||||
const camVector = sceneInfra.camControls.camera.position
|
|
||||||
.clone()
|
|
||||||
.sub(sceneInfra.camControls.target)
|
|
||||||
|
|
||||||
if (engineCommandManager.defaultPlanes?.xy === _entity_id) {
|
|
||||||
zAxis = [0, 0, 1]
|
|
||||||
yAxis = [0, 1, 0]
|
|
||||||
if (camVector.z < 0) {
|
|
||||||
zAxis = [0, 0, -1]
|
|
||||||
_entity_id = engineCommandManager.defaultPlanes?.negXy || ''
|
|
||||||
}
|
|
||||||
} else if (engineCommandManager.defaultPlanes?.yz === _entity_id) {
|
|
||||||
zAxis = [1, 0, 0]
|
|
||||||
yAxis = [0, 0, 1]
|
|
||||||
if (camVector.x < 0) {
|
|
||||||
zAxis = [-1, 0, 0]
|
|
||||||
_entity_id = engineCommandManager.defaultPlanes?.negYz || ''
|
|
||||||
}
|
|
||||||
} else if (engineCommandManager.defaultPlanes?.xz === _entity_id) {
|
|
||||||
zAxis = [0, 1, 0]
|
|
||||||
yAxis = [0, 0, 1]
|
|
||||||
_entity_id = engineCommandManager.defaultPlanes?.negXz || ''
|
|
||||||
if (camVector.y < 0) {
|
|
||||||
zAxis = [0, -1, 0]
|
|
||||||
_entity_id = engineCommandManager.defaultPlanes?.xz || ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sceneInfra.modelingSend({
|
|
||||||
type: 'Select default plane',
|
|
||||||
data: {
|
|
||||||
type: 'defaultPlane',
|
|
||||||
planeId: _entity_id,
|
|
||||||
plane: defaultPlaneStrMap[_entity_id],
|
|
||||||
zAxis,
|
|
||||||
yAxis,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const artifact = this.engineCommandManager.artifactMap[_entity_id]
|
|
||||||
// If we clicked on an extrude wall, we climb up the parent Id
|
|
||||||
// to get the sketch profile's face ID. If we clicked on an endcap,
|
|
||||||
// we already have it.
|
|
||||||
const pathId =
|
|
||||||
artifact?.type === 'extrudeWall' || artifact?.type === 'extrudeCap'
|
|
||||||
? artifact.pathId
|
|
||||||
: ''
|
|
||||||
|
|
||||||
// tsc cannot infer that target can have extrusions
|
|
||||||
// from the commandType (why?) so we need to cast it
|
|
||||||
const path = this.engineCommandManager.artifactMap?.[pathId || '']
|
|
||||||
const extrusionId =
|
|
||||||
path?.type === 'startPath' ? path.extrusionIds[0] : ''
|
|
||||||
|
|
||||||
// TODO: We get the first extrusion command ID,
|
|
||||||
// which is fine while backend systems only support one extrusion.
|
|
||||||
// but we need to more robustly handle resolving to the correct extrusion
|
|
||||||
// if there are multiple.
|
|
||||||
const extrusions = this.engineCommandManager.artifactMap?.[extrusionId]
|
|
||||||
|
|
||||||
if (artifact?.type !== 'extrudeCap' && artifact?.type !== 'extrudeWall')
|
|
||||||
return
|
|
||||||
|
|
||||||
const faceInfo = await getFaceDetails(_entity_id)
|
|
||||||
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis) return
|
|
||||||
const { z_axis, y_axis, origin } = faceInfo
|
|
||||||
const sketchPathToNode = getNodePathFromSourceRange(
|
|
||||||
kclManager.ast,
|
|
||||||
artifact.range
|
|
||||||
)
|
|
||||||
|
|
||||||
const extrudePathToNode = extrusions?.range
|
|
||||||
? getNodePathFromSourceRange(kclManager.ast, extrusions.range)
|
|
||||||
: []
|
|
||||||
|
|
||||||
sceneInfra.modelingSend({
|
|
||||||
type: 'Select default plane',
|
|
||||||
data: {
|
|
||||||
type: 'extrudeFace',
|
|
||||||
zAxis: [z_axis.x, z_axis.y, z_axis.z],
|
|
||||||
yAxis: [y_axis.x, y_axis.y, y_axis.z],
|
|
||||||
position: [origin.x, origin.y, origin.z].map(
|
|
||||||
(num) => num / sceneInfra._baseUnitMultiplier
|
|
||||||
) as [number, number, number],
|
|
||||||
sketchPathToNode,
|
|
||||||
extrudePathToNode,
|
|
||||||
cap: artifact.type === 'extrudeCap' ? artifact.cap : 'none',
|
|
||||||
faceId: _entity_id,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
mouseEnterLeaveCallbacks() {
|
mouseEnterLeaveCallbacks() {
|
||||||
return {
|
return {
|
||||||
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
|
onMouseEnter: ({ selected, dragSelected }: OnMouseEnterLeaveArgs) => {
|
||||||
|
@ -11,10 +11,8 @@ import {
|
|||||||
Raycaster,
|
Raycaster,
|
||||||
Vector2,
|
Vector2,
|
||||||
Group,
|
Group,
|
||||||
PlaneGeometry,
|
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
Mesh,
|
Mesh,
|
||||||
DoubleSide,
|
|
||||||
Intersection,
|
Intersection,
|
||||||
Object3D,
|
Object3D,
|
||||||
Object3DEventMap,
|
Object3DEventMap,
|
||||||
@ -48,7 +46,6 @@ export const DEBUG_SHOW_INTERSECTION_PLANE: false = false
|
|||||||
export const DEBUG_SHOW_BOTH_SCENES: false = false
|
export const DEBUG_SHOW_BOTH_SCENES: false = false
|
||||||
|
|
||||||
export const RAYCASTABLE_PLANE = 'raycastable-plane'
|
export const RAYCASTABLE_PLANE = 'raycastable-plane'
|
||||||
export const DEFAULT_PLANES = 'default-planes'
|
|
||||||
|
|
||||||
export const X_AXIS = 'xAxis'
|
export const X_AXIS = 'xAxis'
|
||||||
export const Y_AXIS = 'yAxis'
|
export const Y_AXIS = 'yAxis'
|
||||||
@ -72,7 +69,7 @@ interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
|
|||||||
}
|
}
|
||||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
||||||
}
|
}
|
||||||
interface OnClickCallbackArgs {
|
export interface OnClickCallbackArgs {
|
||||||
mouseEvent: MouseEvent
|
mouseEvent: MouseEvent
|
||||||
intersectionPoint?: {
|
intersectionPoint?: {
|
||||||
twoD: Vector2
|
twoD: Vector2
|
||||||
@ -325,16 +322,9 @@ export class SceneInfra {
|
|||||||
this.camControls.camera,
|
this.camControls.camera,
|
||||||
this.camControls.target
|
this.camControls.target
|
||||||
)
|
)
|
||||||
const planesGroup = this.scene.getObjectByName(DEFAULT_PLANES)
|
|
||||||
const axisGroup = this.scene
|
const axisGroup = this.scene
|
||||||
.getObjectByName(AXIS_GROUP)
|
.getObjectByName(AXIS_GROUP)
|
||||||
?.getObjectByName('gridHelper')
|
?.getObjectByName('gridHelper')
|
||||||
planesGroup &&
|
|
||||||
planesGroup.scale.set(
|
|
||||||
scale / this._baseUnitMultiplier,
|
|
||||||
scale / this._baseUnitMultiplier,
|
|
||||||
scale / this._baseUnitMultiplier
|
|
||||||
)
|
|
||||||
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
|
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -632,59 +622,6 @@ export class SceneInfra {
|
|||||||
this.onClickCallback({ mouseEvent, intersects })
|
this.onClickCallback({ mouseEvent, intersects })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
showDefaultPlanes() {
|
|
||||||
const addPlane = (
|
|
||||||
rotation: { x: number; y: number; z: number }, //
|
|
||||||
type: DefaultPlane
|
|
||||||
): Mesh => {
|
|
||||||
const planeGeometry = new PlaneGeometry(100, 100)
|
|
||||||
const planeMaterial = new MeshBasicMaterial({
|
|
||||||
color: defaultPlaneColor(type),
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.0,
|
|
||||||
side: DoubleSide,
|
|
||||||
depthTest: false, // needed to avoid transparency issues
|
|
||||||
})
|
|
||||||
const plane = new Mesh(planeGeometry, planeMaterial)
|
|
||||||
plane.rotation.x = rotation.x
|
|
||||||
plane.rotation.y = rotation.y
|
|
||||||
plane.rotation.z = rotation.z
|
|
||||||
plane.userData.type = type
|
|
||||||
plane.name = type
|
|
||||||
return plane
|
|
||||||
}
|
|
||||||
const planes = [
|
|
||||||
addPlane({ x: 0, y: Math.PI / 2, z: 0 }, YZ_PLANE),
|
|
||||||
addPlane({ x: 0, y: 0, z: 0 }, XY_PLANE),
|
|
||||||
addPlane({ x: -Math.PI / 2, y: 0, z: 0 }, XZ_PLANE),
|
|
||||||
]
|
|
||||||
const planesGroup = new Group()
|
|
||||||
planesGroup.userData.type = DEFAULT_PLANES
|
|
||||||
planesGroup.name = DEFAULT_PLANES
|
|
||||||
planesGroup.add(...planes)
|
|
||||||
planesGroup.traverse((child) => {
|
|
||||||
if (child instanceof Mesh) {
|
|
||||||
child.layers.enable(SKETCH_LAYER)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
planesGroup.layers.enable(SKETCH_LAYER)
|
|
||||||
const sceneScale = getSceneScale(
|
|
||||||
this.camControls.camera,
|
|
||||||
this.camControls.target
|
|
||||||
)
|
|
||||||
planesGroup.scale.set(
|
|
||||||
sceneScale / this._baseUnitMultiplier,
|
|
||||||
sceneScale / this._baseUnitMultiplier,
|
|
||||||
sceneScale / this._baseUnitMultiplier
|
|
||||||
)
|
|
||||||
this.scene.add(planesGroup)
|
|
||||||
}
|
|
||||||
removeDefaultPlanes() {
|
|
||||||
const planesGroup = this.scene.children.find(
|
|
||||||
({ userData }) => userData.type === DEFAULT_PLANES
|
|
||||||
)
|
|
||||||
if (planesGroup) this.scene.remove(planesGroup)
|
|
||||||
}
|
|
||||||
updateOtherSelectionColors = (otherSelections: Axis[]) => {
|
updateOtherSelectionColors = (otherSelections: Axis[]) => {
|
||||||
const axisGroup = this.scene.children.find(
|
const axisGroup = this.scene.children.find(
|
||||||
({ userData }) => userData?.type === AXIS_GROUP
|
({ userData }) => userData?.type === AXIS_GROUP
|
||||||
@ -742,28 +679,3 @@ function baseUnitTomm(baseUnit: BaseUnit) {
|
|||||||
return 914.4
|
return 914.4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DefaultPlane =
|
|
||||||
| 'xy-default-plane'
|
|
||||||
| 'xz-default-plane'
|
|
||||||
| 'yz-default-plane'
|
|
||||||
|
|
||||||
export const XY_PLANE: DefaultPlane = 'xy-default-plane'
|
|
||||||
export const XZ_PLANE: DefaultPlane = 'xz-default-plane'
|
|
||||||
export const YZ_PLANE: DefaultPlane = 'yz-default-plane'
|
|
||||||
|
|
||||||
export function defaultPlaneColor(
|
|
||||||
plane: DefaultPlane,
|
|
||||||
lowCh = 0.1,
|
|
||||||
highCh = 0.7
|
|
||||||
): Color {
|
|
||||||
switch (plane) {
|
|
||||||
case XY_PLANE:
|
|
||||||
return new Color(highCh, lowCh, lowCh)
|
|
||||||
case XZ_PLANE:
|
|
||||||
return new Color(lowCh, lowCh, highCh)
|
|
||||||
case YZ_PLANE:
|
|
||||||
return new Color(lowCh, highCh, lowCh)
|
|
||||||
}
|
|
||||||
return new Color(lowCh, lowCh, lowCh)
|
|
||||||
}
|
|
||||||
|
@ -5,6 +5,8 @@ import { CustomIcon } from './CustomIcon'
|
|||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { createAndOpenNewProject } from 'lib/tauriFS'
|
import { createAndOpenNewProject } from 'lib/tauriFS'
|
||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
|
import { useLspContext } from './LspProvider'
|
||||||
|
|
||||||
const HelpMenuDivider = () => (
|
const HelpMenuDivider = () => (
|
||||||
<div className="h-[1px] bg-chalkboard-110 dark:bg-chalkboard-80" />
|
<div className="h-[1px] bg-chalkboard-110 dark:bg-chalkboard-80" />
|
||||||
@ -12,16 +14,18 @@ const HelpMenuDivider = () => (
|
|||||||
|
|
||||||
export function HelpMenu(props: React.PropsWithChildren) {
|
export function HelpMenu(props: React.PropsWithChildren) {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const { onProjectOpen } = useLspContext()
|
||||||
|
const filePath = useAbsoluteFilePath()
|
||||||
const isInProject = location.pathname.includes(paths.FILE)
|
const isInProject = location.pathname.includes(paths.FILE)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
<Popover.Button className="border-none p-0 m-0 rounded-full grid place-content-center">
|
<Popover.Button className="grid p-0 m-0 border-none rounded-full place-content-center">
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
name="questionMark"
|
name="questionMark"
|
||||||
className="w-7 h-7 rounded-full bg-chalkboard-110 dark:bg-chalkboard-80 text-chalkboard-10"
|
className="rounded-full w-7 h-7 bg-chalkboard-110 dark:bg-chalkboard-80 text-chalkboard-10"
|
||||||
/>
|
/>
|
||||||
<span className="sr-only">Help and resources</span>
|
<span className="sr-only">Help and resources</span>
|
||||||
<Tooltip position="top-right" wrapperClassName="ui-open:hidden">
|
<Tooltip position="top-right" wrapperClassName="ui-open:hidden">
|
||||||
@ -30,7 +34,7 @@ export function HelpMenu(props: React.PropsWithChildren) {
|
|||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
<Popover.Panel
|
<Popover.Panel
|
||||||
as="ul"
|
as="ul"
|
||||||
className="absolute right-0 left-auto bottom-full mb-1 w-64 py-2 flex flex-col gap-1 align-stretch text-chalkboard-10 dark:text-inherit bg-chalkboard-110 dark:bg-chalkboard-100 rounded shadow-lg border border-solid border-chalkboard-110 dark:border-chalkboard-80 text-sm m-0 p-0"
|
className="absolute right-0 left-auto flex flex-col w-64 gap-1 p-0 py-2 m-0 mb-1 text-sm border border-solid rounded shadow-lg bottom-full align-stretch text-chalkboard-10 dark:text-inherit bg-chalkboard-110 dark:bg-chalkboard-100 border-chalkboard-110 dark:border-chalkboard-80"
|
||||||
>
|
>
|
||||||
<HelpMenuItem
|
<HelpMenuItem
|
||||||
as="a"
|
as="a"
|
||||||
@ -84,7 +88,12 @@ export function HelpMenu(props: React.PropsWithChildren) {
|
|||||||
</HelpMenuItem>
|
</HelpMenuItem>
|
||||||
<HelpMenuItem
|
<HelpMenuItem
|
||||||
as="button"
|
as="button"
|
||||||
onClick={() => navigate('settings?tab=keybindings')}
|
onClick={() => {
|
||||||
|
const targetPath = location.pathname.includes(paths.FILE)
|
||||||
|
? filePath + paths.SETTINGS
|
||||||
|
: paths.HOME + paths.SETTINGS
|
||||||
|
navigate(targetPath + '?tab=keybindings')
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Keyboard shortcuts
|
Keyboard shortcuts
|
||||||
</HelpMenuItem>
|
</HelpMenuItem>
|
||||||
@ -99,9 +108,9 @@ export function HelpMenu(props: React.PropsWithChildren) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (isInProject) {
|
if (isInProject) {
|
||||||
navigate('onboarding')
|
navigate(filePath + paths.ONBOARDING.INDEX)
|
||||||
} else {
|
} else {
|
||||||
createAndOpenNewProject(navigate)
|
createAndOpenNewProject({ onProjectOpen, navigate })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -128,7 +137,7 @@ function HelpMenuItem({
|
|||||||
}: HelpMenuItemProps) {
|
}: HelpMenuItemProps) {
|
||||||
const baseClassName = 'block px-2 py-1 hover:bg-chalkboard-80'
|
const baseClassName = 'block px-2 py-1 hover:bg-chalkboard-80'
|
||||||
return (
|
return (
|
||||||
<li className="m-0 p-0">
|
<li className="p-0 m-0">
|
||||||
{as === 'a' ? (
|
{as === 'a' ? (
|
||||||
<a
|
<a
|
||||||
{...(props as React.ComponentProps<'a'>)}
|
{...(props as React.ComponentProps<'a'>)}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useMachine } from '@xstate/react'
|
import { useMachine } from '@xstate/react'
|
||||||
import React, { createContext, useEffect, useRef } from 'react'
|
import React, { createContext, useEffect, useMemo, useRef } from 'react'
|
||||||
import {
|
import {
|
||||||
AnyStateMachine,
|
AnyStateMachine,
|
||||||
ContextFrom,
|
ContextFrom,
|
||||||
@ -8,7 +8,12 @@ import {
|
|||||||
StateFrom,
|
StateFrom,
|
||||||
assign,
|
assign,
|
||||||
} from 'xstate'
|
} from 'xstate'
|
||||||
import { SetSelections, modelingMachine } from 'machines/modelingMachine'
|
import {
|
||||||
|
SetSelections,
|
||||||
|
getPersistedContext,
|
||||||
|
modelingMachine,
|
||||||
|
modelingMachineDefaultContext,
|
||||||
|
} from 'machines/modelingMachine'
|
||||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import {
|
import {
|
||||||
@ -99,6 +104,7 @@ export const ModelingMachineProvider = ({
|
|||||||
} = useSettingsAuthContext()
|
} = useSettingsAuthContext()
|
||||||
const token = auth?.context?.token
|
const token = auth?.context?.token
|
||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
|
const persistedContext = useMemo(() => getPersistedContext(), [])
|
||||||
|
|
||||||
let [searchParams] = useSearchParams()
|
let [searchParams] = useSearchParams()
|
||||||
const pool = searchParams.get('pool')
|
const pool = searchParams.get('pool')
|
||||||
@ -121,6 +127,13 @@ export const ModelingMachineProvider = ({
|
|||||||
const [modelingState, modelingSend, modelingActor] = useMachine(
|
const [modelingState, modelingSend, modelingActor] = useMachine(
|
||||||
modelingMachine,
|
modelingMachine,
|
||||||
{
|
{
|
||||||
|
context: {
|
||||||
|
...modelingMachineDefaultContext,
|
||||||
|
store: {
|
||||||
|
...modelingMachineDefaultContext.store,
|
||||||
|
...persistedContext,
|
||||||
|
},
|
||||||
|
},
|
||||||
actions: {
|
actions: {
|
||||||
'disable copilot': () => {
|
'disable copilot': () => {
|
||||||
editorManager.setCopilotEnabled(false)
|
editorManager.setCopilotEnabled(false)
|
||||||
@ -142,7 +155,9 @@ export const ModelingMachineProvider = ({
|
|||||||
kclManager.executeCode().then(() => {
|
kclManager.executeCode().then(() => {
|
||||||
if (engineCommandManager.engineConnection?.idleMode) return
|
if (engineCommandManager.engineConnection?.idleMode) return
|
||||||
|
|
||||||
store.videoElement?.play()
|
store.videoElement?.play().catch((e) => {
|
||||||
|
console.warn('Video playing was prevented', e)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
},
|
},
|
||||||
|
@ -27,27 +27,3 @@ export const LogsPane = () => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const KclErrorsPane = () => {
|
|
||||||
const theme = useResolvedTheme()
|
|
||||||
const { errors } = useKclContext()
|
|
||||||
return (
|
|
||||||
<div className="overflow-hidden">
|
|
||||||
<div className="absolute inset-0 p-2 flex flex-col overflow-auto">
|
|
||||||
<ReactJsonTypeHack
|
|
||||||
src={errors}
|
|
||||||
collapsed={1}
|
|
||||||
collapseStringsAfterLength={60}
|
|
||||||
enableClipboard={false}
|
|
||||||
displayArrayKey={false}
|
|
||||||
displayDataTypes={false}
|
|
||||||
displayObjectSize={true}
|
|
||||||
indentWidth={2}
|
|
||||||
quotesOnKeys={false}
|
|
||||||
name={false}
|
|
||||||
theme={theme === 'light' ? 'rjv-default' : 'monokai'}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -3,7 +3,6 @@ import {
|
|||||||
faBugSlash,
|
faBugSlash,
|
||||||
faCode,
|
faCode,
|
||||||
faCodeCommit,
|
faCodeCommit,
|
||||||
faExclamationCircle,
|
|
||||||
faSquareRootVariable,
|
faSquareRootVariable,
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { KclEditorMenu } from 'components/ModelingSidebar/ModelingPanes/KclEditorMenu'
|
import { KclEditorMenu } from 'components/ModelingSidebar/ModelingPanes/KclEditorMenu'
|
||||||
@ -11,20 +10,28 @@ import { CustomIconName } from 'components/CustomIcon'
|
|||||||
import { KclEditorPane } from 'components/ModelingSidebar/ModelingPanes/KclEditorPane'
|
import { KclEditorPane } from 'components/ModelingSidebar/ModelingPanes/KclEditorPane'
|
||||||
import { ReactNode } from 'react'
|
import { ReactNode } from 'react'
|
||||||
import { MemoryPane, MemoryPaneMenu } from './MemoryPane'
|
import { MemoryPane, MemoryPaneMenu } from './MemoryPane'
|
||||||
import { KclErrorsPane, LogsPane } from './LoggingPanes'
|
import { LogsPane } from './LoggingPanes'
|
||||||
import { DebugPane } from './DebugPane'
|
import { DebugPane } from './DebugPane'
|
||||||
import { FileTreeInner, FileTreeMenu } from 'components/FileTree'
|
import { FileTreeInner, FileTreeMenu } from 'components/FileTree'
|
||||||
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
|
|
||||||
export type SidebarType =
|
export type SidebarType =
|
||||||
| 'code'
|
| 'code'
|
||||||
| 'debug'
|
| 'debug'
|
||||||
| 'export'
|
| 'export'
|
||||||
| 'files'
|
| 'files'
|
||||||
| 'kclErrors'
|
|
||||||
| 'logs'
|
| 'logs'
|
||||||
| 'lspMessages'
|
| 'lspMessages'
|
||||||
| 'variables'
|
| 'variables'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface can be extended as more context is needed for the panes
|
||||||
|
* to determine if they should show their badges or not.
|
||||||
|
*/
|
||||||
|
interface PaneCallbackProps {
|
||||||
|
kclContext: ReturnType<typeof useKclContext>
|
||||||
|
}
|
||||||
|
|
||||||
export type SidebarPane = {
|
export type SidebarPane = {
|
||||||
id: SidebarType
|
id: SidebarType
|
||||||
title: string
|
title: string
|
||||||
@ -33,6 +40,7 @@ export type SidebarPane = {
|
|||||||
Content: ReactNode | React.FC
|
Content: ReactNode | React.FC
|
||||||
Menu?: ReactNode | React.FC
|
Menu?: ReactNode | React.FC
|
||||||
hideOnPlatform?: 'desktop' | 'web'
|
hideOnPlatform?: 'desktop' | 'web'
|
||||||
|
showBadge?: (props: PaneCallbackProps) => boolean | number
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sidebarPanes: SidebarPane[] = [
|
export const sidebarPanes: SidebarPane[] = [
|
||||||
@ -43,6 +51,7 @@ export const sidebarPanes: SidebarPane[] = [
|
|||||||
Content: KclEditorPane,
|
Content: KclEditorPane,
|
||||||
keybinding: 'Shift + C',
|
keybinding: 'Shift + C',
|
||||||
Menu: KclEditorMenu,
|
Menu: KclEditorMenu,
|
||||||
|
showBadge: ({ kclContext }) => kclContext.errors.length,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'files',
|
id: 'files',
|
||||||
@ -68,13 +77,6 @@ export const sidebarPanes: SidebarPane[] = [
|
|||||||
Content: LogsPane,
|
Content: LogsPane,
|
||||||
keybinding: 'Shift + L',
|
keybinding: 'Shift + L',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'kclErrors',
|
|
||||||
title: 'KCL Errors',
|
|
||||||
icon: faExclamationCircle,
|
|
||||||
Content: KclErrorsPane,
|
|
||||||
keybinding: 'Shift + E',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'debug',
|
id: 'debug',
|
||||||
title: 'Debug',
|
title: 'Debug',
|
||||||
|
@ -12,6 +12,7 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
|||||||
import { CustomIconName } from 'components/CustomIcon'
|
import { CustomIconName } from 'components/CustomIcon'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
|
|
||||||
interface ModelingSidebarProps {
|
interface ModelingSidebarProps {
|
||||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||||
@ -19,6 +20,7 @@ interface ModelingSidebarProps {
|
|||||||
|
|
||||||
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
|
const kclContext = useKclContext()
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const onboardingStatus = settings.context.app.onboardingStatus
|
const onboardingStatus = settings.context.app.onboardingStatus
|
||||||
const { send, context } = useModelingContext()
|
const { send, context } = useModelingContext()
|
||||||
@ -62,6 +64,15 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
[sidebarPanes, showDebugPanel.current]
|
[sidebarPanes, showDebugPanel.current]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const paneBadgeMap: Record<SidebarType, number | boolean> = useMemo(() => {
|
||||||
|
return filteredPanes.reduce((acc, pane) => {
|
||||||
|
if (pane.showBadge) {
|
||||||
|
acc[pane.id] = pane.showBadge({ kclContext })
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as Record<SidebarType, number | boolean>)
|
||||||
|
}, [kclContext.errors])
|
||||||
|
|
||||||
const togglePane = useCallback(
|
const togglePane = useCallback(
|
||||||
(newPane: SidebarType) => {
|
(newPane: SidebarType) => {
|
||||||
send({
|
send({
|
||||||
@ -120,6 +131,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
paneIsOpen={context.store?.openPanes.includes(pane.id)}
|
paneIsOpen={context.store?.openPanes.includes(pane.id)}
|
||||||
onClick={() => togglePane(pane.id)}
|
onClick={() => togglePane(pane.id)}
|
||||||
aria-pressed={context.store?.openPanes.includes(pane.id)}
|
aria-pressed={context.store?.openPanes.includes(pane.id)}
|
||||||
|
showBadge={paneBadgeMap[pane.id]}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@ -186,12 +198,14 @@ interface ModelingPaneButtonProps
|
|||||||
}
|
}
|
||||||
onClick: () => void
|
onClick: () => void
|
||||||
paneIsOpen?: boolean
|
paneIsOpen?: boolean
|
||||||
|
showBadge?: boolean | number
|
||||||
}
|
}
|
||||||
|
|
||||||
function ModelingPaneButton({
|
function ModelingPaneButton({
|
||||||
paneConfig,
|
paneConfig,
|
||||||
onClick,
|
onClick,
|
||||||
paneIsOpen,
|
paneIsOpen,
|
||||||
|
showBadge,
|
||||||
...props
|
...props
|
||||||
}: ModelingPaneButtonProps) {
|
}: ModelingPaneButtonProps) {
|
||||||
useHotkeys(paneConfig.keybinding, onClick, {
|
useHotkeys(paneConfig.keybinding, onClick, {
|
||||||
@ -223,6 +237,23 @@ function ModelingPaneButton({
|
|||||||
{paneConfig.title}
|
{paneConfig.title}
|
||||||
{paneIsOpen !== undefined ? ` pane` : ''}
|
{paneIsOpen !== undefined ? ` pane` : ''}
|
||||||
</span>
|
</span>
|
||||||
|
{!!showBadge && (
|
||||||
|
<p
|
||||||
|
className={
|
||||||
|
'absolute m-0 p-0 -top-1 -right-1 w-3 h-3 flex items-center justify-center text-[10px] font-semibold text-white bg-primary hue-rotate-90 rounded-full border border-chalkboard-10 dark:border-chalkboard-80'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<span className="sr-only"> has </span>
|
||||||
|
{typeof showBadge === 'number' ? (
|
||||||
|
<span>{showBadge}</span>
|
||||||
|
) : (
|
||||||
|
<span className="sr-only">a</span>
|
||||||
|
)}
|
||||||
|
<span className="sr-only">
|
||||||
|
notification{Number(showBadge) > 1 ? 's' : ''}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
position="right"
|
position="right"
|
||||||
contentClassName="max-w-none flex items-center gap-4"
|
contentClassName="max-w-none flex items-center gap-4"
|
||||||
|
@ -19,7 +19,8 @@ import { createAndOpenNewProject, getSettingsFolderPaths } from 'lib/tauriFS'
|
|||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import { useDotDotSlash } from 'hooks/useDotDotSlash'
|
import { useDotDotSlash } from 'hooks/useDotDotSlash'
|
||||||
import { sep } from '@tauri-apps/api/path'
|
import { sep } from '@tauri-apps/api/path'
|
||||||
import { ForwardedRef, forwardRef } from 'react'
|
import { ForwardedRef, forwardRef, useEffect } from 'react'
|
||||||
|
import { useLspContext } from 'components/LspProvider'
|
||||||
|
|
||||||
interface AllSettingsFieldsProps {
|
interface AllSettingsFieldsProps {
|
||||||
searchParamTab: SettingsLevel
|
searchParamTab: SettingsLevel
|
||||||
@ -33,9 +34,10 @@ export const AllSettingsFields = forwardRef(
|
|||||||
) => {
|
) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const { onProjectOpen } = useLspContext()
|
||||||
const dotDotSlash = useDotDotSlash()
|
const dotDotSlash = useDotDotSlash()
|
||||||
const {
|
const {
|
||||||
settings: { send, context },
|
settings: { send, context, state },
|
||||||
} = useSettingsAuthContext()
|
} = useSettingsAuthContext()
|
||||||
|
|
||||||
const projectPath =
|
const projectPath =
|
||||||
@ -48,19 +50,37 @@ export const AllSettingsFields = forwardRef(
|
|||||||
)
|
)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
function restartOnboarding() {
|
async function restartOnboarding() {
|
||||||
send({
|
send({
|
||||||
type: `set.app.onboardingStatus`,
|
type: `set.app.onboardingStatus`,
|
||||||
data: { level: 'user', value: '' },
|
data: { level: 'user', value: '' },
|
||||||
})
|
})
|
||||||
|
|
||||||
if (isFileSettings) {
|
|
||||||
navigate(dotDotSlash(1) + paths.ONBOARDING.INDEX)
|
|
||||||
} else {
|
|
||||||
createAndOpenNewProject(navigate)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A "listener" for the XState to return to "idle" state
|
||||||
|
* when the user resets the onboarding, using the callback above
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
async function navigateToOnboardingStart() {
|
||||||
|
if (
|
||||||
|
state.context.app.onboardingStatus.user === '' &&
|
||||||
|
state.matches('idle')
|
||||||
|
) {
|
||||||
|
if (isFileSettings) {
|
||||||
|
// If we're in a project, first navigate to the onboarding start here
|
||||||
|
// so we can trigger the warning screen if necessary
|
||||||
|
navigate(dotDotSlash(1) + paths.ONBOARDING.INDEX)
|
||||||
|
} else {
|
||||||
|
// If we're in the global settings, create a new project and navigate
|
||||||
|
// to the onboarding start in that project
|
||||||
|
await createAndOpenNewProject({ onProjectOpen, navigate })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
navigateToOnboardingStart()
|
||||||
|
}, [isFileSettings, navigate, state])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative overflow-y-auto">
|
<div className="relative overflow-y-auto">
|
||||||
<div ref={scrollRef} className="flex flex-col gap-4 px-2">
|
<div ref={scrollRef} className="flex flex-col gap-4 px-2">
|
||||||
|
@ -175,11 +175,35 @@ export const SettingsAuthProviderBase = ({
|
|||||||
id: `${event.type}.success`,
|
id: `${event.type}.success`,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
'Execute AST': () => {
|
'Execute AST': (context, event) => {
|
||||||
kclManager.isFirstRender = true
|
try {
|
||||||
kclManager.executeCode(true).then(() => {
|
const allSettingsIncludesUnitChange =
|
||||||
kclManager.isFirstRender = false
|
event.type === 'Set all settings' &&
|
||||||
})
|
event.settings?.modeling?.defaultUnit?.current !==
|
||||||
|
context.modeling.defaultUnit.current
|
||||||
|
const resetSettingsIncludesUnitChange =
|
||||||
|
event.type === 'Reset settings' &&
|
||||||
|
context.modeling.defaultUnit.current !==
|
||||||
|
settings?.modeling?.defaultUnit?.default
|
||||||
|
|
||||||
|
if (
|
||||||
|
event.type === 'set.modeling.defaultUnit' ||
|
||||||
|
allSettingsIncludesUnitChange ||
|
||||||
|
resetSettingsIncludesUnitChange
|
||||||
|
) {
|
||||||
|
kclManager.isFirstRender = true
|
||||||
|
kclManager.executeCode(true).then(() => {
|
||||||
|
kclManager.isFirstRender = false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// For any future logging we'd like to do
|
||||||
|
// console.log(
|
||||||
|
// 'Not re-executing AST because the settings change did not affect the code interpretation'
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error executing AST after settings change', e)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
|
@ -156,7 +156,13 @@ export const Stream = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsFirstRender(kclManager.isFirstRender)
|
setIsFirstRender(kclManager.isFirstRender)
|
||||||
if (!kclManager.isFirstRender) videoRef.current?.play()
|
if (!kclManager.isFirstRender)
|
||||||
|
setTimeout(() =>
|
||||||
|
// execute in the next event loop
|
||||||
|
videoRef.current?.play().catch((e) => {
|
||||||
|
console.warn('Video playing was prevented', e, videoRef.current)
|
||||||
|
})
|
||||||
|
)
|
||||||
setIsFreezeFrame(!kclManager.isFirstRender)
|
setIsFreezeFrame(!kclManager.isFirstRender)
|
||||||
}, [kclManager.isFirstRender])
|
}, [kclManager.isFirstRender])
|
||||||
|
|
||||||
@ -170,8 +176,12 @@ export const Stream = () => {
|
|||||||
if (!mediaStream) return
|
if (!mediaStream) return
|
||||||
|
|
||||||
// Do not immediately play the stream!
|
// Do not immediately play the stream!
|
||||||
videoRef.current.srcObject = mediaStream
|
try {
|
||||||
videoRef.current.pause()
|
videoRef.current.srcObject = mediaStream
|
||||||
|
videoRef.current.pause()
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Attempted to pause stream while play was still loading', e)
|
||||||
|
}
|
||||||
|
|
||||||
send({
|
send({
|
||||||
type: 'Set context',
|
type: 'Set context',
|
||||||
@ -215,7 +225,7 @@ export const Stream = () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (state.matches('Sketch')) return
|
if (state.matches('Sketch')) return
|
||||||
if (state.matches('Sketch no face')) return
|
if (state.matches('idle.showPlanes')) return
|
||||||
|
|
||||||
if (!context.store?.didDragInStream && btnName(e).left) {
|
if (!context.store?.didDragInStream && btnName(e).left) {
|
||||||
sendSelectEventToEngine(
|
sendSelectEventToEngine(
|
||||||
|
@ -57,7 +57,8 @@
|
|||||||
transition-delay: var(--_delay);
|
transition-delay: var(--_delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
:is(:focus-visible) > .tooltipWrapper.withFocus {
|
:is(:focus-visible) > .tooltipWrapper.withFocus,
|
||||||
|
:focus-within > .tooltipWrapper.withFocus {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ export default function Tooltip({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
// @ts-ignore while awaiting merge of this PR for support of "inert" https://github.com/DefinitelyTyped/DefinitelyTyped/pull/60822
|
// @ts-ignore while awaiting merge of this PR for support of "inert" https://github.com/DefinitelyTyped/DefinitelyTyped/pull/60822
|
||||||
inert={inert}
|
{...{ inert: inert ? '' : undefined }}
|
||||||
role="tooltip"
|
role="tooltip"
|
||||||
className={`p-3 ${
|
className={`p-3 ${
|
||||||
position !== 'left' && position !== 'right' ? 'px-0' : ''
|
position !== 'left' && position !== 'right' ? 'px-0' : ''
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { editorManager, engineCommandManager } from 'lib/singletons'
|
import {
|
||||||
|
editorManager,
|
||||||
|
engineCommandManager,
|
||||||
|
kclManager,
|
||||||
|
sceneInfra,
|
||||||
|
} from 'lib/singletons'
|
||||||
import { useModelingContext } from './useModelingContext'
|
import { useModelingContext } from './useModelingContext'
|
||||||
import { getEventForSelectWithPoint } from 'lib/selections'
|
import { getEventForSelectWithPoint } from 'lib/selections'
|
||||||
|
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
|
||||||
|
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
|
|
||||||
export function useEngineConnectionSubscriptions() {
|
export function useEngineConnectionSubscriptions() {
|
||||||
const { send, context } = useModelingContext()
|
const { send, context, state } = useModelingContext()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!engineCommandManager) return
|
if (!engineCommandManager) return
|
||||||
@ -40,4 +47,135 @@ export function useEngineConnectionSubscriptions() {
|
|||||||
unSubClick()
|
unSubClick()
|
||||||
}
|
}
|
||||||
}, [engineCommandManager, context?.sketchEnginePathId])
|
}, [engineCommandManager, context?.sketchEnginePathId])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const unSub = engineCommandManager.subscribeTo({
|
||||||
|
event: 'select_with_point',
|
||||||
|
callback: state.matches('Sketch no face')
|
||||||
|
? async ({ data }) => {
|
||||||
|
let planeId = data.entity_id
|
||||||
|
if (!planeId) return
|
||||||
|
if (
|
||||||
|
engineCommandManager.defaultPlanes?.xy === planeId ||
|
||||||
|
engineCommandManager.defaultPlanes?.xz === planeId ||
|
||||||
|
engineCommandManager.defaultPlanes?.yz === planeId ||
|
||||||
|
engineCommandManager.defaultPlanes?.negXy === planeId ||
|
||||||
|
engineCommandManager.defaultPlanes?.negXz === planeId ||
|
||||||
|
engineCommandManager.defaultPlanes?.negYz === planeId
|
||||||
|
) {
|
||||||
|
const defaultPlaneStrMap: Record<string, DefaultPlaneStr> = {
|
||||||
|
[engineCommandManager.defaultPlanes.xy]: 'XY',
|
||||||
|
[engineCommandManager.defaultPlanes.xz]: 'XZ',
|
||||||
|
[engineCommandManager.defaultPlanes.yz]: 'YZ',
|
||||||
|
[engineCommandManager.defaultPlanes.negXy]: '-XY',
|
||||||
|
[engineCommandManager.defaultPlanes.negXz]: '-XZ',
|
||||||
|
[engineCommandManager.defaultPlanes.negYz]: '-YZ',
|
||||||
|
}
|
||||||
|
// TODO can we get this information from rust land when it creates the default planes?
|
||||||
|
// maybe returned from make_default_planes (src/wasm-lib/src/wasm.rs)
|
||||||
|
let zAxis: [number, number, number] = [0, 0, 1]
|
||||||
|
let yAxis: [number, number, number] = [0, 1, 0]
|
||||||
|
|
||||||
|
// get unit vector from camera position to target
|
||||||
|
const camVector = sceneInfra.camControls.camera.position
|
||||||
|
.clone()
|
||||||
|
.sub(sceneInfra.camControls.target)
|
||||||
|
|
||||||
|
if (engineCommandManager.defaultPlanes?.xy === planeId) {
|
||||||
|
zAxis = [0, 0, 1]
|
||||||
|
yAxis = [0, 1, 0]
|
||||||
|
if (camVector.z < 0) {
|
||||||
|
zAxis = [0, 0, -1]
|
||||||
|
planeId = engineCommandManager.defaultPlanes?.negXy || ''
|
||||||
|
}
|
||||||
|
} else if (engineCommandManager.defaultPlanes?.yz === planeId) {
|
||||||
|
zAxis = [1, 0, 0]
|
||||||
|
yAxis = [0, 0, 1]
|
||||||
|
if (camVector.x < 0) {
|
||||||
|
zAxis = [-1, 0, 0]
|
||||||
|
planeId = engineCommandManager.defaultPlanes?.negYz || ''
|
||||||
|
}
|
||||||
|
} else if (engineCommandManager.defaultPlanes?.xz === planeId) {
|
||||||
|
zAxis = [0, 1, 0]
|
||||||
|
yAxis = [0, 0, 1]
|
||||||
|
planeId = engineCommandManager.defaultPlanes?.negXz || ''
|
||||||
|
if (camVector.y < 0) {
|
||||||
|
zAxis = [0, -1, 0]
|
||||||
|
planeId = engineCommandManager.defaultPlanes?.xz || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sceneInfra.modelingSend({
|
||||||
|
type: 'Select default plane',
|
||||||
|
data: {
|
||||||
|
type: 'defaultPlane',
|
||||||
|
planeId: planeId,
|
||||||
|
plane: defaultPlaneStrMap[planeId],
|
||||||
|
zAxis,
|
||||||
|
yAxis,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const artifact = engineCommandManager.artifactMap[planeId]
|
||||||
|
console.log('artifact', artifact)
|
||||||
|
// If we clicked on an extrude wall, we climb up the parent Id
|
||||||
|
// to get the sketch profile's face ID. If we clicked on an endcap,
|
||||||
|
// we already have it.
|
||||||
|
const pathId =
|
||||||
|
artifact?.type === 'extrudeWall' ||
|
||||||
|
artifact?.type === 'extrudeCap'
|
||||||
|
? artifact.pathId
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const path = engineCommandManager.artifactMap?.[pathId || '']
|
||||||
|
const extrusionId =
|
||||||
|
path?.type === 'startPath' ? path.extrusionIds[0] : ''
|
||||||
|
|
||||||
|
// TODO: We get the first extrusion command ID,
|
||||||
|
// which is fine while backend systems only support one extrusion.
|
||||||
|
// but we need to more robustly handle resolving to the correct extrusion
|
||||||
|
// if there are multiple.
|
||||||
|
const extrusions = engineCommandManager.artifactMap?.[extrusionId]
|
||||||
|
|
||||||
|
if (
|
||||||
|
artifact?.type !== 'extrudeCap' &&
|
||||||
|
artifact?.type !== 'extrudeWall'
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
const faceInfo = await getFaceDetails(planeId)
|
||||||
|
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
||||||
|
return
|
||||||
|
const { z_axis, y_axis, origin } = faceInfo
|
||||||
|
const sketchPathToNode = getNodePathFromSourceRange(
|
||||||
|
kclManager.ast,
|
||||||
|
artifact.range
|
||||||
|
)
|
||||||
|
|
||||||
|
const extrudePathToNode = extrusions?.range
|
||||||
|
? getNodePathFromSourceRange(kclManager.ast, extrusions.range)
|
||||||
|
: []
|
||||||
|
|
||||||
|
sceneInfra.modelingSend({
|
||||||
|
type: 'Select default plane',
|
||||||
|
data: {
|
||||||
|
type: 'extrudeFace',
|
||||||
|
zAxis: [z_axis.x, z_axis.y, z_axis.z],
|
||||||
|
yAxis: [y_axis.x, y_axis.y, y_axis.z],
|
||||||
|
position: [origin.x, origin.y, origin.z].map(
|
||||||
|
(num) => num / sceneInfra._baseUnitMultiplier
|
||||||
|
) as [number, number, number],
|
||||||
|
sketchPathToNode,
|
||||||
|
extrudePathToNode,
|
||||||
|
cap: artifact.type === 'extrudeCap' ? artifact.cap : 'none',
|
||||||
|
faceId: planeId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
: () => {},
|
||||||
|
})
|
||||||
|
return unSub
|
||||||
|
}, [state])
|
||||||
}
|
}
|
||||||
|
@ -186,6 +186,7 @@ export function useSetupEngineManager(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getDimensions(streamWidth?: number, streamHeight?: number) {
|
function getDimensions(streamWidth?: number, streamHeight?: number) {
|
||||||
|
const factorOf = 4
|
||||||
const maxResolution = 2000
|
const maxResolution = 2000
|
||||||
const width = streamWidth ? streamWidth : 0
|
const width = streamWidth ? streamWidth : 0
|
||||||
const height = streamHeight ? streamHeight : 0
|
const height = streamHeight ? streamHeight : 0
|
||||||
@ -193,7 +194,7 @@ function getDimensions(streamWidth?: number, streamHeight?: number) {
|
|||||||
Math.min(maxResolution / width, maxResolution / height),
|
Math.min(maxResolution / width, maxResolution / height),
|
||||||
1.0
|
1.0
|
||||||
)
|
)
|
||||||
const quadWidth = Math.round((width * ratio) / 4) * 4
|
const quadWidth = Math.round((width * ratio) / factorOf) * factorOf
|
||||||
const quadHeight = Math.round((height * ratio) / 4) * 4
|
const quadHeight = Math.round((height * ratio) / factorOf) * factorOf
|
||||||
return { width: quadWidth, height: quadHeight }
|
return { width: quadWidth, height: quadHeight }
|
||||||
}
|
}
|
||||||
|
@ -224,6 +224,7 @@ export class KclManager {
|
|||||||
|
|
||||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||||
defaultSelectionFilter(programMemory, this.engineCommandManager)
|
defaultSelectionFilter(programMemory, this.engineCommandManager)
|
||||||
|
await this.engineCommandManager.waitForAllCommands()
|
||||||
|
|
||||||
if (zoomToFit) {
|
if (zoomToFit) {
|
||||||
let zoomObjectId: string | undefined = ''
|
let zoomObjectId: string | undefined = ''
|
||||||
@ -243,6 +244,15 @@ export class KclManager {
|
|||||||
padding: 0.1, // padding around the objects
|
padding: 0.1, // padding around the objects
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
await this.engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'zoom_to_fit',
|
||||||
|
object_ids: zoomObjectId ? [zoomObjectId] : [], // leave empty to zoom to all objects
|
||||||
|
padding: 0.1, // padding around the objects
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
this.isExecuting = false
|
this.isExecuting = false
|
||||||
|
@ -2,7 +2,7 @@ import { Program, SourceRange } from 'lang/wasm'
|
|||||||
import { VITE_KC_API_WS_MODELING_URL } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL } from 'env'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { exportSave } from 'lib/exportSave'
|
import { exportSave } from 'lib/exportSave'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { deferExecution, uuidv4 } from 'lib/utils'
|
||||||
import { Themes, getThemeColorForEngine, getOppositeTheme } from 'lib/theme'
|
import { Themes, getThemeColorForEngine, getOppositeTheme } from 'lib/theme'
|
||||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
import {
|
import {
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
ResponseMap,
|
ResponseMap,
|
||||||
createArtifactMap,
|
createArtifactMap,
|
||||||
} from 'lang/std/artifactMap'
|
} from 'lang/std/artifactMap'
|
||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
|
||||||
// TODO(paultag): This ought to be tweakable.
|
// TODO(paultag): This ought to be tweakable.
|
||||||
const pingIntervalMs = 10000
|
const pingIntervalMs = 10000
|
||||||
@ -1204,6 +1205,8 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
private onEngineConnectionNewTrack = ({
|
private onEngineConnectionNewTrack = ({
|
||||||
detail,
|
detail,
|
||||||
}: CustomEvent<NewTrackArgs>) => {}
|
}: CustomEvent<NewTrackArgs>) => {}
|
||||||
|
modelingSend: ReturnType<typeof useModelingContext>['send'] =
|
||||||
|
(() => {}) as any
|
||||||
|
|
||||||
start({
|
start({
|
||||||
restart,
|
restart,
|
||||||
@ -1549,7 +1552,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async startNewSession() {
|
async startNewSession() {
|
||||||
this.artifactMap = {}
|
|
||||||
this.orderedCommands = []
|
this.orderedCommands = []
|
||||||
this.responseMap = {}
|
this.responseMap = {}
|
||||||
await this.initPlanes()
|
await this.initPlanes()
|
||||||
@ -1784,6 +1786,14 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
this.engineConnection?.send(message.command)
|
this.engineConnection?.send(message.command)
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deferredArtifactPopulated = deferExecution((a?: null) => {
|
||||||
|
this.modelingSend({ type: 'Artifact graph populated' })
|
||||||
|
}, 200)
|
||||||
|
deferredArtifactEmptied = deferExecution((a?: null) => {
|
||||||
|
this.modelingSend({ type: 'Artifact graph emptied' })
|
||||||
|
}, 200)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When an execution takes place we want to wait until we've got replies for all of the commands
|
* When an execution takes place we want to wait until we've got replies for all of the commands
|
||||||
* When this is done when we build the artifact map synchronously.
|
* When this is done when we build the artifact map synchronously.
|
||||||
@ -1795,21 +1805,16 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
responseMap: this.responseMap,
|
responseMap: this.responseMap,
|
||||||
ast: this.getAst(),
|
ast: this.getAst(),
|
||||||
})
|
})
|
||||||
|
if (Object.values(this.artifactMap).length) {
|
||||||
|
this.deferredArtifactEmptied(null)
|
||||||
|
} else {
|
||||||
|
this.deferredArtifactPopulated(null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private async initPlanes() {
|
private async initPlanes() {
|
||||||
if (this.planesInitialized()) return
|
if (this.planesInitialized()) return
|
||||||
const planes = await this.makeDefaultPlanes()
|
const planes = await this.makeDefaultPlanes()
|
||||||
this.defaultPlanes = planes
|
this.defaultPlanes = planes
|
||||||
|
|
||||||
this.subscribeTo({
|
|
||||||
event: 'select_with_point',
|
|
||||||
callback: ({ data }) => {
|
|
||||||
if (!data?.entity_id) return
|
|
||||||
if (!planes) return
|
|
||||||
if (![planes.xy, planes.yz, planes.xz].includes(data.entity_id)) return
|
|
||||||
this.onPlaneSelectCallback(data.entity_id)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
planesInitialized(): boolean {
|
planesInitialized(): boolean {
|
||||||
return (
|
return (
|
||||||
@ -1820,11 +1825,6 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
onPlaneSelectCallback = (id: string) => {}
|
|
||||||
onPlaneSelected(callback: (id: string) => void) {
|
|
||||||
this.onPlaneSelectCallback = callback
|
|
||||||
}
|
|
||||||
|
|
||||||
async setPlaneHidden(id: string, hidden: boolean) {
|
async setPlaneHidden(id: string, hidden: boolean) {
|
||||||
return await this.sendSceneCommand({
|
return await this.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
|
@ -24,13 +24,6 @@ export const getRectangleCallExpressions = (
|
|||||||
rectangleOrigin: [number, number],
|
rectangleOrigin: [number, number],
|
||||||
tags: [string, string, string]
|
tags: [string, string, string]
|
||||||
) => [
|
) => [
|
||||||
createCallExpressionStdLib('startProfileAt', [
|
|
||||||
createArrayExpression([
|
|
||||||
createLiteral(roundOff(rectangleOrigin[0])),
|
|
||||||
createLiteral(roundOff(rectangleOrigin[1])),
|
|
||||||
]),
|
|
||||||
createPipeSubstitution(),
|
|
||||||
]),
|
|
||||||
createCallExpressionStdLib('angledLine', [
|
createCallExpressionStdLib('angledLine', [
|
||||||
createArrayExpression([
|
createArrayExpression([
|
||||||
createLiteral(0), // 0 deg
|
createLiteral(0), // 0 deg
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
listProjects,
|
listProjects,
|
||||||
readAppSettingsFile,
|
readAppSettingsFile,
|
||||||
} from './tauri'
|
} from './tauri'
|
||||||
|
import { engineCommandManager } from './singletons'
|
||||||
|
|
||||||
export const isHidden = (fileOrDir: FileEntry) =>
|
export const isHidden = (fileOrDir: FileEntry) =>
|
||||||
!!fileOrDir.name?.startsWith('.')
|
!!fileOrDir.name?.startsWith('.')
|
||||||
@ -116,9 +117,23 @@ export async function getSettingsFolderPaths(projectPath?: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAndOpenNewProject(
|
export async function createAndOpenNewProject({
|
||||||
|
onProjectOpen,
|
||||||
|
navigate,
|
||||||
|
}: {
|
||||||
|
onProjectOpen: (
|
||||||
|
project: {
|
||||||
|
name: string | null
|
||||||
|
path: string | null
|
||||||
|
} | null,
|
||||||
|
file: FileEntry | null
|
||||||
|
) => void
|
||||||
navigate: (path: string) => void
|
navigate: (path: string) => void
|
||||||
) {
|
}) {
|
||||||
|
// Clear the scene and end the session.
|
||||||
|
engineCommandManager.endSession()
|
||||||
|
|
||||||
|
// Create a new project with the onboarding project name
|
||||||
const configuration = await readAppSettingsFile()
|
const configuration = await readAppSettingsFile()
|
||||||
const projects = await listProjects(configuration)
|
const projects = await listProjects(configuration)
|
||||||
const nextIndex = getNextProjectIndex(ONBOARDING_PROJECT_NAME, projects)
|
const nextIndex = getNextProjectIndex(ONBOARDING_PROJECT_NAME, projects)
|
||||||
@ -126,6 +141,24 @@ export async function createAndOpenNewProject(
|
|||||||
ONBOARDING_PROJECT_NAME,
|
ONBOARDING_PROJECT_NAME,
|
||||||
nextIndex
|
nextIndex
|
||||||
)
|
)
|
||||||
const newFile = await createNewProjectDirectory(name, bracket, configuration)
|
const newProject = await createNewProjectDirectory(
|
||||||
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
|
name,
|
||||||
|
bracket,
|
||||||
|
configuration
|
||||||
|
)
|
||||||
|
|
||||||
|
// Prep the LSP and navigate to the onboarding start
|
||||||
|
onProjectOpen(
|
||||||
|
{
|
||||||
|
name: newProject.name,
|
||||||
|
path: newProject.path,
|
||||||
|
},
|
||||||
|
null
|
||||||
|
)
|
||||||
|
navigate(
|
||||||
|
`${paths.FILE}/${encodeURIComponent(newProject.default_file)}${
|
||||||
|
paths.ONBOARDING.INDEX
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
return newProject
|
||||||
}
|
}
|
||||||
|
@ -3,22 +3,16 @@ import { onboardingPaths } from 'routes/Onboarding/paths'
|
|||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
import {
|
import { createAndOpenNewProject } from 'lib/tauriFS'
|
||||||
getNextProjectIndex,
|
|
||||||
interpolateProjectNameWithIndex,
|
|
||||||
} from 'lib/tauriFS'
|
|
||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
|
||||||
import { paths } from 'lib/paths'
|
|
||||||
import { codeManager, kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
import { join } from '@tauri-apps/api/path'
|
import { APP_NAME } from 'lib/constants'
|
||||||
import {
|
|
||||||
APP_NAME,
|
|
||||||
ONBOARDING_PROJECT_NAME,
|
|
||||||
PROJECT_ENTRYPOINT,
|
|
||||||
} from 'lib/constants'
|
|
||||||
import { createNewProjectDirectory, listProjects } from 'lib/tauri'
|
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
import { useLspContext } from 'components/LspProvider'
|
||||||
|
import { IndexLoaderData } from 'lib/types'
|
||||||
|
import { paths } from 'lib/paths'
|
||||||
|
import { useFileContext } from 'hooks/useFileContext'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show either a welcome screen or a warning screen
|
* Show either a welcome screen or a warning screen
|
||||||
@ -47,30 +41,28 @@ function OnboardingResetWarning(props: OnboardingResetWarningProps) {
|
|||||||
{!isTauri() ? (
|
{!isTauri() ? (
|
||||||
<OnboardingWarningWeb {...props} />
|
<OnboardingWarningWeb {...props} />
|
||||||
) : (
|
) : (
|
||||||
<OnboardingWarningDesktop />
|
<OnboardingWarningDesktop {...props} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function OnboardingWarningDesktop() {
|
function OnboardingWarningDesktop(props: OnboardingResetWarningProps) {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
|
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
||||||
|
const { context: fileContext } = useFileContext()
|
||||||
|
const { onProjectClose, onProjectOpen } = useLspContext()
|
||||||
|
|
||||||
async function createAndOpenNewProject() {
|
async function onAccept() {
|
||||||
const projects = await listProjects()
|
onProjectClose(
|
||||||
const nextIndex = getNextProjectIndex(ONBOARDING_PROJECT_NAME, projects)
|
loaderData.file || null,
|
||||||
const name = interpolateProjectNameWithIndex(
|
fileContext.project.path || null,
|
||||||
ONBOARDING_PROJECT_NAME,
|
false
|
||||||
nextIndex
|
|
||||||
)
|
|
||||||
const newFile = await createNewProjectDirectory(name, bracket)
|
|
||||||
navigate(
|
|
||||||
`${paths.FILE}/${encodeURIComponent(
|
|
||||||
await join(newFile.path, PROJECT_ENTRYPOINT)
|
|
||||||
)}${paths.ONBOARDING.INDEX}`
|
|
||||||
)
|
)
|
||||||
|
await createAndOpenNewProject({ onProjectOpen, navigate })
|
||||||
|
props.setShouldShowWarning(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -88,11 +80,7 @@ function OnboardingWarningDesktop() {
|
|||||||
<OnboardingButtons
|
<OnboardingButtons
|
||||||
className="mt-6"
|
className="mt-6"
|
||||||
dismiss={dismiss}
|
dismiss={dismiss}
|
||||||
next={() => {
|
next={onAccept}
|
||||||
void createAndOpenNewProject()
|
|
||||||
codeManager.updateCodeEditor(bracket)
|
|
||||||
dismiss()
|
|
||||||
}}
|
|
||||||
nextText="Make a new project"
|
nextText="Make a new project"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
@ -79,7 +79,7 @@ export const onboardingRoutes = [
|
|||||||
|
|
||||||
export function useDemoCode() {
|
export function useDemoCode() {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editorManager.editorView) return
|
if (!editorManager.editorView || codeManager.code === bracket) return
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
codeManager.updateCodeStateEditor(bracket)
|
codeManager.updateCodeStateEditor(bracket)
|
||||||
kclManager.isFirstRender = true
|
kclManager.isFirstRender = true
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
# Each test can have at most 4 threads, but if its name contains "serial_test_", then it
|
|
||||||
# also requires 4 threads.
|
|
||||||
# This means such tests run one at a time, with 4 threads.
|
|
||||||
|
|
||||||
[test-groups]
|
[test-groups]
|
||||||
serial-integration = { max-threads = 4 }
|
# If a test uses the engine, we want to limit the number that can run in parallel.
|
||||||
|
# This way we don't start and stop too many engine instances, putting pressure on our cloud.
|
||||||
|
uses-engine = { max-threads = 4 }
|
||||||
|
|
||||||
[profile.default]
|
[profile.default]
|
||||||
slow-timeout = { period = "30s", terminate-after = 1 }
|
slow-timeout = { period = "30s", terminate-after = 1 }
|
||||||
@ -12,13 +10,15 @@ slow-timeout = { period = "30s", terminate-after = 1 }
|
|||||||
slow-timeout = { period = "50s", terminate-after = 5 }
|
slow-timeout = { period = "50s", terminate-after = 5 }
|
||||||
|
|
||||||
[[profile.default.overrides]]
|
[[profile.default.overrides]]
|
||||||
filter = "test(serial_test_)"
|
# If a test starts with kcl_test_, then it uses the engine. So, limit its parallelism.
|
||||||
test-group = "serial-integration"
|
filter = "test(kcl_test_)"
|
||||||
|
test-group = "uses-engine"
|
||||||
threads-required = 2
|
threads-required = 2
|
||||||
|
|
||||||
[[profile.ci.overrides]]
|
[[profile.ci.overrides]]
|
||||||
filter = "test(serial_test_)"
|
# If a test starts with kcl_test_, then it uses the engine. So, limit its parallelism.
|
||||||
test-group = "serial-integration"
|
filter = "test(kcl_test_)"
|
||||||
|
test-group = "uses-engine"
|
||||||
threads-required = 2
|
threads-required = 2
|
||||||
|
|
||||||
[[profile.default.overrides]]
|
[[profile.default.overrides]]
|
||||||
|
37
src/wasm-lib/Cargo.lock
generated
@ -406,9 +406,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.11"
|
version = "4.5.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35723e6a11662c2afb578bcf0b88bf6ea8e21282a953428f240574fcc3a2b5b3"
|
checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@ -416,9 +416,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.11"
|
version = "4.5.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49eb96cbfa7cfa35017b7cd548c75b14c3118c98b423041d70562665e07fb0fa"
|
checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@ -430,9 +430,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.11"
|
version = "4.5.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d029b67f89d30bbb547c89fd5161293c0aec155fc691d7924b64550662db93e"
|
checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -1437,6 +1437,7 @@ dependencies = [
|
|||||||
"ts-rs",
|
"ts-rs",
|
||||||
"twenty-twenty",
|
"twenty-twenty",
|
||||||
"url",
|
"url",
|
||||||
|
"urlencoding",
|
||||||
"uuid",
|
"uuid",
|
||||||
"validator",
|
"validator",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@ -3128,9 +3129,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.16"
|
version = "0.8.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81967dd0dd2c1ab0bc3468bd7caecc32b8a4aa47d0c8c695d8c2b2108168d62c"
|
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
@ -3140,24 +3141,24 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_datetime"
|
name = "toml_datetime"
|
||||||
version = "0.6.7"
|
version = "0.6.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8fb9f64314842840f1d940ac544da178732128f1c78c21772e876579e0da1db"
|
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.22.17"
|
version = "0.22.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d9f8729f5aea9562aac1cc0441f5d6de3cff1ee0c5d67293eeca5eb36ee7c16"
|
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.5",
|
"indexmap 2.2.5",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"winnow 0.6.5",
|
"winnow 0.6.18",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3438,6 +3439,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "urlencoding"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf-8"
|
name = "utf-8"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@ -3851,9 +3858,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.6.5"
|
version = "0.6.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8"
|
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -11,13 +11,13 @@ crate-type = ["cdylib"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bson = { version = "2.11.0", features = ["uuid-1", "chrono"] }
|
bson = { version = "2.11.0", features = ["uuid-1", "chrono"] }
|
||||||
clap = "4.5.11"
|
clap = "4.5.13"
|
||||||
gloo-utils = "0.2.0"
|
gloo-utils = "0.2.0"
|
||||||
kcl-lib = { path = "kcl" }
|
kcl-lib = { path = "kcl" }
|
||||||
kittycad.workspace = true
|
kittycad.workspace = true
|
||||||
serde_json = "1.0.121"
|
serde_json = "1.0.121"
|
||||||
tokio = { version = "1.39.2", features = ["sync"] }
|
tokio = { version = "1.39.2", features = ["sync"] }
|
||||||
toml = "0.8.16"
|
toml = "0.8.19"
|
||||||
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
||||||
wasm-bindgen = "0.2.91"
|
wasm-bindgen = "0.2.91"
|
||||||
wasm-bindgen-futures = "0.4.42"
|
wasm-bindgen-futures = "0.4.42"
|
||||||
|
@ -766,85 +766,10 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn #test_name() {
|
async fn #test_name() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = #code_block;
|
||||||
let http_client = reqwest::Client::builder()
|
// Note, `crate` must be kcl_lib
|
||||||
.user_agent(user_agent)
|
let result = crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm).await.unwrap();
|
||||||
// For file conversions we need this to be long.
|
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #test_name_str), &result, 0.99);
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
// For file conversions we need this to be long.
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
|
|
||||||
// Create the client.
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
// Set a local engine address if it's set.
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer(#code_block).unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default()).await.unwrap();
|
|
||||||
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
|
|
||||||
// Ensure it lints.
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Zoom to fit.
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await.unwrap();
|
|
||||||
|
|
||||||
// Send a snapshot request to the engine.
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await.unwrap();
|
|
||||||
|
|
||||||
// Create a temporary file to write the output to.
|
|
||||||
let output_file = std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
// Save the snapshot locally.
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Read the output file.
|
|
||||||
let actual = image::io::Reader::open(output_file).unwrap().decode().unwrap();
|
|
||||||
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #test_name_str), &actual, 0.99);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,76 +21,14 @@ mod test_examples_someFn {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_someFn0() {
|
async fn serial_test_example_someFn0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "someFn()";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("someFn()").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,76 +21,14 @@ mod test_examples_someFn {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_someFn0() {
|
async fn serial_test_example_someFn0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "someFn()";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("someFn()").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,76 +21,14 @@ mod test_examples_show {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_show0() {
|
async fn serial_test_example_show0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is another code block.\nyes sirrr.\nshow";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nshow").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -116,76 +54,14 @@ mod test_examples_show {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_show1() {
|
async fn serial_test_example_show1() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is code.\nIt does other shit.\nshow";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_show1"),
|
&format!("tests/outputs/{}.png", "serial_test_example_show1"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,76 +21,14 @@ mod test_examples_show {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_show0() {
|
async fn serial_test_example_show0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is code.\nIt does other shit.\nshow";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -22,77 +22,14 @@ mod test_examples_my_func {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_my_func0() {
|
async fn serial_test_example_my_func0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is another code block.\nyes sirrr.\nmyFunc";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens =
|
|
||||||
crate::token::lexer("This is another code block.\nyes sirrr.\nmyFunc").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_my_func0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_my_func0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -118,76 +55,14 @@ mod test_examples_my_func {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_my_func1() {
|
async fn serial_test_example_my_func1() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is code.\nIt does other shit.\nmyFunc";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nmyFunc").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_my_func1"),
|
&format!("tests/outputs/{}.png", "serial_test_example_my_func1"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -22,77 +22,14 @@ mod test_examples_line_to {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_line_to0() {
|
async fn serial_test_example_line_to0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is another code block.\nyes sirrr.\nlineTo";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens =
|
|
||||||
crate::token::lexer("This is another code block.\nyes sirrr.\nlineTo").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_line_to0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_line_to0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -118,76 +55,14 @@ mod test_examples_line_to {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_line_to1() {
|
async fn serial_test_example_line_to1() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is code.\nIt does other shit.\nlineTo";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nlineTo").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_line_to1"),
|
&format!("tests/outputs/{}.png", "serial_test_example_line_to1"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,76 +21,14 @@ mod test_examples_min {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_min0() {
|
async fn serial_test_example_min0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is another code block.\nyes sirrr.\nmin";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nmin").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_min0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_min0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -116,76 +54,14 @@ mod test_examples_min {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_min1() {
|
async fn serial_test_example_min1() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is code.\nIt does other shit.\nmin";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nmin").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_min1"),
|
&format!("tests/outputs/{}.png", "serial_test_example_min1"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,76 +21,14 @@ mod test_examples_show {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_show0() {
|
async fn serial_test_example_show0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is code.\nIt does other shit.\nshow";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,76 +21,14 @@ mod test_examples_import {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_import0() {
|
async fn serial_test_example_import0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is code.\nIt does other shit.\nimport";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,76 +21,14 @@ mod test_examples_import {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_import0() {
|
async fn serial_test_example_import0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is code.\nIt does other shit.\nimport";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,76 +21,14 @@ mod test_examples_import {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_import0() {
|
async fn serial_test_example_import0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is code.\nIt does other shit.\nimport";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,76 +21,14 @@ mod test_examples_show {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
async fn serial_test_example_show0() {
|
async fn serial_test_example_show0() {
|
||||||
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
let code = "This is code.\nIt does other shit.\nshow";
|
||||||
let http_client = reqwest::Client::builder()
|
let result =
|
||||||
.user_agent(user_agent)
|
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
.await
|
||||||
.connect_timeout(std::time::Duration::from_secs(60));
|
.unwrap();
|
||||||
let ws_client = reqwest::Client::builder()
|
|
||||||
.user_agent(user_agent)
|
|
||||||
.timeout(std::time::Duration::from_secs(600))
|
|
||||||
.connect_timeout(std::time::Duration::from_secs(60))
|
|
||||||
.connection_verbose(true)
|
|
||||||
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
||||||
.http1_only();
|
|
||||||
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
||||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
||||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
||||||
client.set_base_url(addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
|
|
||||||
let parser = crate::parser::Parser::new(tokens);
|
|
||||||
let program = parser.ast().unwrap();
|
|
||||||
let ctx = crate::executor::ExecutorContext::new(&client, Default::default())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.run(&program, None).await.unwrap();
|
|
||||||
let results = program.lint_all().unwrap();
|
|
||||||
if !results.is_empty() {
|
|
||||||
panic!("Linting failed: {:?}", results);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::ZoomToFit {
|
|
||||||
object_ids: Default::default(),
|
|
||||||
padding: 0.1,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let resp = ctx
|
|
||||||
.engine
|
|
||||||
.send_modeling_cmd(
|
|
||||||
uuid::Uuid::new_v4(),
|
|
||||||
crate::executor::SourceRange::default(),
|
|
||||||
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
||||||
format: kittycad::types::ImageFormat::Png,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let output_file =
|
|
||||||
std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
|
||||||
if let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
||||||
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
||||||
} = &resp
|
|
||||||
{
|
|
||||||
std::fs::write(&output_file, &data.contents.0).unwrap();
|
|
||||||
} else {
|
|
||||||
panic!("Unexpected response from engine: {:?}", resp);
|
|
||||||
}
|
|
||||||
|
|
||||||
let actual = image::io::Reader::open(output_file)
|
|
||||||
.unwrap()
|
|
||||||
.decode()
|
|
||||||
.unwrap();
|
|
||||||
twenty_twenty::assert_image(
|
twenty_twenty::assert_image(
|
||||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||||
&actual,
|
&result,
|
||||||
0.99,
|
0.99,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ async-recursion = "1.1.1"
|
|||||||
async-trait = "0.1.81"
|
async-trait = "0.1.81"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
clap = { version = "4.5.11", default-features = false, optional = true }
|
clap = { version = "4.5.13", default-features = false, optional = true }
|
||||||
convert_case = "0.6.0"
|
convert_case = "0.6.0"
|
||||||
dashmap = "6.0.1"
|
dashmap = "6.0.1"
|
||||||
databake = { version = "0.1.8", features = ["derive"] }
|
databake = { version = "0.1.8", features = ["derive"] }
|
||||||
@ -25,6 +25,7 @@ form_urlencoded = "1.2.1"
|
|||||||
futures = { version = "0.3.30" }
|
futures = { version = "0.3.30" }
|
||||||
git_rev = "0.1.0"
|
git_rev = "0.1.0"
|
||||||
gltf-json = "1.4.1"
|
gltf-json = "1.4.1"
|
||||||
|
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
||||||
kittycad = { workspace = true, features = ["clap"] }
|
kittycad = { workspace = true, features = ["clap"] }
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
mime_guess = "2.0.5"
|
mime_guess = "2.0.5"
|
||||||
@ -38,9 +39,10 @@ serde_json = "1.0.121"
|
|||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
tabled = { version = "0.15.0", optional = true }
|
tabled = { version = "0.15.0", optional = true }
|
||||||
thiserror = "1.0.63"
|
thiserror = "1.0.63"
|
||||||
toml = "0.8.16"
|
toml = "0.8.19"
|
||||||
ts-rs = { version = "9.0.1", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] }
|
ts-rs = { version = "9.0.1", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] }
|
||||||
url = { version = "2.5.2", features = ["serde"] }
|
url = { version = "2.5.2", features = ["serde"] }
|
||||||
|
urlencoding = "2.1.3"
|
||||||
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.10.0", features = ["v4", "js", "serde"] }
|
||||||
validator = { version = "0.18.1", features = ["derive"] }
|
validator = { version = "0.18.1", features = ["derive"] }
|
||||||
winnow = "0.5.40"
|
winnow = "0.5.40"
|
||||||
|
@ -1494,9 +1494,14 @@ impl CallExpression {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
let result = result.ok_or_else(|| {
|
let result = result.ok_or_else(|| {
|
||||||
|
let mut source_ranges: Vec<SourceRange> = vec![self.into()];
|
||||||
|
// We want to send the source range of the original function.
|
||||||
|
if let MemoryItem::Function { meta, .. } = func {
|
||||||
|
source_ranges = meta.iter().map(|m| m.source_range).collect();
|
||||||
|
};
|
||||||
KclError::UndefinedValue(KclErrorDetails {
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
message: format!("Result of user-defined function {} is undefined", fn_name),
|
message: format!("Result of user-defined function {} is undefined", fn_name),
|
||||||
source_ranges: vec![self.into()],
|
source_ranges,
|
||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
let result = result.get_value()?;
|
let result = result.get_value()?;
|
||||||
@ -3989,7 +3994,7 @@ mod tests {
|
|||||||
|> startProfileAt([0.0000000000, 5.0000000000], %)
|
|> startProfileAt([0.0000000000, 5.0000000000], %)
|
||||||
|> line([0.4900857016, -0.0240763666], %)
|
|> line([0.4900857016, -0.0240763666], %)
|
||||||
|
|
||||||
startSketchOn('XY')
|
let s1 = startSketchOn('XY')
|
||||||
|> startProfileAt([0.0000000000, 5.0000000000], %)
|
|> startProfileAt([0.0000000000, 5.0000000000], %)
|
||||||
|> line([0.4900857016, -0.0240763666], %)
|
|> line([0.4900857016, -0.0240763666], %)
|
||||||
|
|
||||||
@ -4020,7 +4025,7 @@ ghi("things")
|
|||||||
assert_eq!(folding_ranges[1].end_line, 254);
|
assert_eq!(folding_ranges[1].end_line, 254);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
folding_ranges[1].collapsed_text,
|
folding_ranges[1].collapsed_text,
|
||||||
Some("startSketchOn('XY')".to_string())
|
Some("let s1 = startSketchOn('XY')".to_string())
|
||||||
);
|
);
|
||||||
assert_eq!(folding_ranges[2].start_line, 390);
|
assert_eq!(folding_ranges[2].start_line, 390);
|
||||||
assert_eq!(folding_ranges[2].end_line, 403);
|
assert_eq!(folding_ranges[2].end_line, 403);
|
||||||
@ -5259,7 +5264,7 @@ fn ghi = (part001) => {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_trailing_comma() {
|
fn test_recast_trailing_comma() {
|
||||||
let some_program_string = r#"startSketchOn('XY')
|
let some_program_string = r#"let s = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> arc({
|
|> arc({
|
||||||
radius: 1,
|
radius: 1,
|
||||||
@ -5273,7 +5278,7 @@ fn ghi = (part001) => {
|
|||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recasted,
|
recasted,
|
||||||
r#"startSketchOn('XY')
|
r#"let s = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> arc({
|
|> arc({
|
||||||
radius: 1,
|
radius: 1,
|
||||||
@ -5853,7 +5858,7 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_parse_tag_named_std_lib() {
|
async fn test_parse_tag_named_std_lib() {
|
||||||
let some_program_string = r#"startSketchOn('XY')
|
let some_program_string = r#"let s = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([5, 5], %, $xLine)
|
|> line([5, 5], %, $xLine)
|
||||||
"#;
|
"#;
|
||||||
@ -5864,13 +5869,13 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().to_string(),
|
result.unwrap_err().to_string(),
|
||||||
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([76, 82])], message: "Cannot assign a tag to a reserved keyword: xLine" }"#
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([84, 90])], message: "Cannot assign a tag to a reserved keyword: xLine" }"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_parse_empty_tag() {
|
async fn test_parse_empty_tag() {
|
||||||
let some_program_string = r#"startSketchOn('XY')
|
let some_program_string = r#"let s = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([5, 5], %, $)
|
|> line([5, 5], %, $)
|
||||||
"#;
|
"#;
|
||||||
@ -5881,13 +5886,13 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err().to_string(),
|
result.unwrap_err().to_string(),
|
||||||
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([57, 59])], message: "Unexpected token" }"#
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([65, 67])], message: "Unexpected token" }"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_parse_digest() {
|
async fn test_parse_digest() {
|
||||||
let prog1_string = r#"startSketchOn('XY')
|
let prog1_string = r#"let s = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([5, 5], %)
|
|> line([5, 5], %)
|
||||||
"#;
|
"#;
|
||||||
@ -5895,7 +5900,7 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
|
|||||||
let prog1_parser = crate::parser::Parser::new(prog1_tokens);
|
let prog1_parser = crate::parser::Parser::new(prog1_tokens);
|
||||||
let prog1_digest = prog1_parser.ast().unwrap().compute_digest();
|
let prog1_digest = prog1_parser.ast().unwrap().compute_digest();
|
||||||
|
|
||||||
let prog2_string = r#"startSketchOn('XY')
|
let prog2_string = r#"let s = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 2], %)
|
|> startProfileAt([0, 2], %)
|
||||||
|> line([5, 5], %)
|
|> line([5, 5], %)
|
||||||
"#;
|
"#;
|
||||||
@ -5905,7 +5910,7 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
|
|||||||
|
|
||||||
assert!(prog1_digest != prog2_digest);
|
assert!(prog1_digest != prog2_digest);
|
||||||
|
|
||||||
let prog3_string = r#"startSketchOn('XY')
|
let prog3_string = r#"let s = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([5, 5], %)
|
|> line([5, 5], %)
|
||||||
"#;
|
"#;
|
||||||
|
@ -2086,6 +2086,20 @@ const newVar = myVar + 1"#;
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_top_level_pipe_without_variable() {
|
||||||
|
let ast = r#"startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> lineTo([2, 2], %, $yo)
|
||||||
|
"#;
|
||||||
|
let result = parse_execute(ast).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 78])], message: "A top-level pipe expression must be assigned to a new variable declaration" }"#.to_owned()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_execute_angled_line_that_intersects() {
|
async fn test_execute_angled_line_that_intersects() {
|
||||||
let ast_fn = |offset: &str| -> String {
|
let ast_fn = |offset: &str| -> String {
|
||||||
@ -2735,6 +2749,22 @@ const bracket = startSketchOn('XY')
|
|||||||
parse_execute(ast).await.unwrap();
|
parse_execute(ast).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_execute_function_no_return() {
|
||||||
|
let ast = r#"fn test = (origin) => {
|
||||||
|
origin
|
||||||
|
}
|
||||||
|
|
||||||
|
test([0, 0])
|
||||||
|
"#;
|
||||||
|
let result = parse_execute(ast).await;
|
||||||
|
assert!(result.is_err());
|
||||||
|
assert_eq!(
|
||||||
|
result.unwrap_err().to_string(),
|
||||||
|
r#"undefined value: KclErrorDetails { source_ranges: [SourceRange([10, 34])], message: "Result of user-defined function test is undefined" }"#.to_owned()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_math_doubly_nested_parens() {
|
async fn test_math_doubly_nested_parens() {
|
||||||
let ast = r#"const sigmaAllow = 35000 // psi
|
let ast = r#"const sigmaAllow = 35000 // psi
|
||||||
|
@ -26,6 +26,7 @@ pub mod lsp;
|
|||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
pub mod std;
|
pub mod std;
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub mod test_server;
|
pub mod test_server;
|
||||||
pub mod thread;
|
pub mod thread;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
|
@ -1354,7 +1354,7 @@ async fn test_kcl_lsp_formatting() {
|
|||||||
uri: "file:///test.kcl".try_into().unwrap(),
|
uri: "file:///test.kcl".try_into().unwrap(),
|
||||||
language_id: "kcl".to_string(),
|
language_id: "kcl".to_string(),
|
||||||
version: 1,
|
version: 1,
|
||||||
text: r#"startSketchOn('XY')
|
text: r#"let s = startSketchOn('XY')
|
||||||
|> startProfileAt([0,0], %)"#
|
|> startProfileAt([0,0], %)"#
|
||||||
.to_string(),
|
.to_string(),
|
||||||
},
|
},
|
||||||
@ -1385,7 +1385,7 @@ async fn test_kcl_lsp_formatting() {
|
|||||||
assert_eq!(formatting.len(), 1);
|
assert_eq!(formatting.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
formatting[0].new_text,
|
formatting[0].new_text,
|
||||||
r#"startSketchOn('XY')
|
r#"let s = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)"#
|
|> startProfileAt([0, 0], %)"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2901,7 +2901,7 @@ async fn test_kcl_lsp_folding() {
|
|||||||
uri: "file:///test.kcl".try_into().unwrap(),
|
uri: "file:///test.kcl".try_into().unwrap(),
|
||||||
language_id: "kcl".to_string(),
|
language_id: "kcl".to_string(),
|
||||||
version: 1,
|
version: 1,
|
||||||
text: r#"startSketchOn('XY')
|
text: r#"let s = startSketchOn('XY')
|
||||||
|> startProfileAt([0,0], %)"#
|
|> startProfileAt([0,0], %)"#
|
||||||
.to_string(),
|
.to_string(),
|
||||||
},
|
},
|
||||||
@ -2926,12 +2926,12 @@ async fn test_kcl_lsp_folding() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
folding.first().unwrap().clone(),
|
folding.first().unwrap().clone(),
|
||||||
tower_lsp::lsp_types::FoldingRange {
|
tower_lsp::lsp_types::FoldingRange {
|
||||||
start_line: 19,
|
start_line: 27,
|
||||||
start_character: None,
|
start_character: None,
|
||||||
end_line: 67,
|
end_line: 75,
|
||||||
end_character: None,
|
end_character: None,
|
||||||
kind: Some(tower_lsp::lsp_types::FoldingRangeKind::Region),
|
kind: Some(tower_lsp::lsp_types::FoldingRangeKind::Region),
|
||||||
collapsed_text: Some("startSketchOn('XY')".to_string())
|
collapsed_text: Some("let s = startSketchOn('XY')".to_string())
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,37 @@ fn program(i: TokenSlice) -> PResult<Program> {
|
|||||||
// Once this is merged and stable, consider changing this as I think it's more accurate
|
// Once this is merged and stable, consider changing this as I think it's more accurate
|
||||||
// without the -1.
|
// without the -1.
|
||||||
out.end -= 1;
|
out.end -= 1;
|
||||||
|
|
||||||
|
// Prevent top-level pipe expressions without a variable declaration, giving
|
||||||
|
// a good error message that will help users fix their code. This is a
|
||||||
|
// band-aid until we can use the artifact graph.
|
||||||
|
let source_ranges = out
|
||||||
|
.body
|
||||||
|
.iter()
|
||||||
|
.filter_map(|item| {
|
||||||
|
if let BodyItem::ExpressionStatement(ExpressionStatement {
|
||||||
|
expression: Value::PipeExpression(_),
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
..
|
||||||
|
}) = item
|
||||||
|
{
|
||||||
|
Some(SourceRange([*start, *end]))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
if !source_ranges.is_empty() {
|
||||||
|
return Err(ErrMode::Cut(
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges,
|
||||||
|
message: "A top-level pipe expression must be assigned to a new variable declaration".to_owned(),
|
||||||
|
})
|
||||||
|
.into(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,13 +24,16 @@ impl ProjectState {
|
|||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub async fn new_from_path(path: PathBuf) -> Result<ProjectState> {
|
pub async fn new_from_path(path: PathBuf) -> Result<ProjectState> {
|
||||||
// Fix for "." path, which is the current directory.
|
// Fix for "." path, which is the current directory.
|
||||||
|
|
||||||
let source_path = if path == Path::new(".") {
|
let source_path = if path == Path::new(".") {
|
||||||
std::env::current_dir().map_err(|e| anyhow::anyhow!("Error getting the current directory: {:?}", e))?
|
std::env::current_dir().map_err(|e| anyhow::anyhow!("Error getting the current directory: {:?}", e))?
|
||||||
} else {
|
} else {
|
||||||
path
|
path
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Url decode the path.
|
||||||
|
let source_path =
|
||||||
|
std::path::Path::new(&urlencoding::decode(&source_path.display().to_string())?.to_string()).to_path_buf();
|
||||||
|
|
||||||
// If the path does not start with a slash, it is a relative path.
|
// If the path does not start with a slash, it is a relative path.
|
||||||
// We need to convert it to an absolute path.
|
// We need to convert it to an absolute path.
|
||||||
let source_path = if source_path.is_relative() {
|
let source_path = if source_path.is_relative() {
|
||||||
@ -1086,4 +1089,54 @@ const model = import("model.obj")"#
|
|||||||
|
|
||||||
std::fs::remove_dir_all(tmp_project_dir).unwrap();
|
std::fs::remove_dir_all(tmp_project_dir).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_project_state_new_from_path_explicit_open_file_with_space_kcl() {
|
||||||
|
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
|
||||||
|
let tmp_project_dir = std::env::temp_dir().join(&name);
|
||||||
|
std::fs::create_dir_all(&tmp_project_dir).unwrap();
|
||||||
|
std::fs::write(tmp_project_dir.join("i have a space.kcl"), vec![]).unwrap();
|
||||||
|
|
||||||
|
let state = super::ProjectState::new_from_path(tmp_project_dir.join("i have a space.kcl"))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(state.project.file.name, name);
|
||||||
|
assert_eq!(state.project.file.path, tmp_project_dir.display().to_string());
|
||||||
|
assert_eq!(
|
||||||
|
state.current_file,
|
||||||
|
Some(tmp_project_dir.join("i have a space.kcl").display().to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.project.default_file,
|
||||||
|
tmp_project_dir.join("i have a space.kcl").display().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(tmp_project_dir).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_project_state_new_from_path_explicit_open_file_with_space_kcl_url_encoded() {
|
||||||
|
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
|
||||||
|
let tmp_project_dir = std::env::temp_dir().join(&name);
|
||||||
|
std::fs::create_dir_all(&tmp_project_dir).unwrap();
|
||||||
|
std::fs::write(tmp_project_dir.join("i have a space.kcl"), vec![]).unwrap();
|
||||||
|
|
||||||
|
let state = super::ProjectState::new_from_path(tmp_project_dir.join("i%20have%20a%20space.kcl"))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(state.project.file.name, name);
|
||||||
|
assert_eq!(state.project.file.path, tmp_project_dir.display().to_string());
|
||||||
|
assert_eq!(
|
||||||
|
state.current_file,
|
||||||
|
Some(tmp_project_dir.join("i have a space.kcl").display().to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
state.project.default_file,
|
||||||
|
tmp_project_dir.join("i have a space.kcl").display().to_string()
|
||||||
|
);
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(tmp_project_dir).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1395,7 +1395,7 @@ pub async fn close(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
/// Close the current sketch.
|
/// Close the current sketch.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// startSketchOn('XZ')
|
/// const exampleSketch = startSketchOn('XZ')
|
||||||
/// |> startProfileAt([0, 0], %)
|
/// |> startProfileAt([0, 0], %)
|
||||||
/// |> line([10, 10], %)
|
/// |> line([10, 10], %)
|
||||||
/// |> line([10, 0], %)
|
/// |> line([10, 0], %)
|
||||||
|
@ -1,8 +1,70 @@
|
|||||||
//! Types used to send data to the test server.
|
//! Types used to send data to the test server.
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
executor::{ExecutorContext, ExecutorSettings},
|
||||||
|
settings::types::UnitLength,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(serde::Deserialize, serde::Serialize)]
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct RequestBody {
|
pub struct RequestBody {
|
||||||
pub kcl_program: String,
|
pub kcl_program: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub test_name: String,
|
pub test_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Executes a kcl program and takes a snapshot of the result.
|
||||||
|
/// This returns the bytes of the snapshot.
|
||||||
|
pub async fn execute_and_snapshot(code: &str, units: UnitLength) -> anyhow::Result<image::DynamicImage> {
|
||||||
|
let ctx = new_context(units).await?;
|
||||||
|
let tokens = crate::token::lexer(code)?;
|
||||||
|
let parser = crate::parser::Parser::new(tokens);
|
||||||
|
let program = parser.ast()?;
|
||||||
|
|
||||||
|
let snapshot = ctx.execute_and_prepare_snapshot(&program).await?;
|
||||||
|
|
||||||
|
// Create a temporary file to write the output to.
|
||||||
|
let output_file = std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
|
||||||
|
// Save the snapshot locally, to that temporary file.
|
||||||
|
std::fs::write(&output_file, snapshot.contents.0)?;
|
||||||
|
// Decode the snapshot, return it.
|
||||||
|
let img = image::io::Reader::open(output_file).unwrap().decode()?;
|
||||||
|
Ok(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn new_context(units: UnitLength) -> anyhow::Result<ExecutorContext> {
|
||||||
|
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
||||||
|
let http_client = reqwest::Client::builder()
|
||||||
|
.user_agent(user_agent)
|
||||||
|
// For file conversions we need this to be long.
|
||||||
|
.timeout(std::time::Duration::from_secs(600))
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(60));
|
||||||
|
let ws_client = reqwest::Client::builder()
|
||||||
|
.user_agent(user_agent)
|
||||||
|
// For file conversions we need this to be long.
|
||||||
|
.timeout(std::time::Duration::from_secs(600))
|
||||||
|
.connect_timeout(std::time::Duration::from_secs(60))
|
||||||
|
.connection_verbose(true)
|
||||||
|
.tcp_keepalive(std::time::Duration::from_secs(600))
|
||||||
|
.http1_only();
|
||||||
|
|
||||||
|
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
||||||
|
|
||||||
|
// Create the client.
|
||||||
|
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
||||||
|
// Set a local engine address if it's set.
|
||||||
|
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
||||||
|
client.set_base_url(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
let ctx = ExecutorContext::new(
|
||||||
|
&client,
|
||||||
|
ExecutorSettings {
|
||||||
|
units,
|
||||||
|
highlight_edges: true,
|
||||||
|
enable_ssao: false,
|
||||||
|
show_grid: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(ctx)
|
||||||
|
}
|
||||||
|
9
src/wasm-lib/tests/executor/inputs/angled_line.kcl
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([4.83, 12.56], %)
|
||||||
|
|> line([15.1, 2.48], %)
|
||||||
|
|> line([3.15, -9.85], %, $seg01)
|
||||||
|
|> line([-15.17, -4.1], %)
|
||||||
|
|> angledLine([segAng(seg01), 12.35], %)
|
||||||
|
|> line([-13.02, 10.03], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(4, %)
|
9
src/wasm-lib/tests/executor/inputs/close_arc.kcl
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const center = [0,0]
|
||||||
|
const radius = 40
|
||||||
|
const height = 3
|
||||||
|
|
||||||
|
const body = startSketchOn('XY')
|
||||||
|
|> startProfileAt([center[0]+radius, center[1]], %)
|
||||||
|
|> arc({angle_end: 360, angle_start: 0, radius: radius}, %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(height, %)
|
7
src/wasm-lib/tests/executor/inputs/dimensions_match.kcl
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(10, %)
|
4
src/wasm-lib/tests/executor/inputs/helix_ccw.kcl
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> circle([5, 5], 10, %)
|
||||||
|
|> extrude(10, %)
|
||||||
|
|> helix({revolutions: 16, angle_start: 0, ccw: true}, %)
|
4
src/wasm-lib/tests/executor/inputs/helix_defaults.kcl
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> circle([5, 5], 10, %)
|
||||||
|
|> extrude(10, %)
|
||||||
|
|> helix({revolutions: 16, angle_start: 0}, %)
|
@ -0,0 +1,4 @@
|
|||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> circle([5, 5], 10, %)
|
||||||
|
|> extrude(-10, %)
|
||||||
|
|> helix({revolutions: 16, angle_start: 0}, %)
|
4
src/wasm-lib/tests/executor/inputs/helix_with_length.kcl
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> circle([5, 5], 10, %)
|
||||||
|
|> extrude(10, %)
|
||||||
|
|> helix({revolutions: 16, angle_start: 0, length: 3}, %)
|
@ -0,0 +1,17 @@
|
|||||||
|
fn cube = (pos, scale) => {
|
||||||
|
const sg = startSketchOn('XY')
|
||||||
|
|> startProfileAt(pos, %)
|
||||||
|
|> line([0, scale], %)
|
||||||
|
|> line([scale, 0], %)
|
||||||
|
|> line([0, -scale], %)
|
||||||
|
|> close(%)
|
||||||
|
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
const b1 = cube([0,0], 10)
|
||||||
|
const b2 = cube([3,3], 4)
|
||||||
|
|> extrude(10, %)
|
||||||
|
|
||||||
|
const pt1 = b1.value[0]
|
||||||
|
const pt2 = b2.value[0]
|
19
src/wasm-lib/tests/executor/inputs/negative_args.kcl
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
const width = 5
|
||||||
|
const height = 10
|
||||||
|
const length = 12
|
||||||
|
|
||||||
|
fn box = (sk1, sk2, scale) => {
|
||||||
|
const boxSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([sk1, sk2], %)
|
||||||
|
|> line([0, scale], %)
|
||||||
|
|> line([scale, 0], %)
|
||||||
|
|> line([0, -scale], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(scale, %)
|
||||||
|
return boxSketch
|
||||||
|
}
|
||||||
|
|
||||||
|
box(0, 0, 5)
|
||||||
|
box(10, 23, 8)
|
||||||
|
let thing = box(-12, -15, 10)
|
||||||
|
box(-20, -5, 10)
|
18
src/wasm-lib/tests/executor/inputs/parametric.kcl
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const sigmaAllow = 35000 // psi
|
||||||
|
const width = 9 // inch
|
||||||
|
const p = 150 // Force on shelf - lbs
|
||||||
|
const distance = 6 // inches
|
||||||
|
const FOS = 2
|
||||||
|
|
||||||
|
const leg1 = 5 // inches
|
||||||
|
const leg2 = 8 // inches
|
||||||
|
const thickness = sqrt(distance * p * FOS * 6 / sigmaAllow / width) // inches
|
||||||
|
const bracket = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([0, leg1], %)
|
||||||
|
|> line([leg2, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([-leg2 + thickness, 0], %)
|
||||||
|
|> line([0, -leg1 + thickness], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(width, %)
|
@ -0,0 +1,26 @@
|
|||||||
|
const sigmaAllow = 15000 // psi
|
||||||
|
const width = 11 // inch
|
||||||
|
const p = 150 // Force on shelf - lbs
|
||||||
|
const distance = 12 // inches
|
||||||
|
const FOS = 2
|
||||||
|
const thickness = sqrt(distance * p * FOS * 6 / ( sigmaAllow * width ))
|
||||||
|
const filletR = thickness * 2
|
||||||
|
const shelfMountL = 9
|
||||||
|
const wallMountL = 8
|
||||||
|
|
||||||
|
const bracket = startSketchAt([0, 0])
|
||||||
|
|> line([0, wallMountL], %)
|
||||||
|
|> tangentialArc({
|
||||||
|
radius: filletR,
|
||||||
|
offset: 90
|
||||||
|
}, %)
|
||||||
|
|> line([-shelfMountL, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([shelfMountL, 0], %)
|
||||||
|
|> tangentialArc({
|
||||||
|
radius: filletR - thickness,
|
||||||
|
offset: -90
|
||||||
|
}, %)
|
||||||
|
|> line([0, -wallMountL], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(width, %)
|