Compare commits
47 Commits
Author | SHA1 | Date | |
---|---|---|---|
239ab6850e | |||
4a7dd6e650 | |||
af2609e678 | |||
30909dedda | |||
39d76ed54f | |||
4925251c29 | |||
9772869545 | |||
a7e830cd02 | |||
ca102116b6 | |||
c2fba89e77 | |||
7e31678ba2 | |||
1140ced121 | |||
32b7ddaa7c | |||
2525f99515 | |||
4b8ce34b31 | |||
6617f72373 | |||
e9033e1754 | |||
9b697e30cf | |||
a70facdab4 | |||
4083f9f3dd | |||
7ead2bb875 | |||
19d01c563e | |||
dfe7cfc91c | |||
01443e445d | |||
e16eb49f51 | |||
5d5138e8e6 | |||
e1d6e29523 | |||
49657ad2e5 | |||
b40d353994 | |||
62ffa53add | |||
64dce4d8b1 | |||
02588b2672 | |||
3d1ac2ac0b | |||
ff5ce29fd7 | |||
4bd7e02271 | |||
26042790b6 | |||
af74f3bb05 | |||
0bdedf5854 | |||
d2c6b5cf3a | |||
c42967d0e7 | |||
cb8fc33adb | |||
2dc8b429ff | |||
19ffa220e8 | |||
5332ddd88e | |||
11d9a2ee00 | |||
bfebc41a5c | |||
824b4c823e |
10
.github/workflows/build-test-publish-apps.yml
vendored
@ -15,6 +15,7 @@ on:
|
||||
env:
|
||||
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
|
||||
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
@ -25,7 +26,6 @@ jobs:
|
||||
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
|
||||
outputs:
|
||||
version: ${{ steps.export_version.outputs.version }}
|
||||
notes: ${{ steps.export_version.outputs.notes }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@ -55,8 +55,6 @@ jobs:
|
||||
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json
|
||||
|
||||
- name: Generate release notes
|
||||
env:
|
||||
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
|
||||
run: |
|
||||
echo "$NOTES" > release-notes.md
|
||||
cat release-notes.md
|
||||
@ -84,9 +82,6 @@ jobs:
|
||||
path: |
|
||||
electron-builder.yml
|
||||
|
||||
- id: export_notes
|
||||
run: echo "notes=`cat release-notes.md'`" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Prepare electron-builder.yml file for updater test
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
run: |
|
||||
@ -211,7 +206,7 @@ jobs:
|
||||
out/*-x86_64-linux.*
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: ${{ env.CUT_RELEASE_PR == 'true' }}
|
||||
if: ${{ env.BUILD_RELEASE == 'true' }}
|
||||
with:
|
||||
name: out-yml
|
||||
path: |
|
||||
@ -262,7 +257,6 @@ jobs:
|
||||
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
|
||||
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
|
||||
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
|
||||
NOTES: ${{ needs.prepare-files.outputs.notes }}
|
||||
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
|
||||
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
|
||||
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}
|
||||
|
2
.github/workflows/cargo-test.yml
vendored
@ -62,7 +62,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |-
|
||||
cd "${{ matrix.dir }}"
|
||||
cargo llvm-cov nextest --all --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
|
||||
cargo llvm-cov nextest --workspace --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
|
||||
env:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||
RUST_MIN_STACK: 10485760000
|
||||
|
38010
docs/kcl/std.json
@ -162,6 +162,28 @@ A base path.
|
||||
|
||||
|
||||
----
|
||||
A circular arc, not necessarily tangential to the current point.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Arc`| | No |
|
||||
| `center` |`[number, number]`| Center of the circle that this arc is drawn on. | No |
|
||||
| `radius` |`number`| Radius of the circle that this arc is drawn on. | No |
|
||||
| `from` |`[number, number]`| The from point. | No |
|
||||
| `to` |`[number, number]`| The to point. | No |
|
||||
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
|
||||
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -16,8 +16,8 @@ A sketch is a collection of paths.
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No |
|
||||
| `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
||||
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No |
|
||||
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
||||
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||
|
@ -25,8 +25,8 @@ A sketch is a collection of paths.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `sketch`| | No |
|
||||
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No |
|
||||
| `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
||||
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No |
|
||||
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
||||
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||
|
@ -18,7 +18,7 @@ Engine information for a tag.
|
||||
|----------|------|-------------|----------|
|
||||
| `id` |`string`| The id of the tagged object. | No |
|
||||
| `sketch` |`string`| The sketch the tag is on. | No |
|
||||
| `path` |[`BasePath`](/docs/kcl/types/BasePath)| The path the tag is on. | No |
|
||||
| `path` |[`Path`](/docs/kcl/types/Path)| The path the tag is on. | No |
|
||||
| `surface` |[`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface)| The surface information for the tag. | No |
|
||||
|
||||
|
||||
|
@ -104,7 +104,7 @@ test(
|
||||
},
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBe(431341)
|
||||
.toBeGreaterThan(300_000)
|
||||
|
||||
// clean up output.gltf
|
||||
await fsp.rm('output.gltf')
|
||||
@ -179,7 +179,7 @@ test(
|
||||
},
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBe(102040)
|
||||
.toBeGreaterThan(100_000)
|
||||
|
||||
// clean up output.gltf
|
||||
await fsp.rm('output.gltf')
|
||||
|
@ -1,6 +1,16 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import fsp from 'fs/promises'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { getUtils, setup, tearDown } from './test-utils'
|
||||
import {
|
||||
darkModeBgColor,
|
||||
darkModePlaneColorXZ,
|
||||
executorInputPath,
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
} from './test-utils'
|
||||
import { join } from 'path'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
@ -974,4 +984,84 @@ test.describe('Editor tests', () => {
|
||||
|> close(%)
|
||||
|> extrude(5, %)`)
|
||||
})
|
||||
|
||||
test(
|
||||
`Can use the import stdlib function on a local OBJ file`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'cube')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cube.obj'),
|
||||
join(bracketDir, 'cube.obj')
|
||||
)
|
||||
await fsp.writeFile(join(bracketDir, 'main.kcl'), '')
|
||||
},
|
||||
})
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewportSize)
|
||||
|
||||
// Locators and constants
|
||||
const u = await getUtils(page)
|
||||
const projectLink = page.getByRole('link', { name: 'cube' })
|
||||
const gizmo = page.locator('[aria-label*=gizmo]')
|
||||
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
|
||||
const locationToHavColor = async (
|
||||
position: { x: number; y: number },
|
||||
color: [number, number, number]
|
||||
) => {
|
||||
return u.getGreatestPixDiff(position, color)
|
||||
}
|
||||
const notTheOrigin = {
|
||||
x: viewportSize.width * 0.55,
|
||||
y: viewportSize.height * 0.3,
|
||||
}
|
||||
const origin = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||
const errorIndicators = page.locator('.cm-lint-marker-error')
|
||||
|
||||
await test.step(`Open the empty file, see the default planes`, async () => {
|
||||
await projectLink.click()
|
||||
await u.waitForPageLoad()
|
||||
await expect
|
||||
.poll(
|
||||
async () => locationToHavColor(notTheOrigin, darkModePlaneColorXZ),
|
||||
{
|
||||
timeout: 5000,
|
||||
message: 'XZ plane color is visible',
|
||||
}
|
||||
)
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
await test.step(`Write the import function line`, async () => {
|
||||
await u.codeLocator.fill(`import('cube.obj')`)
|
||||
await page.waitForTimeout(800)
|
||||
})
|
||||
await test.step(`Reset the camera before checking`, async () => {
|
||||
await u.doAndWaitForCmd(async () => {
|
||||
await gizmo.click({ button: 'right' })
|
||||
await resetCameraButton.click()
|
||||
}, 'zoom_to_fit')
|
||||
})
|
||||
await test.step(`Verify that we see the imported geometry and no errors`, async () => {
|
||||
await expect(errorIndicators).toHaveCount(0)
|
||||
await expect
|
||||
.poll(async () => locationToHavColor(origin, darkModePlaneColorXZ), {
|
||||
timeout: 3000,
|
||||
message: 'Plane color should not be visible',
|
||||
})
|
||||
.toBeGreaterThan(15)
|
||||
await expect
|
||||
.poll(async () => locationToHavColor(origin, darkModeBgColor), {
|
||||
timeout: 3000,
|
||||
message: 'Background color should not be visible',
|
||||
})
|
||||
.toBeGreaterThan(15)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -136,6 +136,9 @@ test.describe('when using the file tree to', () => {
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
|
||||
await tronApp.page.waitForTimeout(2000)
|
||||
|
||||
await renameFile(fromFile, toFile)
|
||||
await tronApp.page.reload()
|
||||
|
||||
@ -222,9 +225,11 @@ test.describe('when using the file tree to', () => {
|
||||
)
|
||||
await pasteCodeInEditor(kclCube)
|
||||
|
||||
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
|
||||
await tronApp.page.waitForTimeout(2000)
|
||||
|
||||
const kcl1 = 'main.kcl'
|
||||
const kcl2 = '2.kcl'
|
||||
|
||||
await createNewFileAndSelect(kcl2)
|
||||
const kclCylinder = await fsp.readFile(
|
||||
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
|
||||
@ -232,6 +237,9 @@ test.describe('when using the file tree to', () => {
|
||||
)
|
||||
await pasteCodeInEditor(kclCylinder)
|
||||
|
||||
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
|
||||
await tronApp.page.waitForTimeout(2000)
|
||||
|
||||
await renameFile(kcl2, kcl1)
|
||||
|
||||
await test.step(`Postcondition: ${kcl1} still has the original content`, async () => {
|
||||
|
@ -55,6 +55,53 @@ test.describe('Onboarding tests', () => {
|
||||
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
|
||||
})
|
||||
|
||||
test(
|
||||
'Desktop: fresh onboarding executes and loads',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName: _ }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
appSettings: {
|
||||
app: {
|
||||
onboardingStatus: 'incomplete',
|
||||
},
|
||||
},
|
||||
cleanProjectDir: true,
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setViewportSize(viewportSize)
|
||||
|
||||
// Locators and constants
|
||||
const newProjectButton = page.getByRole('button', { name: 'New project' })
|
||||
const projectLink = page.getByTestId('project-link')
|
||||
|
||||
await test.step(`Create a project and open to the onboarding`, async () => {
|
||||
await newProjectButton.click()
|
||||
await projectLink.click()
|
||||
await test.step(`Ensure the engine connection works by testing the sketch button`, async () => {
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
})
|
||||
|
||||
await test.step(`Ensure we see the onboarding stuff`, async () => {
|
||||
// Test that the onboarding pane loaded
|
||||
await expect(
|
||||
page.getByText('Welcome to Modeling App! This')
|
||||
).toBeVisible()
|
||||
|
||||
// *and* that the code is shown in the editor
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
'// Shelf Bracket'
|
||||
)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test('Code resets after confirmation', async ({ page }) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')`
|
||||
|
||||
|
@ -255,7 +255,7 @@ test.describe('Can export from electron app', () => {
|
||||
},
|
||||
{ timeout: 15_000 }
|
||||
)
|
||||
.toBe(431341)
|
||||
.toBeGreaterThan(300_000)
|
||||
|
||||
// clean up output.gltf
|
||||
await fsp.rm('output.gltf')
|
||||
@ -851,7 +851,7 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
test.fixme(
|
||||
'When the project folder is empty, user can create new project and open it.',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
@ -861,6 +861,12 @@ test(
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
// Locators and constants
|
||||
const gizmo = page.locator('[aria-label*=gizmo]')
|
||||
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
|
||||
const pointOnModel = { x: 660, y: 250 }
|
||||
const expectedStartCamZPosition = 15633.47
|
||||
|
||||
// expect to see text "No Projects found"
|
||||
await expect(page.getByText('No Projects found')).toBeVisible()
|
||||
|
||||
@ -873,16 +879,7 @@ test(
|
||||
|
||||
await page.getByText('project-000').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await page.locator('.cm-content').fill(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-87.4, 282.92], %)
|
||||
@ -892,8 +889,28 @@ test(
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(200, sketch001)`)
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
const pointOnModel = { x: 660, y: 250 }
|
||||
async function getCameraZValue() {
|
||||
return page
|
||||
.getByTestId('cam-z-position')
|
||||
.inputValue()
|
||||
.then((value) => parseFloat(value))
|
||||
}
|
||||
|
||||
await test.step(`Reset camera`, async () => {
|
||||
await u.openDebugPanel()
|
||||
await u.clearCommandLogs()
|
||||
await u.doAndWaitForCmd(async () => {
|
||||
await gizmo.click({ button: 'right' })
|
||||
await resetCameraButton.click()
|
||||
}, 'zoom_to_fit')
|
||||
await expect
|
||||
.poll(getCameraZValue, {
|
||||
message: 'Camera Z should be at expected position after reset',
|
||||
})
|
||||
.toEqual(expectedStartCamZPosition)
|
||||
})
|
||||
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
@ -901,7 +918,7 @@ extrude001 = extrude(200, sketch001)`)
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [143, 143, 143]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
.toBeLessThan(30)
|
||||
|
||||
await expect(async () => {
|
||||
await page.mouse.move(0, 0, { steps: 5 })
|
||||
|
@ -471,7 +471,7 @@ test(
|
||||
|
||||
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
|
||||
|
||||
await page.waitForTimeout(300)
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
@ -528,6 +528,7 @@ test(
|
||||
// Draw the rectangle
|
||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 30)
|
||||
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 10, { steps: 5 })
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
// Ensure the draft rectangle looks the same as it usually does
|
||||
await expect(page).toHaveScreenshot({
|
||||
@ -895,7 +896,7 @@ test(
|
||||
// Wait for the second extrusion to appear
|
||||
// TODO: Find a way to truly know that the objects have finished
|
||||
// rendering, because an execution-done message is not sufficient.
|
||||
await page.waitForTimeout(1000)
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
@ -939,7 +940,7 @@ test(
|
||||
// Wait for the second extrusion to appear
|
||||
// TODO: Find a way to truly know that the objects have finished
|
||||
// rendering, because an execution-done message is not sufficient.
|
||||
await page.waitForTimeout(1000)
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
@ -47,6 +47,14 @@ export const commonPoints = {
|
||||
num2: 14.44,
|
||||
}
|
||||
|
||||
/** A semi-reliable color to check the default XZ plane on
|
||||
* in dark mode in the default camera position
|
||||
*/
|
||||
export const darkModePlaneColorXZ: [number, number, number] = [50, 50, 99]
|
||||
|
||||
/** A semi-reliable color to check the default dark mode bg color against */
|
||||
export const darkModeBgColor: [number, number, number] = [27, 27, 27]
|
||||
|
||||
export const editorSelector = '[role="textbox"][data-language="kcl"]'
|
||||
type PaneId = 'variables' | 'code' | 'files' | 'logs'
|
||||
|
||||
@ -880,10 +888,20 @@ export async function setupElectron({
|
||||
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
|
||||
const settingsOverrides = TOML.stringify(
|
||||
appSettings
|
||||
? { settings: appSettings }
|
||||
: {
|
||||
...TEST_SETTINGS,
|
||||
? {
|
||||
settings: {
|
||||
...TEST_SETTINGS,
|
||||
...appSettings,
|
||||
app: {
|
||||
...TEST_SETTINGS.app,
|
||||
projectDirectory: projectDirName,
|
||||
...appSettings.app,
|
||||
},
|
||||
},
|
||||
}
|
||||
: {
|
||||
settings: {
|
||||
...TEST_SETTINGS,
|
||||
app: {
|
||||
...TEST_SETTINGS.app,
|
||||
projectDirectory: projectDirName,
|
||||
|
@ -292,7 +292,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
|
||||
await test.step(`Verify the camera moved`, async () => {
|
||||
await scene.expectState({
|
||||
camera: {
|
||||
position: [0, -23865.37, 11073.54],
|
||||
position: [0, -23865.37, 11073.53],
|
||||
target: [0, 0, 0],
|
||||
},
|
||||
})
|
||||
|
@ -430,7 +430,6 @@ test.describe('Testing settings', () => {
|
||||
await test.step('Check color of logo changed when in modeling view', async () => {
|
||||
await page.getByRole('button', { name: 'New project' }).click()
|
||||
await page.getByTestId('project-link').first().click()
|
||||
await page.getByRole('button', { name: 'Dismiss' }).click()
|
||||
await changeColor('58')
|
||||
await expect(logoLink).toHaveCSS('--primary-hue', '58')
|
||||
})
|
||||
|
4
interface.d.ts
vendored
@ -2,7 +2,7 @@ import fs from 'node:fs/promises'
|
||||
import fsSync from 'node:fs'
|
||||
import path from 'path'
|
||||
import { dialog, shell } from 'electron'
|
||||
import { MachinesListing } from 'lib/machineManager'
|
||||
import { MachinesListing } from 'components/MachineManagerProvider'
|
||||
|
||||
type EnvFn = (value?: string) => string
|
||||
|
||||
@ -23,8 +23,8 @@ export interface IElectronAPI {
|
||||
key: string,
|
||||
callback: (eventType: string, path: string) => void
|
||||
) => void
|
||||
readFile: typeof fs.readFile
|
||||
watchFileOff: (path: string, key: string) => void
|
||||
readFile: (path: string) => ReturnType<fs.readFile>
|
||||
writeFile: (
|
||||
path: string,
|
||||
data: string | Uint8Array
|
||||
|
@ -70,7 +70,7 @@ echo ""
|
||||
echo "Suggested changelog:"
|
||||
echo "\`\`\`"
|
||||
echo "## What's Changed"
|
||||
git log $(git describe --tags --abbrev=0)..HEAD --oneline --pretty=format:%s | grep -v Bump | grep -v 'Cut release v' | awk '{print "* "toupper(substr($0,0,1))substr($0,2)}'
|
||||
git log $(git describe --tags --match="v[0-9]*" --abbrev=0)..HEAD --oneline --pretty=format:%s | grep -v Bump | grep -v 'Cut release v' | awk '{print "* "toupper(substr($0,0,1))substr($0,2)}'
|
||||
echo ""
|
||||
echo "**Full Changelog**: https://github.com/KittyCAD/modeling-app/compare/${latest_tag}...${new_version}"
|
||||
echo "\`\`\`"
|
||||
|
@ -107,6 +107,13 @@
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"loaded_filament_idx": {
|
||||
"description": "The currently loaded filament index.",
|
||||
"format": "uint",
|
||||
"minimum": 0,
|
||||
"nullable": true,
|
||||
"type": "integer"
|
||||
},
|
||||
"nozzle_diameter": {
|
||||
"description": "Diameter of the extrusion nozzle, in mm.",
|
||||
"format": "double",
|
||||
@ -285,6 +292,21 @@
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"description": "Unknown material",
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"unknown"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -974,7 +996,7 @@
|
||||
},
|
||||
"description": "",
|
||||
"title": "machine-api",
|
||||
"version": "0.1.0"
|
||||
"version": "0.1.1"
|
||||
},
|
||||
"openapi": "3.0.3",
|
||||
"paths": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zoo-modeling-app",
|
||||
"version": "0.25.6",
|
||||
"version": "0.26.2",
|
||||
"private": true,
|
||||
"productName": "Zoo Modeling App",
|
||||
"author": {
|
||||
@ -161,7 +161,7 @@
|
||||
"@types/isomorphic-fetch": "^0.0.39",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/mocha": "^10.0.6",
|
||||
"@types/node": "^22.5.0",
|
||||
"@types/node": "^22.7.8",
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react": "^18.3.4",
|
||||
|
@ -21,6 +21,7 @@ import { WasmErrBanner } from 'components/WasmErrBanner'
|
||||
import { CommandBar } from 'components/CommandBar/CommandBar'
|
||||
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
||||
import FileMachineProvider from 'components/FileMachineProvider'
|
||||
import { MachineManagerProvider } from 'components/MachineManagerProvider'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import {
|
||||
fileLoader,
|
||||
@ -49,6 +50,7 @@ const router = createRouter([
|
||||
{
|
||||
loader: settingsLoader,
|
||||
id: PATHS.INDEX,
|
||||
// TODO: Re-evaluate if this is true
|
||||
/* Make sure auth is the outermost provider or else we will have
|
||||
* inefficient re-renders, use the react profiler to see. */
|
||||
element: (
|
||||
@ -57,7 +59,9 @@ const router = createRouter([
|
||||
<LspProvider>
|
||||
<KclContextProvider>
|
||||
<AppStateProvider>
|
||||
<Outlet />
|
||||
<MachineManagerProvider>
|
||||
<Outlet />
|
||||
</MachineManagerProvider>
|
||||
</AppStateProvider>
|
||||
</KclContextProvider>
|
||||
</LspProvider>
|
||||
|
@ -64,6 +64,27 @@ export type ReactCameraProperties =
|
||||
|
||||
const lastCmdDelay = 50
|
||||
|
||||
class CameraRateLimiter {
|
||||
lastSend?: Date = undefined
|
||||
rateLimitMs: number = 16 //60 FPS
|
||||
|
||||
send = (f: () => void) => {
|
||||
let now = new Date()
|
||||
|
||||
if (
|
||||
this.lastSend === undefined ||
|
||||
now.getTime() - this.lastSend.getTime() > this.rateLimitMs
|
||||
) {
|
||||
f()
|
||||
this.lastSend = now
|
||||
}
|
||||
}
|
||||
|
||||
reset = () => {
|
||||
this.lastSend = undefined
|
||||
}
|
||||
}
|
||||
|
||||
export class CameraControls {
|
||||
engineCommandManager: EngineCommandManager
|
||||
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
|
||||
@ -71,15 +92,15 @@ export class CameraControls {
|
||||
target: Vector3
|
||||
domElement: HTMLCanvasElement
|
||||
isDragging: boolean
|
||||
wasDragging: boolean
|
||||
mouseDownPosition: Vector2
|
||||
mouseNewPosition: Vector2
|
||||
rotationSpeed = 0.3
|
||||
enableRotate = true
|
||||
enablePan = true
|
||||
enableZoom = true
|
||||
zoomDataFromLastFrame?: number = undefined
|
||||
// holds coordinates, and interaction
|
||||
moveDataFromLastFrame?: [number, number, string] = undefined
|
||||
moveSender: CameraRateLimiter = new CameraRateLimiter()
|
||||
zoomSender: CameraRateLimiter = new CameraRateLimiter()
|
||||
lastPerspectiveFov: number = 45
|
||||
pendingZoom: number | null = null
|
||||
pendingRotation: Vector2 | null = null
|
||||
@ -171,6 +192,36 @@ export class CameraControls {
|
||||
}
|
||||
}
|
||||
|
||||
doMove = (interaction: any, coordinates: any) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'camera_drag_move',
|
||||
interaction: interaction,
|
||||
window: {
|
||||
x: coordinates[0],
|
||||
y: coordinates[1],
|
||||
},
|
||||
},
|
||||
cmd_id: uuidv4(),
|
||||
})
|
||||
}
|
||||
|
||||
doZoom = (zoom: number) => {
|
||||
this.handleStart()
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'default_camera_zoom',
|
||||
magnitude: (-1 * zoom) / window.devicePixelRatio,
|
||||
},
|
||||
cmd_id: uuidv4(),
|
||||
})
|
||||
this.handleEnd()
|
||||
}
|
||||
|
||||
constructor(
|
||||
isOrtho = false,
|
||||
domElement: HTMLCanvasElement,
|
||||
@ -183,6 +234,7 @@ export class CameraControls {
|
||||
this.target = new Vector3()
|
||||
this.domElement = domElement
|
||||
this.isDragging = false
|
||||
this.wasDragging = false
|
||||
this.mouseDownPosition = new Vector2()
|
||||
this.mouseNewPosition = new Vector2()
|
||||
|
||||
@ -258,49 +310,6 @@ export class CameraControls {
|
||||
this.onCameraChange()
|
||||
}
|
||||
|
||||
// Our stream is never more than 60fps.
|
||||
// We can get away with capping our "virtual fps" to 60 then.
|
||||
const FPS_VIRTUAL = 60
|
||||
|
||||
const doZoom = () => {
|
||||
if (this.zoomDataFromLastFrame !== undefined) {
|
||||
this.handleStart()
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'default_camera_zoom',
|
||||
magnitude:
|
||||
(-1 * this.zoomDataFromLastFrame) / window.devicePixelRatio,
|
||||
},
|
||||
cmd_id: uuidv4(),
|
||||
})
|
||||
this.handleEnd()
|
||||
}
|
||||
this.zoomDataFromLastFrame = undefined
|
||||
}
|
||||
setInterval(doZoom, 1000 / FPS_VIRTUAL)
|
||||
|
||||
const doMove = () => {
|
||||
if (this.moveDataFromLastFrame !== undefined) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'camera_drag_move',
|
||||
interaction: this.moveDataFromLastFrame[2] as any,
|
||||
window: {
|
||||
x: this.moveDataFromLastFrame[0],
|
||||
y: this.moveDataFromLastFrame[1],
|
||||
},
|
||||
},
|
||||
cmd_id: uuidv4(),
|
||||
})
|
||||
}
|
||||
this.moveDataFromLastFrame = undefined
|
||||
}
|
||||
setInterval(doMove, 1000 / FPS_VIRTUAL)
|
||||
|
||||
setTimeout(() => {
|
||||
this.engineCommandManager.subscribeTo({
|
||||
event: 'camera_drag_end',
|
||||
@ -356,6 +365,8 @@ export class CameraControls {
|
||||
onMouseDown = (event: PointerEvent) => {
|
||||
this.domElement.setPointerCapture(event.pointerId)
|
||||
this.isDragging = true
|
||||
// Reset the wasDragging flag to false when starting a new drag
|
||||
this.wasDragging = false
|
||||
this.mouseDownPosition.set(event.clientX, event.clientY)
|
||||
let interaction = this.getInteractionType(event)
|
||||
if (interaction === 'none') return
|
||||
@ -385,11 +396,18 @@ export class CameraControls {
|
||||
const interaction = this.getInteractionType(event)
|
||||
if (interaction === 'none') return
|
||||
|
||||
// If there's a valid interaction and the mouse is moving,
|
||||
// our past (and current) interaction was a drag.
|
||||
this.wasDragging = true
|
||||
|
||||
if (this.syncDirection === 'engineToClient') {
|
||||
this.moveDataFromLastFrame = [event.clientX, event.clientY, interaction]
|
||||
this.moveSender.send(() => {
|
||||
this.doMove(interaction, [event.clientX, event.clientY])
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// else "clientToEngine" (Sketch Mode) or forceUpdate
|
||||
// Implement camera movement logic here based on deltaMove
|
||||
// For example, for rotating the camera around the target:
|
||||
if (interaction === 'rotate') {
|
||||
@ -418,6 +436,9 @@ export class CameraControls {
|
||||
* under the cursor. This recently moved from being handled in App.tsx.
|
||||
* This might not be the right spot, but it is more consolidated.
|
||||
*/
|
||||
|
||||
// Clear any previous drag state
|
||||
this.wasDragging = false
|
||||
if (this.syncDirection === 'engineToClient') {
|
||||
const newCmdId = uuidv4()
|
||||
|
||||
@ -459,7 +480,9 @@ export class CameraControls {
|
||||
|
||||
if (this.syncDirection === 'engineToClient') {
|
||||
if (interaction === 'zoom') {
|
||||
this.zoomDataFromLastFrame = event.deltaY
|
||||
this.zoomSender.send(() => {
|
||||
this.doZoom(event.deltaY)
|
||||
})
|
||||
} else {
|
||||
// This case will get handled when we add pan and rotate using Apple trackpad.
|
||||
console.error(
|
||||
|
@ -338,6 +338,11 @@ export class SceneEntities {
|
||||
sceneInfra.setCallbacks({
|
||||
onClick: async (args) => {
|
||||
if (!args) return
|
||||
// If there is a valid camera interaction that matches, do that instead
|
||||
const interaction = sceneInfra.camControls.getInteractionType(
|
||||
args.mouseEvent
|
||||
)
|
||||
if (interaction !== 'none') return
|
||||
if (args.mouseEvent.which !== 1) return
|
||||
const { intersectionPoint } = args
|
||||
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) return
|
||||
@ -407,7 +412,7 @@ export class SceneEntities {
|
||||
if (err(sketch)) return Promise.reject(sketch)
|
||||
if (!sketch) return Promise.reject('sketch not found')
|
||||
|
||||
if (!isArray(sketch?.value))
|
||||
if (!isArray(sketch?.paths))
|
||||
return {
|
||||
truncatedAst,
|
||||
programMemoryOverride,
|
||||
@ -435,7 +440,7 @@ export class SceneEntities {
|
||||
maybeModdedAst,
|
||||
sketch.start.__geoMeta.sourceRange
|
||||
)
|
||||
if (sketch?.value?.[0]?.type !== 'Circle') {
|
||||
if (sketch?.paths?.[0]?.type !== 'Circle') {
|
||||
const _profileStart = createProfileStartHandle({
|
||||
from: sketch.start.from,
|
||||
id: sketch.start.__geoMeta.id,
|
||||
@ -451,16 +456,16 @@ export class SceneEntities {
|
||||
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
|
||||
}
|
||||
const callbacks: (() => SegmentOverlayPayload | null)[] = []
|
||||
sketch.value.forEach((segment, index) => {
|
||||
sketch.paths.forEach((segment, index) => {
|
||||
let segPathToNode = getNodePathFromSourceRange(
|
||||
maybeModdedAst,
|
||||
segment.__geoMeta.sourceRange
|
||||
)
|
||||
if (
|
||||
draftExpressionsIndices &&
|
||||
(sketch.value[index - 1] || sketch.start)
|
||||
(sketch.paths[index - 1] || sketch.start)
|
||||
) {
|
||||
const previousSegment = sketch.value[index - 1] || sketch.start
|
||||
const previousSegment = sketch.paths[index - 1] || sketch.start
|
||||
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
||||
maybeModdedAst,
|
||||
previousSegment.__geoMeta.sourceRange
|
||||
@ -511,7 +516,7 @@ export class SceneEntities {
|
||||
to: segment.to,
|
||||
}
|
||||
const result = initSegment({
|
||||
prevSegment: sketch.value[index - 1],
|
||||
prevSegment: sketch.paths[index - 1],
|
||||
callExpName,
|
||||
input,
|
||||
id: segment.__geoMeta.id,
|
||||
@ -610,9 +615,9 @@ export class SceneEntities {
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sg)) return Promise.reject(sg)
|
||||
const lastSeg = sg?.value?.slice(-1)[0] || sg.start
|
||||
const lastSeg = sg?.paths?.slice(-1)[0] || sg.start
|
||||
|
||||
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
||||
const index = sg.paths.length // because we've added a new segment that's not in the memory yet, no need for `-1`
|
||||
const mod = addNewSketchLn({
|
||||
node: _ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
@ -645,7 +650,13 @@ export class SceneEntities {
|
||||
sceneInfra.setCallbacks({
|
||||
onClick: async (args) => {
|
||||
if (!args) return
|
||||
// If there is a valid camera interaction that matches, do that instead
|
||||
const interaction = sceneInfra.camControls.getInteractionType(
|
||||
args.mouseEvent
|
||||
)
|
||||
if (interaction !== 'none') return
|
||||
if (args.mouseEvent.which !== 1) return
|
||||
|
||||
const { intersectionPoint } = args
|
||||
let intersection2d = intersectionPoint?.twoD
|
||||
const profileStart = args.intersects
|
||||
@ -654,7 +665,7 @@ export class SceneEntities {
|
||||
|
||||
let modifiedAst
|
||||
if (profileStart) {
|
||||
const lastSegment = sketch.value.slice(-1)[0]
|
||||
const lastSegment = sketch.paths.slice(-1)[0]
|
||||
modifiedAst = addCallExpressionsToPipe({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
@ -686,7 +697,7 @@ export class SceneEntities {
|
||||
})
|
||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
} else if (intersection2d) {
|
||||
const lastSegment = sketch.value.slice(-1)[0]
|
||||
const lastSegment = sketch.paths.slice(-1)[0]
|
||||
const tmp = addNewSketchLn({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
@ -735,7 +746,6 @@ export class SceneEntities {
|
||||
},
|
||||
})
|
||||
},
|
||||
...this.mouseEnterLeaveCallbacks(),
|
||||
})
|
||||
}
|
||||
setupDraftRectangle = async (
|
||||
@ -817,7 +827,7 @@ export class SceneEntities {
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sketch)) return Promise.reject(sketch)
|
||||
const sgPaths = sketch.value
|
||||
const sgPaths = sketch.paths
|
||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||
|
||||
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
||||
@ -826,6 +836,11 @@ export class SceneEntities {
|
||||
)
|
||||
},
|
||||
onClick: async (args) => {
|
||||
// If there is a valid camera interaction that matches, do that instead
|
||||
const interaction = sceneInfra.camControls.getInteractionType(
|
||||
args.mouseEvent
|
||||
)
|
||||
if (interaction !== 'none') return
|
||||
// Commit the rectangle to the full AST/code and return to sketch.idle
|
||||
const cornerPoint = args.intersectionPoint?.twoD
|
||||
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
||||
@ -868,7 +883,7 @@ export class SceneEntities {
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sketch)) return
|
||||
const sgPaths = sketch.value
|
||||
const sgPaths = sketch.paths
|
||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||
|
||||
// Update the starting segment of the THREEjs scene
|
||||
@ -985,7 +1000,7 @@ export class SceneEntities {
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sketch)) return
|
||||
const sgPaths = sketch.value
|
||||
const sgPaths = sketch.paths
|
||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||
|
||||
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
||||
@ -994,6 +1009,11 @@ export class SceneEntities {
|
||||
)
|
||||
},
|
||||
onClick: async (args) => {
|
||||
// If there is a valid camera interaction that matches, do that instead
|
||||
const interaction = sceneInfra.camControls.getInteractionType(
|
||||
args.mouseEvent
|
||||
)
|
||||
if (interaction !== 'none') return
|
||||
// Commit the rectangle to the full AST/code and return to sketch.idle
|
||||
const cornerPoint = args.intersectionPoint?.twoD
|
||||
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
||||
@ -1105,7 +1125,7 @@ export class SceneEntities {
|
||||
|
||||
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
|
||||
if (addingNewSegmentStatus === 'nothing') {
|
||||
const prevSegment = sketch.value[pipeIndex - 2]
|
||||
const prevSegment = sketch.paths[pipeIndex - 2]
|
||||
const mod = addNewSketchLn({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
@ -1157,6 +1177,11 @@ export class SceneEntities {
|
||||
},
|
||||
onMove: () => {},
|
||||
onClick: (args) => {
|
||||
// If there is a valid camera interaction that matches, do that instead
|
||||
const interaction = sceneInfra.camControls.getInteractionType(
|
||||
args.mouseEvent
|
||||
)
|
||||
if (interaction !== 'none') return
|
||||
if (args?.mouseEvent.which !== 1) return
|
||||
if (!args || !args.selected) {
|
||||
sceneInfra.modelingSend({
|
||||
@ -1345,7 +1370,7 @@ export class SceneEntities {
|
||||
}
|
||||
if (!sketch) return
|
||||
|
||||
const sgPaths = sketch.value
|
||||
const sgPaths = sketch.paths
|
||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||
|
||||
this.updateSegment(
|
||||
@ -1393,7 +1418,7 @@ export class SceneEntities {
|
||||
modifiedAst,
|
||||
segment.__geoMeta.sourceRange
|
||||
)
|
||||
const sgPaths = sketch.value
|
||||
const sgPaths = sketch.paths
|
||||
const originalPathToNodeStr = JSON.stringify(segPathToNode)
|
||||
segPathToNode[1][0] = varDecIndex
|
||||
const pathToNodeStr = JSON.stringify(segPathToNode)
|
||||
@ -1701,7 +1726,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
variableDeclarationName
|
||||
)
|
||||
if (err(sg)) return sg
|
||||
const lastSeg = sg?.value.slice(-1)[0]
|
||||
const lastSeg = sg?.paths.slice(-1)[0]
|
||||
if (draftSegment) {
|
||||
// truncatedAst needs to setup with another segment at the end
|
||||
let newSegment
|
||||
|
@ -213,7 +213,7 @@ export class SceneInfra {
|
||||
to: Coords2d
|
||||
angle?: number
|
||||
}): SegmentOverlayPayload | null {
|
||||
if (group.userData.pathToNode && arrowGroup) {
|
||||
if (!group.userData.draft && group.userData.pathToNode && arrowGroup) {
|
||||
const vector = new Vector3(0, 0, 0)
|
||||
|
||||
// Get the position of the object3D in world space
|
||||
|
@ -58,7 +58,7 @@ import { err } from 'lib/trap'
|
||||
|
||||
interface CreateSegmentArgs {
|
||||
input: SegmentInputs
|
||||
prevSegment: Sketch['value'][number]
|
||||
prevSegment: Sketch['paths'][number]
|
||||
id: string
|
||||
pathToNode: PathToNode
|
||||
isDraftSegment?: boolean
|
||||
@ -72,7 +72,7 @@ interface CreateSegmentArgs {
|
||||
|
||||
interface UpdateSegmentArgs {
|
||||
input: SegmentInputs
|
||||
prevSegment: Sketch['value'][number]
|
||||
prevSegment: Sketch['paths'][number]
|
||||
group: Group
|
||||
sceneInfra: SceneInfra
|
||||
scale?: number
|
||||
@ -147,6 +147,7 @@ class StraightSegment implements SegmentUtils {
|
||||
segmentGroup.name = STRAIGHT_SEGMENT
|
||||
segmentGroup.userData = {
|
||||
type: STRAIGHT_SEGMENT,
|
||||
draft: isDraftSegment,
|
||||
id,
|
||||
from,
|
||||
to,
|
||||
@ -347,6 +348,7 @@ class TangentialArcToSegment implements SegmentUtils {
|
||||
mesh.name = meshName
|
||||
group.userData = {
|
||||
type: TANGENTIAL_ARC_TO_SEGMENT,
|
||||
draft: isDraftSegment,
|
||||
id,
|
||||
from,
|
||||
to,
|
||||
@ -515,11 +517,18 @@ class CircleSegment implements SegmentUtils {
|
||||
const meshType = isDraftSegment ? CIRCLE_SEGMENT_DASH : CIRCLE_SEGMENT_BODY
|
||||
const arrowGroup = createArrowhead(scale, theme, color)
|
||||
const circleCenterGroup = createCircleCenterHandle(scale, theme, color)
|
||||
// A radius indicator that appears from the center to the perimeter
|
||||
const radiusIndicatorGroup = createLengthIndicator({
|
||||
from: center,
|
||||
to: [center[0] + radius, center[1]],
|
||||
scale,
|
||||
})
|
||||
|
||||
arcMesh.userData.type = meshType
|
||||
arcMesh.name = meshType
|
||||
group.userData = {
|
||||
type: CIRCLE_SEGMENT,
|
||||
draft: isDraftSegment,
|
||||
id,
|
||||
from,
|
||||
radius,
|
||||
@ -532,7 +541,7 @@ class CircleSegment implements SegmentUtils {
|
||||
}
|
||||
group.name = CIRCLE_SEGMENT
|
||||
|
||||
group.add(arcMesh, arrowGroup, circleCenterGroup)
|
||||
group.add(arcMesh, arrowGroup, circleCenterGroup, radiusIndicatorGroup)
|
||||
const updateOverlaysCallback = this.update({
|
||||
prevSegment,
|
||||
input,
|
||||
@ -564,6 +573,9 @@ class CircleSegment implements SegmentUtils {
|
||||
group.userData.radius = radius
|
||||
group.userData.prevSegment = prevSegment
|
||||
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
||||
const radiusLengthIndicator = group.getObjectByName(
|
||||
SEGMENT_LENGTH_LABEL
|
||||
) as Group
|
||||
const circleCenterHandle = group.getObjectByName(
|
||||
CIRCLE_CENTER_HANDLE
|
||||
) as Group
|
||||
@ -581,11 +593,14 @@ class CircleSegment implements SegmentUtils {
|
||||
}
|
||||
|
||||
if (arrowGroup) {
|
||||
arrowGroup.position.set(
|
||||
center[0] + Math.cos(Math.PI / 4) * radius,
|
||||
center[1] + Math.sin(Math.PI / 4) * radius,
|
||||
0
|
||||
)
|
||||
// The arrowhead is placed at the perimeter of the circle,
|
||||
// pointing up and to the right
|
||||
const arrowPoint = {
|
||||
x: center[0] + Math.cos(Math.PI / 4) * radius,
|
||||
y: center[1] + Math.sin(Math.PI / 4) * radius,
|
||||
}
|
||||
|
||||
arrowGroup.position.set(arrowPoint.x, arrowPoint.y, 0)
|
||||
|
||||
const arrowheadAngle = Math.PI / 4
|
||||
arrowGroup.quaternion.setFromUnitVectors(
|
||||
@ -596,6 +611,31 @@ class CircleSegment implements SegmentUtils {
|
||||
arrowGroup.visible = isHandlesVisible
|
||||
}
|
||||
|
||||
if (radiusLengthIndicator) {
|
||||
// The radius indicator is placed at the midpoint of the radius,
|
||||
// at a 45 degree CCW angle from the positive X-axis
|
||||
const indicatorPoint = {
|
||||
x: center[0] + (Math.cos(Math.PI / 4) * radius) / 2,
|
||||
y: center[1] + (Math.sin(Math.PI / 4) * radius) / 2,
|
||||
}
|
||||
const labelWrapper = radiusLengthIndicator.getObjectByName(
|
||||
SEGMENT_LENGTH_LABEL_TEXT
|
||||
) as CSS2DObject
|
||||
const labelWrapperElem = labelWrapper.element as HTMLDivElement
|
||||
const label = labelWrapperElem.children[0] as HTMLParagraphElement
|
||||
label.innerText = `${roundOff(radius)}`
|
||||
label.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
|
||||
const isPlaneBackFace = center[0] > indicatorPoint.x
|
||||
label.style.setProperty(
|
||||
'--degree',
|
||||
`${isPlaneBackFace ? '45' : '-45'}deg`
|
||||
)
|
||||
label.style.setProperty('--x', `0px`)
|
||||
label.style.setProperty('--y', `0px`)
|
||||
labelWrapper.position.set(indicatorPoint.x, indicatorPoint.y, 0)
|
||||
radiusLengthIndicator.visible = isHandlesVisible
|
||||
}
|
||||
|
||||
if (circleCenterHandle) {
|
||||
circleCenterHandle.position.set(center[0], center[1], 0)
|
||||
circleCenterHandle.scale.set(scale, scale, scale)
|
||||
|
@ -135,7 +135,9 @@ function CommandArgOptionInput({
|
||||
<Combobox.Input
|
||||
id="option-input"
|
||||
ref={inputRef}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
onChange={(event) =>
|
||||
!event.target.disabled && setQuery(event.target.value)
|
||||
}
|
||||
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
|
||||
onKeyDown={(event) => {
|
||||
if (event.metaKey && event.key === 'k')
|
||||
@ -175,9 +177,18 @@ function CommandArgOptionInput({
|
||||
<Combobox.Option
|
||||
key={option.name}
|
||||
value={option}
|
||||
disabled={option.disabled}
|
||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||
>
|
||||
<p className="flex-grow">{option.name} </p>
|
||||
<p
|
||||
className={`flex-grow ${
|
||||
(option.disabled &&
|
||||
'text-chalkboard-70 dark:text-chalkboard-50 cursor-not-allowed') ||
|
||||
''
|
||||
}`}
|
||||
>
|
||||
{option.name}
|
||||
</p>
|
||||
{option.value === currentOption?.value && (
|
||||
<small className="text-chalkboard-70 dark:text-chalkboard-50">
|
||||
current
|
||||
|
@ -140,7 +140,14 @@ const FileTreeItem = ({
|
||||
async (eventType, path) => {
|
||||
// Don't try to read a file that was removed.
|
||||
if (isCurrentFile && eventType !== 'unlink') {
|
||||
let code = await window.electron.readFile(path)
|
||||
// Prevents a cyclic read / write causing editor problems such as
|
||||
// misplaced cursor positions.
|
||||
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
|
||||
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
|
||||
return
|
||||
}
|
||||
|
||||
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
|
||||
code = normalizeLineEndings(code)
|
||||
codeManager.updateCodeStateEditor(code)
|
||||
}
|
||||
@ -488,6 +495,12 @@ export const FileTreeInner = ({
|
||||
// Refresh the file tree when there are changes.
|
||||
useFileSystemWatcher(
|
||||
async (eventType, path) => {
|
||||
// Our other watcher races with this watcher on the current file changes,
|
||||
// so we need to stop this one from reacting at all, otherwise Bad Things
|
||||
// Happen™.
|
||||
const isCurrentFile = loaderData.file?.path === path
|
||||
const hasChanged = eventType === 'change'
|
||||
if (isCurrentFile && hasChanged) return
|
||||
fileSend({ type: 'Refresh' })
|
||||
},
|
||||
[loaderData?.project?.path, fileContext.selectedDirectory.path].filter(
|
||||
|
@ -23,6 +23,7 @@ export function LowerRightControls({
|
||||
}) {
|
||||
const location = useLocation()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
|
||||
const linkOverrideClassName =
|
||||
'!text-chalkboard-70 hover:!text-chalkboard-80 dark:!text-chalkboard-40 dark:hover:!text-chalkboard-30'
|
||||
|
||||
|
123
src/components/MachineManagerProvider.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import { createContext, useEffect, useState } from 'react'
|
||||
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { toSync } from 'lib/utils'
|
||||
|
||||
export type MachinesListing = Array<
|
||||
components['schemas']['MachineInfoResponse']
|
||||
>
|
||||
|
||||
export interface MachineManager {
|
||||
machines: MachinesListing
|
||||
machineApiIp: string | null
|
||||
currentMachine: components['schemas']['MachineInfoResponse'] | null
|
||||
noMachinesReason: () => string | undefined
|
||||
setCurrentMachine: (
|
||||
m: components['schemas']['MachineInfoResponse'] | null
|
||||
) => void
|
||||
}
|
||||
|
||||
export const MachineManagerContext = createContext<MachineManager>({
|
||||
machines: [],
|
||||
machineApiIp: null,
|
||||
currentMachine: null,
|
||||
setCurrentMachine: (
|
||||
_: components['schemas']['MachineInfoResponse'] | null
|
||||
) => {},
|
||||
noMachinesReason: () => undefined,
|
||||
})
|
||||
|
||||
export const MachineManagerProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) => {
|
||||
const [machines, setMachines] = useState<MachinesListing>([])
|
||||
const [machineApiIp, setMachineApiIp] = useState<string | null>(null)
|
||||
const [currentMachine, setCurrentMachine] = useState<
|
||||
components['schemas']['MachineInfoResponse'] | null
|
||||
>(null)
|
||||
|
||||
const commandBarActor = CommandsContext.useActorRef()
|
||||
|
||||
// Get the reason message for why there are no machines.
|
||||
const noMachinesReason = (): string | undefined => {
|
||||
if (machines.length > 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (machineApiIp === null) {
|
||||
return 'Machine API server was not discovered'
|
||||
}
|
||||
|
||||
return 'Machine API server was discovered, but no machines are available'
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDesktop()) return
|
||||
|
||||
const update = async () => {
|
||||
const _machineApiIp = await window.electron.getMachineApiIp()
|
||||
if (_machineApiIp === null) return
|
||||
|
||||
setMachineApiIp(_machineApiIp)
|
||||
|
||||
const _machines = await window.electron.listMachines(_machineApiIp)
|
||||
setMachines(_machines)
|
||||
}
|
||||
|
||||
// Start a background job to update the machines every ten seconds.
|
||||
// If MDNS is already watching, this timeout will wait until it's done to trigger the
|
||||
// finding again.
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
const timeoutLoop = () => {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(
|
||||
toSync(async () => {
|
||||
await update()
|
||||
timeoutLoop()
|
||||
}, reportRejection),
|
||||
1000
|
||||
)
|
||||
}
|
||||
timeoutLoop()
|
||||
update().catch(reportRejection)
|
||||
}, [])
|
||||
|
||||
// Update engineCommandManager's copy of this data.
|
||||
useEffect(() => {
|
||||
const machineManagerNext = {
|
||||
machines,
|
||||
machineApiIp,
|
||||
currentMachine,
|
||||
noMachinesReason,
|
||||
setCurrentMachine,
|
||||
}
|
||||
|
||||
engineCommandManager.machineManager = machineManagerNext
|
||||
|
||||
commandBarActor.send({
|
||||
type: 'Set machine manager',
|
||||
data: machineManagerNext,
|
||||
})
|
||||
}, [machines, machineApiIp, currentMachine])
|
||||
|
||||
return (
|
||||
<MachineManagerContext.Provider
|
||||
value={{
|
||||
machines,
|
||||
machineApiIp,
|
||||
currentMachine,
|
||||
setCurrentMachine,
|
||||
noMachinesReason,
|
||||
}}
|
||||
>
|
||||
{' '}
|
||||
{children}{' '}
|
||||
</MachineManagerContext.Provider>
|
||||
)
|
||||
}
|
@ -1,5 +1,11 @@
|
||||
import { useMachine } from '@xstate/react'
|
||||
import React, { createContext, useEffect, useMemo, useRef } from 'react'
|
||||
import React, {
|
||||
createContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useContext,
|
||||
} from 'react'
|
||||
import {
|
||||
Actor,
|
||||
AnyStateMachine,
|
||||
@ -28,7 +34,7 @@ import {
|
||||
editorManager,
|
||||
sceneEntitiesManager,
|
||||
} from 'lib/singletons'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||
import {
|
||||
@ -69,7 +75,7 @@ import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
import { Models } from '@kittycad/lib/dist/types/src'
|
||||
import toast from 'react-hot-toast'
|
||||
import { EditorSelection, Transaction } from '@codemirror/state'
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||
import { getVarNameModal } from 'hooks/useToolbarGuards'
|
||||
import { err, reportRejection, trap } from 'lib/trap'
|
||||
@ -84,6 +90,7 @@ import {
|
||||
import { submitAndAwaitTextToKcl } from 'lib/textToCad'
|
||||
import { useFileContext } from 'hooks/useFileContext'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { IndexLoaderData } from 'lib/types'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -116,6 +123,7 @@ export const ModelingMachineProvider = ({
|
||||
} = useSettingsAuthContext()
|
||||
const navigate = useNavigate()
|
||||
const { context, send: fileMachineSend } = useFileContext()
|
||||
const { file } = useLoaderData() as IndexLoaderData
|
||||
const token = auth?.context?.token
|
||||
const streamRef = useRef<HTMLDivElement>(null)
|
||||
const persistedContext = useMemo(() => getPersistedContext(), [])
|
||||
@ -138,6 +146,8 @@ export const ModelingMachineProvider = ({
|
||||
// >
|
||||
// )
|
||||
|
||||
const machineManager = useContext(MachineManagerContext)
|
||||
|
||||
const [modelingState, modelingSend, modelingActor] = useMachine(
|
||||
modelingMachine.provide({
|
||||
actions: {
|
||||
@ -406,18 +416,35 @@ export const ModelingMachineProvider = ({
|
||||
return {}
|
||||
}
|
||||
),
|
||||
Make: ({ event }) => {
|
||||
Make: ({ context, event }) => {
|
||||
if (event.type !== 'Make') return
|
||||
// Check if we already have an export intent.
|
||||
if (engineCommandManager.exportIntent) {
|
||||
if (engineCommandManager.exportInfo) {
|
||||
toast.error('Already exporting')
|
||||
return
|
||||
}
|
||||
// Set the export intent.
|
||||
engineCommandManager.exportIntent = ExportIntent.Make
|
||||
engineCommandManager.exportInfo = {
|
||||
intent: ExportIntent.Make,
|
||||
name: file?.name || '',
|
||||
}
|
||||
|
||||
// Set the current machine.
|
||||
machineManager.currentMachine = event.data.machine
|
||||
// Due to our use of singeton pattern, we need to do this to reliably
|
||||
// update this object across React and non-React boundary.
|
||||
// We need to do this eagerly because of the exportToEngine call below.
|
||||
if (engineCommandManager.machineManager === null) {
|
||||
console.warn(
|
||||
"engineCommandManager.machineManager is null. It shouldn't be at this point. Aborting operation."
|
||||
)
|
||||
return
|
||||
} else {
|
||||
engineCommandManager.machineManager.currentMachine =
|
||||
event.data.machine
|
||||
}
|
||||
|
||||
// Update the rest of the UI that needs to know the current machine
|
||||
context.machineManager.setCurrentMachine(event.data.machine)
|
||||
|
||||
const format: Models['OutputFormat_type'] = {
|
||||
type: 'stl',
|
||||
@ -443,12 +470,16 @@ export const ModelingMachineProvider = ({
|
||||
},
|
||||
'Engine export': ({ event }) => {
|
||||
if (event.type !== 'Export') return
|
||||
if (engineCommandManager.exportIntent) {
|
||||
if (engineCommandManager.exportInfo) {
|
||||
toast.error('Already exporting')
|
||||
return
|
||||
}
|
||||
// Set the export intent.
|
||||
engineCommandManager.exportIntent = ExportIntent.Save
|
||||
engineCommandManager.exportInfo = {
|
||||
intent: ExportIntent.Save,
|
||||
// This never gets used its only for make.
|
||||
name: '',
|
||||
}
|
||||
|
||||
const format = {
|
||||
...event.data,
|
||||
@ -635,6 +666,7 @@ export const ModelingMachineProvider = ({
|
||||
input.plane
|
||||
)
|
||||
await kclManager.updateAst(modifiedAst, false)
|
||||
sceneInfra.camControls.enableRotate = false
|
||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||
|
||||
await letEngineAnimateAndSyncCamAfter(
|
||||
@ -985,6 +1017,7 @@ export const ModelingMachineProvider = ({
|
||||
...modelingMachineDefaultContext.store,
|
||||
...persistedContext,
|
||||
},
|
||||
machineManager,
|
||||
},
|
||||
// devTools: true,
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ export const processMemory = (programMemory: ProgramMemory) => {
|
||||
return rest
|
||||
})
|
||||
} else if (!err(sg)) {
|
||||
processedMemory[key] = sg.value.map(({ __geoMeta, ...rest }: Path) => {
|
||||
processedMemory[key] = sg.paths.map(({ __geoMeta, ...rest }: Path) => {
|
||||
return rest
|
||||
})
|
||||
} else if ((val.type as any) === 'Function') {
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { Resizable } from 're-resizable'
|
||||
import { MouseEventHandler, useCallback, useEffect, useMemo } from 'react'
|
||||
import {
|
||||
MouseEventHandler,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useContext,
|
||||
} from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { SidebarAction, SidebarType, sidebarPanes } from './ModelingPanes'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
@ -13,7 +19,7 @@ import { CustomIconName } from 'components/CustomIcon'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
|
||||
interface ModelingSidebarProps {
|
||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||
@ -29,6 +35,7 @@ function getPlatformString(): 'web' | 'desktop' {
|
||||
}
|
||||
|
||||
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
const machineManager = useContext(MachineManagerContext)
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const kclContext = useKclContext()
|
||||
const { settings } = useSettingsAuthContext()
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { useContext } from 'react'
|
||||
import Tooltip from './Tooltip'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
|
||||
export const NetworkMachineIndicator = ({
|
||||
@ -9,10 +11,12 @@ export const NetworkMachineIndicator = ({
|
||||
}: {
|
||||
className?: string
|
||||
}) => {
|
||||
const machineCount = machineManager.machineCount()
|
||||
const reason = machineManager.noMachinesReason()
|
||||
const machines = machineManager.machines
|
||||
console.log('react machines', machines)
|
||||
const {
|
||||
noMachinesReason,
|
||||
machines,
|
||||
machines: { length: machineCount },
|
||||
} = useContext(MachineManagerContext)
|
||||
const reason = noMachinesReason()
|
||||
|
||||
return isDesktop() ? (
|
||||
<Popover className="relative">
|
||||
@ -48,29 +52,36 @@ export const NetworkMachineIndicator = ({
|
||||
</div>
|
||||
{machineCount > 0 && (
|
||||
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||
{machines.map((machine) => {
|
||||
return (
|
||||
<li key={machine.id} className={'px-2 py-4 gap-1 last:mb-0 '}>
|
||||
<p className="">{machine.id.toUpperCase()}</p>
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{machine.make_model.model}
|
||||
</p>
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{machine.state.state.toUpperCase()}
|
||||
{machine.state.state === 'failed' && machine.state.message
|
||||
? ': ' + machine.state.message
|
||||
: ''}
|
||||
</p>
|
||||
{machine.extra &&
|
||||
machine.extra.type === 'bambu' &&
|
||||
machine.extra.nozzle_diameter && (
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
Nozzle Diameter: {machine.extra.nozzle_diameter}
|
||||
</p>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{machines.map(
|
||||
(machine: components['schemas']['MachineInfoResponse']) => {
|
||||
return (
|
||||
<li key={machine.id} className={'px-2 py-4 gap-1 last:mb-0 '}>
|
||||
<p className="">{machine.id.toUpperCase()}</p>
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{machine.make_model.model}
|
||||
</p>
|
||||
{machine.extra &&
|
||||
machine.extra.type === 'bambu' &&
|
||||
machine.extra.nozzle_diameter && (
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
Nozzle Diameter: {machine.extra.nozzle_diameter}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{`Status: ${machine.state.state
|
||||
.charAt(0)
|
||||
.toUpperCase()}${machine.state.state.slice(1)}`}
|
||||
{machine.state.state === 'failed' && machine.state.message
|
||||
? ` (${machine.state.message})`
|
||||
: ''}
|
||||
{machine.state.state === 'running' && machine.progress
|
||||
? ` (${Math.round(machine.progress)}%)`
|
||||
: ''}
|
||||
</p>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</Popover.Panel>
|
||||
|
@ -4,14 +4,14 @@ import { type IndexLoaderData } from 'lib/types'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { isDesktop } from '../lib/isDesktop'
|
||||
import { Link, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Fragment, useMemo } from 'react'
|
||||
import { Fragment, useMemo, useContext } from 'react'
|
||||
import { Logo } from './Logo'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { useLspContext } from './LspProvider'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import Tooltip from './Tooltip'
|
||||
@ -96,6 +96,8 @@ function ProjectMenuPopover({
|
||||
const location = useLocation()
|
||||
const navigate = useNavigate()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
const machineManager = useContext(MachineManagerContext)
|
||||
|
||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||
const { onProjectClose } = useLspContext()
|
||||
const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
|
||||
@ -106,7 +108,7 @@ function ProjectMenuPopover({
|
||||
(c) => c.name === obj.name && c.groupId === obj.groupId
|
||||
)
|
||||
)
|
||||
const machineCount = machineManager.machineCount()
|
||||
const machineCount = machineManager.machines.length
|
||||
|
||||
// We filter this memoized list so that no orphan "break" elements are rendered.
|
||||
const projectMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
|
||||
|
@ -255,10 +255,14 @@ export const Stream = () => {
|
||||
}, [mediaStream])
|
||||
|
||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||
// If we've got no stream or connection, don't do anything
|
||||
if (!isNetworkOkay) return
|
||||
if (!videoRef.current) return
|
||||
// If we're in sketch mode, don't send a engine-side select event
|
||||
if (state.matches('Sketch')) return
|
||||
if (state.matches({ idle: 'showPlanes' })) return
|
||||
// If we're mousing up from a camera drag, don't send a select event
|
||||
if (sceneInfra.camControls.wasDragging === true) return
|
||||
|
||||
if (btnName(e.nativeEvent).left) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
|
@ -32,7 +32,7 @@ const mySketch001 = startSketchOn('XY')
|
||||
sourceRange: [46, 71],
|
||||
},
|
||||
},
|
||||
value: [
|
||||
paths: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
tag: null,
|
||||
@ -96,7 +96,7 @@ const mySketch001 = startSketchOn('XY')
|
||||
on: expect.any(Object),
|
||||
start: expect.any(Object),
|
||||
type: 'Sketch',
|
||||
value: [
|
||||
paths: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
from: [0, 0],
|
||||
@ -202,7 +202,7 @@ const sk2 = startSketchOn('XY')
|
||||
info: expect.any(Object),
|
||||
},
|
||||
},
|
||||
value: [
|
||||
paths: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
from: [0, 0],
|
||||
@ -294,7 +294,7 @@ const sk2 = startSketchOn('XY')
|
||||
info: expect.any(Object),
|
||||
},
|
||||
},
|
||||
value: [
|
||||
paths: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
from: [0, 0],
|
||||
|
@ -18,6 +18,9 @@ export default class CodeManager {
|
||||
#updateState: (arg: string) => void = () => {}
|
||||
private _currentFilePath: string | null = null
|
||||
private _hotkeys: { [key: string]: () => void } = {}
|
||||
private timeoutWriter: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
|
||||
public writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
|
||||
|
||||
constructor() {
|
||||
if (isDesktop()) {
|
||||
@ -115,7 +118,12 @@ export default class CodeManager {
|
||||
|
||||
async writeToFile() {
|
||||
if (isDesktop()) {
|
||||
setTimeout(() => {
|
||||
// Only write our buffer contents to file once per second. Any faster
|
||||
// and file-system watchers which read, will receive empty data during
|
||||
// writes.
|
||||
clearTimeout(this.timeoutWriter)
|
||||
this.writeCausedByAppCheckedInFileTreeFileSystemWatcher = true
|
||||
this.timeoutWriter = setTimeout(() => {
|
||||
// Wait one event loop to give a chance for params to be set
|
||||
// Save the file to disk
|
||||
this._currentFilePath &&
|
||||
@ -126,7 +134,7 @@ export default class CodeManager {
|
||||
console.error('error saving file', err)
|
||||
toast.error('Error saving file, please check file permissions')
|
||||
})
|
||||
})
|
||||
}, 1000)
|
||||
} else {
|
||||
safeLSSetItem(PERSIST_CODE_KEY, this.code)
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ const newVar = myVar + 1`
|
||||
`
|
||||
const mem = await exe(code)
|
||||
// geo is three js buffer geometry and is very bloated to have in tests
|
||||
const minusGeo = mem.get('mySketch')?.value?.value
|
||||
const minusGeo = mem.get('mySketch')?.value?.paths
|
||||
expect(minusGeo).toEqual([
|
||||
{
|
||||
type: 'ToPoint',
|
||||
@ -175,7 +175,7 @@ const newVar = myVar + 1`
|
||||
info: expect.any(Object),
|
||||
},
|
||||
},
|
||||
value: [
|
||||
paths: [
|
||||
{
|
||||
type: 'ToPoint',
|
||||
to: [1, 1],
|
||||
@ -367,7 +367,7 @@ describe('testing math operators', () => {
|
||||
const mem = await exe(code)
|
||||
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
|
||||
// result of `-legLen(5, min(3, 999))` should be -4
|
||||
const yVal = (sketch as Sketch).value?.[0]?.to?.[1]
|
||||
const yVal = (sketch as Sketch).paths?.[0]?.to?.[1]
|
||||
expect(yVal).toBe(-4)
|
||||
})
|
||||
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
|
||||
@ -385,8 +385,8 @@ describe('testing math operators', () => {
|
||||
const mem = await exe(code)
|
||||
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
|
||||
// expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0
|
||||
expect((sketch as Sketch).value?.[1]?.from).toEqual([3, 4])
|
||||
expect((sketch as Sketch).value?.[1]?.to).toEqual([6, 0])
|
||||
expect((sketch as Sketch).paths?.[1]?.from).toEqual([3, 4])
|
||||
expect((sketch as Sketch).paths?.[1]?.to).toEqual([6, 0])
|
||||
const removedUnaryExp = code.replace(
|
||||
`-legLen(segLen(seg01), myVar)`,
|
||||
`legLen(segLen(seg01), myVar)`
|
||||
@ -398,7 +398,7 @@ describe('testing math operators', () => {
|
||||
)
|
||||
|
||||
// without the minus sign, the y value should be 8
|
||||
expect((removedUnaryExpMemSketch as Sketch).value?.[1]?.to).toEqual([6, 8])
|
||||
expect((removedUnaryExpMemSketch as Sketch).paths?.[1]?.to).toEqual([6, 8])
|
||||
})
|
||||
it('with nested callExpression and binaryExpression', async () => {
|
||||
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
|
||||
|
@ -41,7 +41,7 @@ beforeAll(async () => {
|
||||
},
|
||||
})
|
||||
})
|
||||
}, 20_000)
|
||||
}, 30_000)
|
||||
|
||||
afterAll(() => {
|
||||
engineCommandManager.tearDown()
|
||||
|
@ -717,7 +717,7 @@ export function isLinesParallelAndConstrained(
|
||||
constraintType === 'angle' || constraintLevel === 'full'
|
||||
|
||||
// get the previous segment
|
||||
const prevSegment = sg.value[secondaryIndex - 1]
|
||||
const prevSegment = sg.paths[secondaryIndex - 1]
|
||||
const prevSourceRange = prevSegment.__geoMeta.sourceRange
|
||||
|
||||
const isParallelAndConstrained =
|
||||
|
@ -28,6 +28,7 @@ import {
|
||||
} from 'lib/constants'
|
||||
import { KclManager } from 'lang/KclSingleton'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { MachineManager } from 'components/MachineManagerProvider'
|
||||
|
||||
// TODO(paultag): This ought to be tweakable.
|
||||
const pingIntervalMs = 5_000
|
||||
@ -50,6 +51,11 @@ export enum ExportIntent {
|
||||
Make = 'make',
|
||||
}
|
||||
|
||||
export interface ExportInfo {
|
||||
intent: ExportIntent
|
||||
name: string
|
||||
}
|
||||
|
||||
type ClientMetrics = Models['ClientMetrics_type']
|
||||
|
||||
interface WebRTCClientMetrics extends ClientMetrics {
|
||||
@ -1354,7 +1360,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
* export in progress. Otherwise it is an enum value of the intent.
|
||||
* Another export cannot be started if one is already in progress.
|
||||
*/
|
||||
private _exportIntent: ExportIntent | null = null
|
||||
private _exportInfo: ExportInfo | null = null
|
||||
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
||||
|
||||
subscriptions: {
|
||||
@ -1410,12 +1416,15 @@ export class EngineCommandManager extends EventTarget {
|
||||
(() => {}) as any
|
||||
kclManager: null | KclManager = null
|
||||
|
||||
set exportIntent(intent: ExportIntent | null) {
|
||||
this._exportIntent = intent
|
||||
// The current "manufacturing machine" aka 3D printer, CNC, etc.
|
||||
public machineManager: MachineManager | null = null
|
||||
|
||||
set exportInfo(info: ExportInfo | null) {
|
||||
this._exportInfo = info
|
||||
}
|
||||
|
||||
get exportIntent() {
|
||||
return this._exportIntent
|
||||
get exportInfo() {
|
||||
return this._exportInfo
|
||||
}
|
||||
|
||||
start({
|
||||
@ -1607,7 +1616,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
// because in all other cases we send JSON strings. But in the case of
|
||||
// export we send a binary blob.
|
||||
// Pass this to our export function.
|
||||
if (this.exportIntent === null || this.pendingExport === undefined) {
|
||||
if (this.exportInfo === null || this.pendingExport === undefined) {
|
||||
toast.error(
|
||||
'Export intent was not set, but export data was received'
|
||||
)
|
||||
@ -1617,7 +1626,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
return
|
||||
}
|
||||
|
||||
switch (this.exportIntent) {
|
||||
switch (this.exportInfo.intent) {
|
||||
case ExportIntent.Save: {
|
||||
exportSave(event.data, this.pendingExport.toastId).then(() => {
|
||||
this.pendingExport?.resolve(null)
|
||||
@ -1625,21 +1634,28 @@ export class EngineCommandManager extends EventTarget {
|
||||
break
|
||||
}
|
||||
case ExportIntent.Make: {
|
||||
exportMake(event.data, this.pendingExport.toastId).then(
|
||||
(result) => {
|
||||
if (result) {
|
||||
this.pendingExport?.resolve(null)
|
||||
} else {
|
||||
this.pendingExport?.reject('Failed to make export')
|
||||
}
|
||||
},
|
||||
this.pendingExport?.reject
|
||||
)
|
||||
if (!this.machineManager) {
|
||||
console.warn('Some how, no manufacturing machine is selected.')
|
||||
break
|
||||
}
|
||||
|
||||
exportMake(
|
||||
event.data,
|
||||
this.exportInfo.name,
|
||||
this.pendingExport.toastId,
|
||||
this.machineManager
|
||||
).then((result) => {
|
||||
if (result) {
|
||||
this.pendingExport?.resolve(null)
|
||||
} else {
|
||||
this.pendingExport?.reject('Failed to make export')
|
||||
}
|
||||
}, this.pendingExport?.reject)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Set the export intent back to null.
|
||||
this.exportIntent = null
|
||||
this.exportInfo = null
|
||||
return
|
||||
}
|
||||
|
||||
@ -1953,15 +1969,15 @@ export class EngineCommandManager extends EventTarget {
|
||||
return Promise.resolve(null)
|
||||
} else if (cmd.type === 'export') {
|
||||
const promise = new Promise<null>((resolve, reject) => {
|
||||
if (this.exportIntent === null) {
|
||||
if (this.exportIntent === null) {
|
||||
if (this.exportInfo === null) {
|
||||
if (this.exportInfo === null) {
|
||||
toast.error('Export intent was not set, but export is being sent')
|
||||
console.error('Export intent was not set, but export is being sent')
|
||||
return
|
||||
}
|
||||
}
|
||||
const toastId = toast.loading(
|
||||
this.exportIntent === ExportIntent.Save
|
||||
this.exportInfo.intent === ExportIntent.Save
|
||||
? EXPORT_TOAST_MESSAGES.START
|
||||
: MAKE_TOAST_MESSAGES.START
|
||||
)
|
||||
@ -1975,7 +1991,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
resolve(passThrough)
|
||||
},
|
||||
reject: (reason: string) => {
|
||||
this.exportIntent = null
|
||||
this.exportInfo = null
|
||||
reject(reason)
|
||||
},
|
||||
commandId: command.cmd_id,
|
||||
|
@ -18,7 +18,7 @@ class FileSystemManager {
|
||||
return Promise.resolve(window.electron.path.join(dir, path))
|
||||
}
|
||||
|
||||
async readFile(path: string): Promise<Uint8Array | void> {
|
||||
async readFile(path: string): Promise<Uint8Array> {
|
||||
// Using local file system only works from desktop.
|
||||
if (!isDesktop()) {
|
||||
return Promise.reject(
|
||||
|
@ -64,7 +64,7 @@ const ARC_SEGMENT_ERR = new Error('Invalid input, expected "arc-segment"')
|
||||
export type Coords2d = [number, number]
|
||||
|
||||
export function getCoordsFromPaths(skGroup: Sketch, index = 0): Coords2d {
|
||||
const currentPath = skGroup?.value?.[index]
|
||||
const currentPath = skGroup?.paths?.[index]
|
||||
if (!currentPath && skGroup?.start) {
|
||||
return skGroup.start.to
|
||||
} else if (!currentPath) {
|
||||
@ -1704,7 +1704,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
varName
|
||||
)
|
||||
if (err(sketch)) return sketch
|
||||
const intersectPath = sketch.value.find(
|
||||
const intersectPath = sketch.paths.find(
|
||||
({ tag }: Path) => tag && tag.value === intersectTagName
|
||||
)
|
||||
let offset = 0
|
||||
|
@ -18,7 +18,7 @@ export function getSketchSegmentFromPathToNode(
|
||||
pathToNode: PathToNode
|
||||
):
|
||||
| {
|
||||
segment: Sketch['value'][number]
|
||||
segment: Sketch['paths'][number]
|
||||
index: number
|
||||
}
|
||||
| Error {
|
||||
@ -39,15 +39,15 @@ export function getSketchSegmentFromSourceRange(
|
||||
[rangeStart, rangeEnd]: SourceRange
|
||||
):
|
||||
| {
|
||||
segment: Sketch['value'][number]
|
||||
segment: Sketch['paths'][number]
|
||||
index: number
|
||||
}
|
||||
| Error {
|
||||
const lineIndex = sketch.value.findIndex(
|
||||
const lineIndex = sketch.paths.findIndex(
|
||||
({ __geoMeta: { sourceRange } }: Path) =>
|
||||
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
|
||||
)
|
||||
const line = sketch.value[lineIndex]
|
||||
const line = sketch.paths[lineIndex]
|
||||
if (line) {
|
||||
return {
|
||||
segment: line,
|
||||
|
@ -1732,7 +1732,7 @@ export function transformAstSketchLines({
|
||||
if (err(_segment)) return _segment
|
||||
referencedSegment = _segment.segment
|
||||
} else {
|
||||
referencedSegment = sketch.value.find(
|
||||
referencedSegment = sketch.paths.find(
|
||||
(path) => path.tag?.value === _referencedSegmentName
|
||||
)
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
||||
|
||||
type OutputFormat = Models['OutputFormat_type']
|
||||
@ -187,27 +186,41 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
machine.make_model.model ||
|
||||
machine.make_model.manufacturer ||
|
||||
'Unknown Machine',
|
||||
options: () => {
|
||||
return Object.entries(machineManager.machines).map(
|
||||
([_, machine]) => ({
|
||||
name:
|
||||
`${machine.id} (${
|
||||
machine.make_model.model || machine.make_model.manufacturer
|
||||
}) (${machine.state.state})` +
|
||||
(machine.extra &&
|
||||
machine.extra.type === 'bambu' &&
|
||||
machine.extra.nozzle_diameter
|
||||
? ` - Nozzle Diameter: ${machine.extra.nozzle_diameter}`
|
||||
: ''),
|
||||
isCurrent: false,
|
||||
disabled: machine.state.state !== 'idle',
|
||||
value: machine as components['schemas']['MachineInfoResponse'],
|
||||
})
|
||||
)
|
||||
},
|
||||
defaultValue: () => {
|
||||
options: (commandBarContext) => {
|
||||
return Object.values(
|
||||
machineManager.machines
|
||||
commandBarContext.machineManager?.machines || []
|
||||
).map((machine: components['schemas']['MachineInfoResponse']) => ({
|
||||
name:
|
||||
`${machine.id} (${
|
||||
machine.make_model.model || machine.make_model.manufacturer
|
||||
}) (${machine.state.state})` +
|
||||
(machine.hardware_configuration &&
|
||||
machine.hardware_configuration.type !== 'none' &&
|
||||
machine.hardware_configuration.config.nozzle_diameter
|
||||
? ` - Nozzle Diameter: ${machine.hardware_configuration.config.nozzle_diameter}`
|
||||
: '') +
|
||||
(machine.hardware_configuration &&
|
||||
machine.hardware_configuration.type !== 'none' &&
|
||||
machine.hardware_configuration.config.filaments &&
|
||||
machine.hardware_configuration.config.filaments[0]
|
||||
? ` - ${
|
||||
machine.hardware_configuration.config.filaments[0].name
|
||||
} #${
|
||||
machine.hardware_configuration.config &&
|
||||
machine.hardware_configuration.config.filaments[0].color?.slice(
|
||||
0,
|
||||
6
|
||||
)
|
||||
}`
|
||||
: ''),
|
||||
isCurrent: false,
|
||||
disabled: machine.state.state !== 'idle',
|
||||
value: machine,
|
||||
}))
|
||||
},
|
||||
defaultValue: (commandBarContext) => {
|
||||
return Object.values(
|
||||
commandBarContext.machineManager.machines || []
|
||||
)[0] as components['schemas']['MachineInfoResponse']
|
||||
},
|
||||
},
|
||||
|
@ -5,6 +5,7 @@ import { Selection } from './selections'
|
||||
import { Identifier, Expr, VariableDeclaration } from 'lang/wasm'
|
||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
||||
import { ReactNode } from 'react'
|
||||
import { MachineManager } from 'components/MachineManagerProvider'
|
||||
|
||||
type Icon = CustomIconName
|
||||
const PLATFORMS = ['both', 'web', 'desktop'] as const
|
||||
@ -127,6 +128,7 @@ export type CommandArgumentConfig<
|
||||
| ((
|
||||
commandBarContext: {
|
||||
argumentsToSubmit: Record<string, unknown>
|
||||
machineManager?: MachineManager
|
||||
}, // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||
machineContext?: C
|
||||
) => CommandArgumentOption<OutputType>[])
|
||||
@ -258,5 +260,6 @@ export type CommandArgumentWithName<
|
||||
export type CommandArgumentOption<A> = {
|
||||
name: string
|
||||
isCurrent?: boolean
|
||||
disabled?: boolean
|
||||
value: A
|
||||
}
|
||||
|
@ -92,6 +92,7 @@ export const MAKE_TOAST_MESSAGES = {
|
||||
NO_MACHINE_API_IP: 'No machine api ip available',
|
||||
NO_CURRENT_MACHINE: 'No current machine available',
|
||||
NO_MACHINE_ID: 'No machine id available',
|
||||
NO_NAME: 'No name provided',
|
||||
ERROR_STARTING_PRINT: 'Error while starting print',
|
||||
SUCCESS: 'Started print successfully',
|
||||
}
|
||||
|
@ -448,7 +448,9 @@ export const readProjectSettingsFile = async (
|
||||
}
|
||||
}
|
||||
|
||||
const configToml = await window.electron.readFile(settingsPath)
|
||||
const configToml = await window.electron.readFile(settingsPath, {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
const configObj = parseProjectSettings(configToml)
|
||||
if (err(configObj)) {
|
||||
return Promise.reject(configObj)
|
||||
@ -467,7 +469,9 @@ export const readAppSettingsFile = async () => {
|
||||
|
||||
// The file exists, read it and parse it.
|
||||
if (window.electron.exists(settingsPath)) {
|
||||
const configToml = await window.electron.readFile(settingsPath)
|
||||
const configToml = await window.electron.readFile(settingsPath, {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
const parsedAppConfig = parseAppSettings(configToml)
|
||||
if (err(parsedAppConfig)) {
|
||||
return Promise.reject(parsedAppConfig)
|
||||
@ -527,7 +531,9 @@ export const readTokenFile = async () => {
|
||||
let settingsPath = await getTokenFilePath()
|
||||
|
||||
if (window.electron.exists(settingsPath)) {
|
||||
const token: string = await window.electron.readFile(settingsPath)
|
||||
const token: string = await window.electron.readFile(settingsPath, {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
if (!token) return ''
|
||||
|
||||
return token
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { deserialize_files } from 'wasm-lib/pkg/wasm_lib'
|
||||
import { machineManager } from './machineManager'
|
||||
import { MachineManager } from 'components/MachineManagerProvider'
|
||||
import toast from 'react-hot-toast'
|
||||
import { components } from './machine-api'
|
||||
import ModelingAppFile from './modelingAppFile'
|
||||
@ -8,9 +8,17 @@ import { MAKE_TOAST_MESSAGES } from './constants'
|
||||
// Make files locally from an export call.
|
||||
export async function exportMake(
|
||||
data: ArrayBuffer,
|
||||
toastId: string
|
||||
name: string,
|
||||
toastId: string,
|
||||
machineManager: MachineManager
|
||||
): Promise<Response | null> {
|
||||
if (machineManager.machineCount() === 0) {
|
||||
if (name === '') {
|
||||
console.error(MAKE_TOAST_MESSAGES.NO_NAME)
|
||||
toast.error(MAKE_TOAST_MESSAGES.NO_NAME, { id: toastId })
|
||||
return null
|
||||
}
|
||||
|
||||
if (machineManager.machines.length === 0) {
|
||||
console.error(MAKE_TOAST_MESSAGES.NO_MACHINES)
|
||||
toast.error(MAKE_TOAST_MESSAGES.NO_MACHINES, { id: toastId })
|
||||
return null
|
||||
@ -39,7 +47,7 @@ export async function exportMake(
|
||||
|
||||
const params: components['schemas']['PrintParameters'] = {
|
||||
machine_id: machineId,
|
||||
job_name: 'Exported Job', // TODO: make this the project name.
|
||||
job_name: name,
|
||||
}
|
||||
try {
|
||||
console.log('params', params)
|
||||
|
9
src/lib/machine-api.d.ts
vendored
@ -138,6 +138,11 @@ export interface components {
|
||||
FdmHardwareConfiguration: {
|
||||
/** @description The filaments the printer has access to. */
|
||||
filaments: components['schemas']['Filament'][]
|
||||
/**
|
||||
* Format: uint
|
||||
* @description The currently loaded filament index.
|
||||
*/
|
||||
loaded_filament_idx?: number | null
|
||||
/**
|
||||
* Format: double
|
||||
* @description Diameter of the extrusion nozzle, in mm.
|
||||
@ -191,6 +196,10 @@ export interface components {
|
||||
/** @enum {string} */
|
||||
type: 'composite'
|
||||
}
|
||||
| {
|
||||
/** @enum {string} */
|
||||
type: 'unknown'
|
||||
}
|
||||
/** @description The hardware configuration of a machine. */
|
||||
HardwareConfiguration:
|
||||
| {
|
||||
|
@ -1,105 +0,0 @@
|
||||
import { isDesktop } from './isDesktop'
|
||||
import { components } from './machine-api'
|
||||
import { reportRejection } from './trap'
|
||||
import { toSync } from './utils'
|
||||
|
||||
export type MachinesListing = Array<
|
||||
components['schemas']['MachineInfoResponse']
|
||||
>
|
||||
|
||||
export class MachineManager {
|
||||
private _isDesktop: boolean = isDesktop()
|
||||
private _machines: MachinesListing = []
|
||||
private _machineApiIp: string | null = null
|
||||
private _currentMachine: components['schemas']['MachineInfoResponse'] | null =
|
||||
null
|
||||
|
||||
constructor() {
|
||||
if (!this._isDesktop) {
|
||||
return
|
||||
}
|
||||
|
||||
this.updateMachines().catch(reportRejection)
|
||||
}
|
||||
|
||||
start() {
|
||||
if (!this._isDesktop) {
|
||||
return
|
||||
}
|
||||
|
||||
// Start a background job to update the machines every ten seconds.
|
||||
// If MDNS is already watching, this timeout will wait until it's done to trigger the
|
||||
// finding again.
|
||||
let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
const timeoutLoop = () => {
|
||||
clearTimeout(timeoutId)
|
||||
timeoutId = setTimeout(
|
||||
toSync(async () => {
|
||||
await this.updateMachineApiIp()
|
||||
await this.updateMachines()
|
||||
timeoutLoop()
|
||||
}, reportRejection),
|
||||
10000
|
||||
)
|
||||
}
|
||||
timeoutLoop()
|
||||
}
|
||||
|
||||
get machines(): MachinesListing {
|
||||
return this._machines
|
||||
}
|
||||
|
||||
machineCount(): number {
|
||||
return this._machines.length
|
||||
}
|
||||
|
||||
get machineApiIp(): string | null {
|
||||
return this._machineApiIp
|
||||
}
|
||||
|
||||
// Get the reason message for why there are no machines.
|
||||
noMachinesReason(): string | undefined {
|
||||
if (this.machineCount() > 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (this.machineApiIp === null) {
|
||||
return 'Machine API server was not discovered'
|
||||
}
|
||||
|
||||
return 'Machine API server was discovered, but no machines are available'
|
||||
}
|
||||
|
||||
get currentMachine(): components['schemas']['MachineInfoResponse'] | null {
|
||||
return this._currentMachine
|
||||
}
|
||||
|
||||
set currentMachine(
|
||||
machine: components['schemas']['MachineInfoResponse'] | null
|
||||
) {
|
||||
this._currentMachine = machine
|
||||
}
|
||||
|
||||
private async updateMachines(): Promise<void> {
|
||||
if (!this._isDesktop) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this._machineApiIp === null) {
|
||||
return
|
||||
}
|
||||
|
||||
this._machines = await window.electron.listMachines(this._machineApiIp)
|
||||
}
|
||||
|
||||
private async updateMachineApiIp(): Promise<void> {
|
||||
if (!this._isDesktop) {
|
||||
return
|
||||
}
|
||||
|
||||
this._machineApiIp = await window.electron.getMachineApiIp()
|
||||
}
|
||||
}
|
||||
|
||||
export const machineManager = new MachineManager()
|
||||
machineManager.start()
|
@ -109,7 +109,9 @@ export const fileLoader: LoaderFunction = async (
|
||||
)
|
||||
}
|
||||
|
||||
code = await window.electron.readFile(currentFilePath)
|
||||
code = await window.electron.readFile(currentFilePath, {
|
||||
encoding: 'utf-8',
|
||||
})
|
||||
code = normalizeLineEndings(code)
|
||||
|
||||
// Update both the state and the editor's code.
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
} from 'lib/commandTypes'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
|
||||
import { MachineManager } from 'components/MachineManagerProvider'
|
||||
|
||||
export type CommandBarContext = {
|
||||
commands: Command[]
|
||||
@ -14,6 +15,7 @@ export type CommandBarContext = {
|
||||
currentArgument?: CommandArgument<unknown> & { name: string }
|
||||
selectionRanges: Selections
|
||||
argumentsToSubmit: { [x: string]: unknown }
|
||||
machineManager: MachineManager
|
||||
}
|
||||
|
||||
export type CommandBarMachineEvent =
|
||||
@ -71,6 +73,7 @@ export type CommandBarMachineEvent =
|
||||
type: 'Change current argument'
|
||||
data: { [x: string]: CommandArgumentWithName<unknown> }
|
||||
}
|
||||
| { type: 'Set machine manager'; data: MachineManager }
|
||||
|
||||
export const commandBarMachine = setup({
|
||||
types: {
|
||||
@ -90,6 +93,12 @@ export const commandBarMachine = setup({
|
||||
}
|
||||
},
|
||||
}),
|
||||
'Set machine manager': assign({
|
||||
machineManager: ({ event, context }) => {
|
||||
if (event.type !== 'Set machine manager') return context.machineManager
|
||||
return event.data
|
||||
},
|
||||
}),
|
||||
'Execute command': ({ context, event }) => {
|
||||
const { selectedCommand } = context
|
||||
if (!selectedCommand) return
|
||||
@ -339,6 +348,13 @@ export const commandBarMachine = setup({
|
||||
codeBasedSelections: [],
|
||||
},
|
||||
argumentsToSubmit: {},
|
||||
machineManager: {
|
||||
machines: [],
|
||||
machineApiIp: null,
|
||||
currentMachine: null,
|
||||
setCurrentMachine: () => {},
|
||||
noMachinesReason: () => undefined,
|
||||
},
|
||||
},
|
||||
id: 'Command Bar',
|
||||
initial: 'Closed',
|
||||
@ -520,6 +536,11 @@ export const commandBarMachine = setup({
|
||||
},
|
||||
},
|
||||
on: {
|
||||
'Set machine manager': {
|
||||
reenter: false,
|
||||
actions: 'Set machine manager',
|
||||
},
|
||||
|
||||
Close: {
|
||||
target: '.Closed',
|
||||
},
|
||||
|
@ -64,6 +64,7 @@ import toast from 'react-hot-toast'
|
||||
import { ToolbarModeName } from 'lib/toolbar'
|
||||
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
||||
import { Vector3 } from 'three'
|
||||
import { MachineManager } from 'components/MachineManagerProvider'
|
||||
|
||||
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
||||
|
||||
@ -301,6 +302,7 @@ export const getPersistedContext = (): Partial<PersistedModelingContext> => {
|
||||
export interface ModelingMachineContext {
|
||||
currentMode: ToolbarModeName
|
||||
currentTool: SketchTool
|
||||
machineManager: MachineManager
|
||||
selection: string[]
|
||||
selectionRanges: Selections
|
||||
sketchDetails: SketchDetails | null
|
||||
@ -315,6 +317,13 @@ export interface ModelingMachineContext {
|
||||
export const modelingMachineDefaultContext: ModelingMachineContext = {
|
||||
currentMode: 'modeling',
|
||||
currentTool: 'none',
|
||||
machineManager: {
|
||||
machines: [],
|
||||
machineApiIp: null,
|
||||
currentMachine: null,
|
||||
setCurrentMachine: () => {},
|
||||
noMachinesReason: () => undefined,
|
||||
},
|
||||
selection: [],
|
||||
selectionRanges: {
|
||||
otherSelections: [],
|
||||
|
@ -4,7 +4,7 @@ import fs from 'node:fs/promises'
|
||||
import os from 'node:os'
|
||||
import fsSync from 'node:fs'
|
||||
import packageJson from '../package.json'
|
||||
import { MachinesListing } from 'lib/machineManager'
|
||||
import { MachinesListing } from 'components/MachineManagerProvider'
|
||||
import chokidar from 'chokidar'
|
||||
|
||||
const open = (args: any) => ipcRenderer.invoke('dialog.showOpenDialog', args)
|
||||
@ -74,7 +74,7 @@ const watchFileOff = (path: string, key: string) => {
|
||||
fsWatchListeners.set(path, watchers)
|
||||
}
|
||||
}
|
||||
const readFile = (path: string) => fs.readFile(path, 'utf-8')
|
||||
const readFile = fs.readFile
|
||||
// It seems like from the node source code this does not actually block but also
|
||||
// don't trust me on that (jess).
|
||||
const exists = (path: string) => fsSync.existsSync(path)
|
||||
|
@ -23,6 +23,9 @@ import { codeManager, editorManager, kclManager } from 'lib/singletons'
|
||||
import { bracket } from 'lib/exampleKcl'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { EngineConnectionStateType } from 'lang/std/engineConnection'
|
||||
|
||||
export const kbdClasses =
|
||||
'py-0.5 px-1 text-sm rounded bg-chalkboard-10 dark:bg-chalkboard-100 border border-chalkboard-50 border-b-2'
|
||||
@ -80,8 +83,20 @@ export const onboardingRoutes = [
|
||||
]
|
||||
|
||||
export function useDemoCode() {
|
||||
const { overallState, immediateState } = useNetworkContext()
|
||||
|
||||
useEffect(() => {
|
||||
if (!editorManager.editorView || codeManager.code === bracket) return
|
||||
// Don't run if the editor isn't loaded or the code is already the bracket
|
||||
if (!editorManager.editorView || codeManager.code === bracket) {
|
||||
return
|
||||
}
|
||||
// Don't run if the network isn't healthy or the connection isn't established
|
||||
if (
|
||||
overallState !== NetworkHealthState.Ok ||
|
||||
immediateState.type !== EngineConnectionStateType.ConnectionEstablished
|
||||
) {
|
||||
return
|
||||
}
|
||||
setTimeout(
|
||||
toSync(async () => {
|
||||
codeManager.updateCodeStateEditor(bracket)
|
||||
@ -89,7 +104,7 @@ export function useDemoCode() {
|
||||
await codeManager.writeToFile()
|
||||
}, reportRejection)
|
||||
)
|
||||
}, [editorManager.editorView])
|
||||
}, [editorManager.editorView, immediateState, overallState])
|
||||
}
|
||||
|
||||
export function useNextClick(newStatus: string) {
|
||||
|
128
src/wasm-lib/Cargo.lock
generated
@ -121,9 +121,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.89"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
@ -176,7 +176,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -187,7 +187,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -204,7 +204,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -465,7 +465,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -656,7 +656,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -667,7 +667,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -722,7 +722,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@ -751,7 +751,7 @@ dependencies = [
|
||||
"rustfmt-wrapper",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -762,7 +762,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -789,7 +789,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -827,7 +827,7 @@ checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -988,7 +988,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1084,7 +1084,7 @@ dependencies = [
|
||||
"inflections",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1542,7 +1542,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.2.22"
|
||||
version = "0.2.23"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx 0.5.1",
|
||||
@ -1612,12 +1612,12 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kcl-test-server"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"hyper 0.14.30",
|
||||
@ -1644,9 +1644,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad"
|
||||
version = "0.3.23"
|
||||
version = "0.3.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71b6f0c34165939697548dd0c94221200dbb8b5d1c84b5d8e803e70f9f720ea7"
|
||||
checksum = "f6359cc0a1bbccbcf78775eea17a033cf2aa89d3fe6a9784f8ce94e5f882c185"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1684,9 +1684,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-modeling-cmds"
|
||||
version = "0.2.68"
|
||||
version = "0.2.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3aedfcc1d8ea9995ec3eb78a6743c585c9380475c48701797f107489b696aa"
|
||||
checksum = "c6d2160dcb0e5373b1242a760dbf17fb9c12de523c410c11b145381c852b377b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -1716,7 +1716,7 @@ dependencies = [
|
||||
"kittycad-modeling-cmds-macros-impl",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1727,7 +1727,7 @@ checksum = "6607507a8a0e4273b943179f0a3ef8e90712308d1d3095246040c29cfdbf985b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2112,7 +2112,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.8.5",
|
||||
"structmeta",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2126,7 +2126,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.8.5",
|
||||
"structmeta",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2166,7 +2166,7 @@ dependencies = [
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2224,7 +2224,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2337,9 +2337,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.88"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -2391,7 +2391,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2404,7 +2404,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2586,9 +2586,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.0"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@ -2925,7 +2925,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2965,9 +2965,9 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.210"
|
||||
version = "1.0.213"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
|
||||
checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -2983,13 +2983,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.210"
|
||||
version = "1.0.213"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
|
||||
checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3000,14 +3000,14 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.128"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
dependencies = [
|
||||
"indexmap 2.6.0",
|
||||
"itoa",
|
||||
@ -3024,7 +3024,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3045,7 +3045,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3182,7 +3182,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3193,7 +3193,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3215,7 +3215,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3237,9 +3237,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.79"
|
||||
version = "2.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590"
|
||||
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3263,7 +3263,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3326,22 +3326,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.64"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
|
||||
checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.64"
|
||||
version = "1.0.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
|
||||
checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3437,7 +3437,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3579,7 +3579,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3607,7 +3607,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3689,7 +3689,7 @@ checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@ -3865,7 +3865,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3927,7 +3927,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -3962,7 +3962,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -4328,7 +4328,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.79",
|
||||
"syn 2.0.85",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -71,8 +71,8 @@ members = [
|
||||
|
||||
[workspace.dependencies]
|
||||
http = "1"
|
||||
kittycad = { version = "0.3.23", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-modeling-cmds = { version = "0.2.68", features = ["websocket"] }
|
||||
kittycad = { version = "0.3.25", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-modeling-cmds = { version = "0.2.71", features = ["websocket"] }
|
||||
|
||||
[[test]]
|
||||
name = "executor"
|
||||
@ -83,6 +83,6 @@ name = "modify"
|
||||
path = "tests/modify/main.rs"
|
||||
|
||||
# Example: how to point modeling-api at a different repo (e.g. a branch or a local clone)
|
||||
#[patch."https://github.com/KittyCAD/modeling-api"]
|
||||
#[patch.crates-io]
|
||||
#kittycad-modeling-cmds = { path = "../../../modeling-api/modeling-cmds" }
|
||||
#kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }
|
||||
|
@ -17,13 +17,13 @@ convert_case = "0.6.0"
|
||||
once_cell = "1.20.2"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
regex = "1.10"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
regex = "1.11"
|
||||
serde = { version = "1.0.213", features = ["derive"] }
|
||||
serde_tokenstream = "0.2"
|
||||
syn = { version = "2.0.79", features = ["full"] }
|
||||
syn = { version = "2.0.85", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.89"
|
||||
anyhow = "1.0.91"
|
||||
expectorate = "1.1.0"
|
||||
pretty_assertions = "1.4.1"
|
||||
rustfmt-wrapper = "0.2.1"
|
||||
|
@ -15,7 +15,7 @@ databake = "0.1.8"
|
||||
kcl-lib = { path = "../kcl" }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "2.0.79", features = ["full"] }
|
||||
syn = { version = "2.0.85", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.1"
|
||||
|
@ -12,7 +12,7 @@ fn basic() {
|
||||
let expected = Program {
|
||||
start: 0,
|
||||
end: 11,
|
||||
body: vec![BodyItem::VariableDeclaration(VariableDeclaration {
|
||||
body: vec![BodyItem::VariableDeclaration(Box::new(VariableDeclaration {
|
||||
start: 0,
|
||||
end: 11,
|
||||
declarations: vec![VariableDeclarator {
|
||||
@ -36,7 +36,7 @@ fn basic() {
|
||||
visibility: ItemVisibility::Default,
|
||||
kind: VariableKind::Const,
|
||||
digest: None,
|
||||
})],
|
||||
}))],
|
||||
non_code_meta: NonCodeMeta::default(),
|
||||
digest: None,
|
||||
};
|
||||
|
@ -1,15 +1,15 @@
|
||||
[package]
|
||||
name = "kcl-test-server"
|
||||
description = "A test server for KCL"
|
||||
version = "0.1.14"
|
||||
version = "0.1.15"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.89"
|
||||
anyhow = "1.0.91"
|
||||
hyper = { version = "0.14.29", features = ["http1", "server", "tcp"] }
|
||||
kcl-lib = { version = "0.2", path = "../kcl" }
|
||||
pico-args = "0.5.0"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde = { version = "1.0.213", features = ["derive"] }
|
||||
serde_json = "1.0.128"
|
||||
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
|
||||
|
@ -31,7 +31,7 @@ pub struct ServerArgs {
|
||||
/// Where to find the engine.
|
||||
/// If none, uses the prod engine.
|
||||
/// This is useful for testing a local engine instance.
|
||||
/// Overridden by the $LOCAL_ENGINE_ADDR environment variable.
|
||||
/// Overridden by the $ZOO_HOST environment variable.
|
||||
pub engine_address: Option<String>,
|
||||
}
|
||||
|
||||
@ -44,8 +44,8 @@ impl ServerArgs {
|
||||
num_engine_conns: pargs.opt_value_from_str("--num-engine-conns")?.unwrap_or(1),
|
||||
engine_address: pargs.opt_value_from_str("--engine-address")?,
|
||||
};
|
||||
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
||||
println!("Overriding engine address via $LOCAL_ENGINE_ADDR");
|
||||
if let Ok(addr) = std::env::var("ZOO_HOST") {
|
||||
println!("Overriding engine address via $ZOO_HOST");
|
||||
args.engine_address = Some(addr);
|
||||
}
|
||||
println!("Config is {args:?}");
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-lib"
|
||||
description = "KittyCAD Language implementation and tools"
|
||||
version = "0.2.22"
|
||||
version = "0.2.23"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
@ -11,7 +11,7 @@ keywords = ["kcl", "KittyCAD", "CAD"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.89", features = ["backtrace"] }
|
||||
anyhow = { version = "1.0.91", features = ["backtrace"] }
|
||||
async-recursion = "1.1.1"
|
||||
async-trait = "0.1.83"
|
||||
base64 = "0.22.1"
|
||||
@ -38,11 +38,11 @@ pyo3 = { version = "0.22.5", optional = true }
|
||||
reqwest = { version = "0.12", default-features = false, features = ["stream", "rustls-tls"] }
|
||||
ropey = "1.6.1"
|
||||
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1", "preserve_order"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde = { version = "1.0.213", features = ["derive"] }
|
||||
serde_json = "1.0.128"
|
||||
sha2 = "0.10.8"
|
||||
tabled = { version = "0.15.0", optional = true }
|
||||
thiserror = "1.0.64"
|
||||
thiserror = "1.0.65"
|
||||
toml = "0.8.19"
|
||||
ts-rs = { version = "10.0.0", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings", "serde-json-impl"] }
|
||||
url = { version = "2.5.2", features = ["serde"] }
|
||||
|
@ -454,7 +454,7 @@ pub(crate) use impl_value_meta;
|
||||
pub enum BodyItem {
|
||||
ImportStatement(Box<ImportStatement>),
|
||||
ExpressionStatement(ExpressionStatement),
|
||||
VariableDeclaration(VariableDeclaration),
|
||||
VariableDeclaration(Box<VariableDeclaration>),
|
||||
ReturnStatement(ReturnStatement),
|
||||
}
|
||||
|
||||
@ -494,7 +494,7 @@ impl From<&BodyItem> for SourceRange {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
#[serde(untagged)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Expr {
|
||||
Literal(Box<Literal>),
|
||||
Identifier(Box<Identifier>),
|
||||
@ -2719,7 +2719,7 @@ pub struct FunctionExpression {
|
||||
impl_value_meta!(FunctionExpression);
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct RequiredParamAfterOptionalParam(pub Parameter);
|
||||
pub struct RequiredParamAfterOptionalParam(pub Box<Parameter>);
|
||||
|
||||
impl std::fmt::Display for RequiredParamAfterOptionalParam {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
@ -2751,7 +2751,7 @@ impl FunctionExpression {
|
||||
if param.optional {
|
||||
found_optional = true;
|
||||
} else if found_optional {
|
||||
return Err(RequiredParamAfterOptionalParam(param.clone()));
|
||||
return Err(RequiredParamAfterOptionalParam(Box::new(param.clone())));
|
||||
}
|
||||
}
|
||||
let boundary = self.params.partition_point(|param| !param.optional);
|
||||
|
@ -551,13 +551,13 @@ impl ArrayRangeExpression {
|
||||
.execute_expr(&self.start_element, exec_state, &metadata, StatementKind::Expression)
|
||||
.await?
|
||||
.get_json_value()?;
|
||||
let start = parse_json_number_as_u64(&start, (&*self.start_element).into())?;
|
||||
let start = parse_json_number_as_i64(&start, (&*self.start_element).into())?;
|
||||
let metadata = Metadata::from(&*self.end_element);
|
||||
let end = ctx
|
||||
.execute_expr(&self.end_element, exec_state, &metadata, StatementKind::Expression)
|
||||
.await?
|
||||
.get_json_value()?;
|
||||
let end = parse_json_number_as_u64(&end, (&*self.end_element).into())?;
|
||||
let end = parse_json_number_as_i64(&end, (&*self.end_element).into())?;
|
||||
|
||||
if end < start {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
@ -603,9 +603,9 @@ impl ObjectExpression {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_json_number_as_u64(j: &serde_json::Value, source_range: SourceRange) -> Result<u64, KclError> {
|
||||
fn parse_json_number_as_i64(j: &serde_json::Value, source_range: SourceRange) -> Result<i64, KclError> {
|
||||
if let serde_json::Value::Number(n) = &j {
|
||||
n.as_u64().ok_or_else(|| {
|
||||
n.as_i64().ok_or_else(|| {
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![source_range],
|
||||
message: format!("Invalid integer: {}", j),
|
||||
|
@ -1127,7 +1127,7 @@ pub struct TagEngineInfo {
|
||||
/// The sketch the tag is on.
|
||||
pub sketch: uuid::Uuid,
|
||||
/// The path the tag is on.
|
||||
pub path: Option<BasePath>,
|
||||
pub path: Option<Path>,
|
||||
/// The surface information for the tag.
|
||||
pub surface: Option<ExtrudeSurface>,
|
||||
}
|
||||
@ -1137,10 +1137,10 @@ pub struct TagEngineInfo {
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub struct Sketch {
|
||||
/// The id of the sketch (this will change when the engine's reference to it changes.
|
||||
/// The id of the sketch (this will change when the engine's reference to it changes).
|
||||
pub id: uuid::Uuid,
|
||||
/// The paths in the sketch.
|
||||
pub value: Vec<Path>,
|
||||
pub paths: Vec<Path>,
|
||||
/// What the sketch is on (can be a plane or a face).
|
||||
pub on: SketchSurface,
|
||||
/// The starting path.
|
||||
@ -1206,7 +1206,7 @@ impl Sketch {
|
||||
tag_identifier.info = Some(TagEngineInfo {
|
||||
id: base.geo_meta.id,
|
||||
sketch: self.id,
|
||||
path: Some(base.clone()),
|
||||
path: Some(current_path.clone()),
|
||||
surface: None,
|
||||
});
|
||||
|
||||
@ -1215,7 +1215,7 @@ impl Sketch {
|
||||
|
||||
/// Get the path most recently sketched.
|
||||
pub(crate) fn latest_path(&self) -> Option<&Path> {
|
||||
self.value.last()
|
||||
self.paths.last()
|
||||
}
|
||||
|
||||
/// The "pen" is an imaginary pen drawing the path.
|
||||
@ -1656,6 +1656,43 @@ pub enum Path {
|
||||
#[serde(flatten)]
|
||||
base: BasePath,
|
||||
},
|
||||
/// A circular arc, not necessarily tangential to the current point.
|
||||
Arc {
|
||||
#[serde(flatten)]
|
||||
base: BasePath,
|
||||
/// Center of the circle that this arc is drawn on.
|
||||
center: [f64; 2],
|
||||
/// Radius of the circle that this arc is drawn on.
|
||||
radius: f64,
|
||||
},
|
||||
}
|
||||
|
||||
/// What kind of path is this?
|
||||
#[derive(Display)]
|
||||
enum PathType {
|
||||
ToPoint,
|
||||
Base,
|
||||
TangentialArc,
|
||||
TangentialArcTo,
|
||||
Circle,
|
||||
Horizontal,
|
||||
AngledLineTo,
|
||||
Arc,
|
||||
}
|
||||
|
||||
impl From<&Path> for PathType {
|
||||
fn from(value: &Path) -> Self {
|
||||
match value {
|
||||
Path::ToPoint { .. } => Self::ToPoint,
|
||||
Path::TangentialArcTo { .. } => Self::TangentialArcTo,
|
||||
Path::TangentialArc { .. } => Self::TangentialArc,
|
||||
Path::Circle { .. } => Self::Circle,
|
||||
Path::Horizontal { .. } => Self::Horizontal,
|
||||
Path::AngledLineTo { .. } => Self::AngledLineTo,
|
||||
Path::Base { .. } => Self::Base,
|
||||
Path::Arc { .. } => Self::Arc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Path {
|
||||
@ -1668,6 +1705,7 @@ impl Path {
|
||||
Path::TangentialArcTo { base, .. } => base.geo_meta.id,
|
||||
Path::TangentialArc { base, .. } => base.geo_meta.id,
|
||||
Path::Circle { base, .. } => base.geo_meta.id,
|
||||
Path::Arc { base, .. } => base.geo_meta.id,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1680,6 +1718,7 @@ impl Path {
|
||||
Path::TangentialArcTo { base, .. } => base.tag.clone(),
|
||||
Path::TangentialArc { base, .. } => base.tag.clone(),
|
||||
Path::Circle { base, .. } => base.tag.clone(),
|
||||
Path::Arc { base, .. } => base.tag.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1692,6 +1731,47 @@ impl Path {
|
||||
Path::TangentialArcTo { base, .. } => base,
|
||||
Path::TangentialArc { base, .. } => base,
|
||||
Path::Circle { base, .. } => base,
|
||||
Path::Arc { base, .. } => base,
|
||||
}
|
||||
}
|
||||
|
||||
/// Where does this path segment start?
|
||||
pub fn get_from(&self) -> &[f64; 2] {
|
||||
&self.get_base().from
|
||||
}
|
||||
/// Where does this path segment end?
|
||||
pub fn get_to(&self) -> &[f64; 2] {
|
||||
&self.get_base().to
|
||||
}
|
||||
|
||||
/// Length of this path segment, in cartesian plane.
|
||||
pub fn length(&self) -> f64 {
|
||||
match self {
|
||||
Self::ToPoint { .. } | Self::Base { .. } | Self::Horizontal { .. } | Self::AngledLineTo { .. } => {
|
||||
linear_distance(self.get_from(), self.get_to())
|
||||
}
|
||||
Self::TangentialArc {
|
||||
base: _,
|
||||
center,
|
||||
ccw: _,
|
||||
}
|
||||
| Self::TangentialArcTo {
|
||||
base: _,
|
||||
center,
|
||||
ccw: _,
|
||||
} => {
|
||||
// The radius can be calculated as the linear distance between `to` and `center`,
|
||||
// or between `from` and `center`. They should be the same.
|
||||
let radius = linear_distance(self.get_from(), center);
|
||||
debug_assert_eq!(radius, linear_distance(self.get_to(), center));
|
||||
// TODO: Call engine utils to figure this out.
|
||||
linear_distance(self.get_from(), self.get_to())
|
||||
}
|
||||
Self::Circle { radius, .. } => 2.0 * std::f64::consts::PI * radius,
|
||||
Self::Arc { .. } => {
|
||||
// TODO: Call engine utils to figure this out.
|
||||
linear_distance(self.get_from(), self.get_to())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1704,10 +1784,22 @@ impl Path {
|
||||
Path::TangentialArcTo { base, .. } => Some(base),
|
||||
Path::TangentialArc { base, .. } => Some(base),
|
||||
Path::Circle { base, .. } => Some(base),
|
||||
Path::Arc { base, .. } => Some(base),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the straight-line distance between a pair of (2D) points.
|
||||
#[rustfmt::skip]
|
||||
fn linear_distance(
|
||||
[x0, y0]: &[f64; 2],
|
||||
[x1, y1]: &[f64; 2]
|
||||
) -> f64 {
|
||||
let y_sq = (y1 - y0).powi(2);
|
||||
let x_sq = (x1 - x0).powi(2);
|
||||
(y_sq + x_sq).sqrt()
|
||||
}
|
||||
|
||||
/// An extrude surface.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -1888,9 +1980,73 @@ impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new zoo api client.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new_zoo_client(token: Option<String>, engine_addr: Option<String>) -> Result<kittycad::Client> {
|
||||
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 zoo_token_env = std::env::var("ZOO_API_TOKEN");
|
||||
|
||||
let token = if let Some(token) = token {
|
||||
token
|
||||
} else if let Ok(token) = std::env::var("KITTYCAD_API_TOKEN") {
|
||||
if let Ok(zoo_token) = zoo_token_env {
|
||||
if zoo_token != token {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Both environment variables KITTYCAD_API_TOKEN=`{}` and ZOO_API_TOKEN=`{}` are set. Use only one.",
|
||||
token,
|
||||
zoo_token
|
||||
));
|
||||
}
|
||||
}
|
||||
token
|
||||
} else if let Ok(token) = zoo_token_env {
|
||||
token
|
||||
} else {
|
||||
return Err(anyhow::anyhow!(
|
||||
"No API token found in environment variables. Use KITTYCAD_API_TOKEN or ZOO_API_TOKEN"
|
||||
));
|
||||
};
|
||||
|
||||
// Create the client.
|
||||
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
||||
// Set an engine address if it's set.
|
||||
let kittycad_host_env = std::env::var("KITTYCAD_HOST");
|
||||
if let Some(addr) = engine_addr {
|
||||
client.set_base_url(addr);
|
||||
} else if let Ok(addr) = std::env::var("ZOO_HOST") {
|
||||
if let Ok(kittycad_host) = kittycad_host_env {
|
||||
if kittycad_host != addr {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Both environment variables KITTYCAD_HOST=`{}` and ZOO_HOST=`{}` are set. Use only one.",
|
||||
kittycad_host,
|
||||
addr
|
||||
));
|
||||
}
|
||||
}
|
||||
client.set_base_url(addr);
|
||||
} else if let Ok(addr) = kittycad_host_env {
|
||||
client.set_base_url(addr);
|
||||
}
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
impl ExecutorContext {
|
||||
/// Create a new default executor context.
|
||||
/// Also returns the response HTTP headers from the server.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn new(client: &kittycad::Client, settings: ExecutorSettings) -> Result<Self> {
|
||||
let (ws, _headers) = client
|
||||
@ -1935,6 +2091,35 @@ impl ExecutorContext {
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new default executor context.
|
||||
/// With a kittycad client.
|
||||
/// This allows for passing in `ZOO_API_TOKEN` and `ZOO_HOST` as environment
|
||||
/// variables.
|
||||
/// But also allows for passing in a token and engine address directly.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn new_with_client(
|
||||
settings: ExecutorSettings,
|
||||
token: Option<String>,
|
||||
engine_addr: Option<String>,
|
||||
) -> Result<Self> {
|
||||
// Create the client.
|
||||
let client = new_zoo_client(token, engine_addr)?;
|
||||
|
||||
let ctx = Self::new(&client, settings).await?;
|
||||
Ok(ctx)
|
||||
}
|
||||
|
||||
/// Create a new default executor context.
|
||||
/// With the default kittycad client.
|
||||
/// This allows for passing in `ZOO_API_TOKEN` and `ZOO_HOST` as environment
|
||||
/// variables.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn new_with_default_client(settings: ExecutorSettings) -> Result<Self> {
|
||||
// Create the client.
|
||||
let ctx = Self::new_with_client(settings, None, None).await?;
|
||||
Ok(ctx)
|
||||
}
|
||||
|
||||
pub fn is_mock(&self) -> bool {
|
||||
self.context_type == ContextType::Mock || self.context_type == ContextType::MockCustomForwarded
|
||||
}
|
||||
@ -1942,35 +2127,7 @@ impl ExecutorContext {
|
||||
/// For executing unit tests.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn new_for_unit_test(units: UnitLength, engine_addr: Option<String>) -> Result<Self> {
|
||||
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);
|
||||
}
|
||||
if let Some(addr) = engine_addr {
|
||||
client.set_base_url(addr);
|
||||
}
|
||||
|
||||
let ctx = ExecutorContext::new(
|
||||
&client,
|
||||
let ctx = ExecutorContext::new_with_client(
|
||||
ExecutorSettings {
|
||||
units,
|
||||
highlight_edges: true,
|
||||
@ -1978,6 +2135,8 @@ impl ExecutorContext {
|
||||
show_grid: false,
|
||||
replay: None,
|
||||
},
|
||||
None,
|
||||
engine_addr,
|
||||
)
|
||||
.await?;
|
||||
Ok(ctx)
|
||||
|
@ -3,41 +3,13 @@ use std::sync::{Arc, RwLock};
|
||||
use anyhow::Result;
|
||||
use tower_lsp::LanguageServer;
|
||||
|
||||
fn new_zoo_client() -> kittycad::Client {
|
||||
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);
|
||||
}
|
||||
|
||||
client
|
||||
}
|
||||
|
||||
// Create a fake kcl lsp server for testing.
|
||||
pub async fn kcl_lsp_server(execute: bool) -> Result<crate::lsp::kcl::Backend> {
|
||||
let stdlib = crate::std::StdLib::new();
|
||||
let stdlib_completions = crate::lsp::kcl::get_completions_from_stdlib(&stdlib)?;
|
||||
let stdlib_signatures = crate::lsp::kcl::get_signatures_from_stdlib(&stdlib)?;
|
||||
|
||||
let zoo_client = new_zoo_client();
|
||||
let zoo_client = crate::executor::new_zoo_client(None, None)?;
|
||||
|
||||
let executor_ctx = if execute {
|
||||
Some(crate::executor::ExecutorContext::new(&zoo_client, Default::default()).await?)
|
||||
|
@ -1342,7 +1342,7 @@ fn declaration_keyword(i: TokenSlice) -> PResult<(VariableKind, Token)> {
|
||||
}
|
||||
|
||||
/// Parse a variable/constant declaration.
|
||||
fn declaration(i: TokenSlice) -> PResult<VariableDeclaration> {
|
||||
fn declaration(i: TokenSlice) -> PResult<Box<VariableDeclaration>> {
|
||||
let (visibility, visibility_token) = opt(terminated(item_visibility, whitespace))
|
||||
.parse_next(i)?
|
||||
.map_or((ItemVisibility::Default, None), |pair| (pair.0, Some(pair.1)));
|
||||
@ -1404,7 +1404,7 @@ fn declaration(i: TokenSlice) -> PResult<VariableDeclaration> {
|
||||
.map_err(|e| e.cut())?;
|
||||
|
||||
let end = val.end();
|
||||
Ok(VariableDeclaration {
|
||||
Ok(Box::new(VariableDeclaration {
|
||||
start,
|
||||
end,
|
||||
declarations: vec![VariableDeclarator {
|
||||
@ -1417,7 +1417,7 @@ fn declaration(i: TokenSlice) -> PResult<VariableDeclaration> {
|
||||
visibility,
|
||||
kind,
|
||||
digest: None,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
impl TryFrom<Token> for Identifier {
|
||||
|
@ -23,11 +23,13 @@ expression: actual
|
||||
"name": "boxSketch"
|
||||
},
|
||||
"init": {
|
||||
"type": "PipeExpression",
|
||||
"type": "PipeExpression",
|
||||
"start": 18,
|
||||
"end": 143,
|
||||
"body": [
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 18,
|
||||
"end": 39,
|
||||
@ -39,11 +41,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 32,
|
||||
"end": 38,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 33,
|
||||
"end": 34,
|
||||
@ -51,6 +55,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 36,
|
||||
"end": 37,
|
||||
@ -63,6 +68,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 47,
|
||||
"end": 63,
|
||||
@ -74,11 +80,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 52,
|
||||
"end": 59,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 53,
|
||||
"end": 54,
|
||||
@ -86,6 +94,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 56,
|
||||
"end": 58,
|
||||
@ -95,6 +104,7 @@ expression: actual
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 61,
|
||||
"end": 62
|
||||
@ -103,6 +113,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 71,
|
||||
"end": 96,
|
||||
@ -114,11 +125,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 85,
|
||||
"end": 92,
|
||||
"elements": [
|
||||
{
|
||||
"type": "UnaryExpression",
|
||||
"type": "UnaryExpression",
|
||||
"start": 86,
|
||||
"end": 88,
|
||||
@ -133,6 +146,7 @@ expression: actual
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 90,
|
||||
"end": 91,
|
||||
@ -142,6 +156,7 @@ expression: actual
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 94,
|
||||
"end": 95
|
||||
@ -150,6 +165,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 104,
|
||||
"end": 121,
|
||||
@ -161,11 +177,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 109,
|
||||
"end": 117,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 110,
|
||||
"end": 111,
|
||||
@ -173,6 +191,7 @@ expression: actual
|
||||
"raw": "5"
|
||||
},
|
||||
{
|
||||
"type": "UnaryExpression",
|
||||
"type": "UnaryExpression",
|
||||
"start": 113,
|
||||
"end": 116,
|
||||
@ -189,6 +208,7 @@ expression: actual
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 119,
|
||||
"end": 120
|
||||
@ -197,6 +217,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 129,
|
||||
"end": 143,
|
||||
@ -208,6 +229,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 137,
|
||||
"end": 139,
|
||||
@ -215,6 +237,7 @@ expression: actual
|
||||
"raw": "10"
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 141,
|
||||
"end": 142
|
||||
|
@ -23,6 +23,7 @@ expression: actual
|
||||
"name": "sg"
|
||||
},
|
||||
"init": {
|
||||
"type": "UnaryExpression",
|
||||
"type": "UnaryExpression",
|
||||
"start": 11,
|
||||
"end": 17,
|
||||
|
@ -12,6 +12,7 @@ expression: actual
|
||||
"start": 0,
|
||||
"end": 23,
|
||||
"expression": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 0,
|
||||
"end": 23,
|
||||
@ -23,6 +24,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ObjectExpression",
|
||||
"type": "ObjectExpression",
|
||||
"start": 7,
|
||||
"end": 22,
|
||||
@ -38,11 +40,13 @@ expression: actual
|
||||
"name": "to"
|
||||
},
|
||||
"value": {
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 13,
|
||||
"end": 20,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 14,
|
||||
"end": 15,
|
||||
@ -50,6 +54,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "UnaryExpression",
|
||||
"type": "UnaryExpression",
|
||||
"start": 17,
|
||||
"end": 19,
|
||||
|
@ -23,10 +23,12 @@ expression: actual
|
||||
"name": "myArray"
|
||||
},
|
||||
"init": {
|
||||
"type": "ArrayRangeExpression",
|
||||
"type": "ArrayRangeExpression",
|
||||
"start": 16,
|
||||
"end": 23,
|
||||
"startElement": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
@ -34,6 +36,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
"endElement": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 20,
|
||||
"end": 22,
|
||||
|
@ -23,6 +23,7 @@ expression: actual
|
||||
"name": "firstPrimeNumber"
|
||||
},
|
||||
"init": {
|
||||
"type": "FunctionExpression",
|
||||
"type": "FunctionExpression",
|
||||
"start": 27,
|
||||
"end": 57,
|
||||
@ -37,6 +38,7 @@ expression: actual
|
||||
"start": 43,
|
||||
"end": 51,
|
||||
"argument": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 50,
|
||||
"end": 51,
|
||||
@ -57,6 +59,7 @@ expression: actual
|
||||
"start": 62,
|
||||
"end": 80,
|
||||
"expression": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 62,
|
||||
"end": 80,
|
||||
|
@ -23,6 +23,7 @@ expression: actual
|
||||
"name": "thing"
|
||||
},
|
||||
"init": {
|
||||
"type": "FunctionExpression",
|
||||
"type": "FunctionExpression",
|
||||
"start": 11,
|
||||
"end": 49,
|
||||
@ -48,6 +49,7 @@ expression: actual
|
||||
"start": 32,
|
||||
"end": 43,
|
||||
"argument": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 39,
|
||||
"end": 43,
|
||||
@ -68,6 +70,7 @@ expression: actual
|
||||
"start": 54,
|
||||
"end": 66,
|
||||
"expression": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 54,
|
||||
"end": 66,
|
||||
@ -79,6 +82,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 60,
|
||||
"end": 65,
|
||||
|
@ -23,11 +23,13 @@ expression: actual
|
||||
"name": "mySketch"
|
||||
},
|
||||
"init": {
|
||||
"type": "PipeExpression",
|
||||
"type": "PipeExpression",
|
||||
"start": 17,
|
||||
"end": 165,
|
||||
"body": [
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 17,
|
||||
"end": 37,
|
||||
@ -39,11 +41,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 31,
|
||||
"end": 36,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 32,
|
||||
"end": 33,
|
||||
@ -51,6 +55,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 34,
|
||||
"end": 35,
|
||||
@ -63,6 +68,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 49,
|
||||
"end": 75,
|
||||
@ -74,11 +80,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 56,
|
||||
"end": 62,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 57,
|
||||
"end": 58,
|
||||
@ -86,6 +94,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 60,
|
||||
"end": 61,
|
||||
@ -95,11 +104,13 @@ expression: actual
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 64,
|
||||
"end": 65
|
||||
},
|
||||
{
|
||||
"type": "TagDeclarator",
|
||||
"type": "TagDeclarator",
|
||||
"start": 67,
|
||||
"end": 74,
|
||||
@ -109,6 +120,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 87,
|
||||
"end": 104,
|
||||
@ -120,11 +132,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 94,
|
||||
"end": 100,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 95,
|
||||
"end": 96,
|
||||
@ -132,6 +146,7 @@ expression: actual
|
||||
"raw": "1"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 98,
|
||||
"end": 99,
|
||||
@ -141,6 +156,7 @@ expression: actual
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 102,
|
||||
"end": 103
|
||||
@ -149,6 +165,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 116,
|
||||
"end": 145,
|
||||
@ -160,11 +177,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 123,
|
||||
"end": 129,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 124,
|
||||
"end": 125,
|
||||
@ -172,6 +191,7 @@ expression: actual
|
||||
"raw": "1"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 127,
|
||||
"end": 128,
|
||||
@ -181,11 +201,13 @@ expression: actual
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 131,
|
||||
"end": 132
|
||||
},
|
||||
{
|
||||
"type": "TagDeclarator",
|
||||
"type": "TagDeclarator",
|
||||
"start": 134,
|
||||
"end": 144,
|
||||
@ -195,6 +217,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 157,
|
||||
"end": 165,
|
||||
@ -206,6 +229,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 163,
|
||||
"end": 164
|
||||
|
@ -23,11 +23,13 @@ expression: actual
|
||||
"name": "mySketch"
|
||||
},
|
||||
"init": {
|
||||
"type": "PipeExpression",
|
||||
"type": "PipeExpression",
|
||||
"start": 17,
|
||||
"end": 70,
|
||||
"body": [
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 17,
|
||||
"end": 37,
|
||||
@ -39,11 +41,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 31,
|
||||
"end": 36,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 32,
|
||||
"end": 33,
|
||||
@ -51,6 +55,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 34,
|
||||
"end": 35,
|
||||
@ -63,6 +68,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 41,
|
||||
"end": 58,
|
||||
@ -74,11 +80,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 48,
|
||||
"end": 54,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 49,
|
||||
"end": 50,
|
||||
@ -86,6 +94,7 @@ expression: actual
|
||||
"raw": "1"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 52,
|
||||
"end": 53,
|
||||
@ -95,6 +104,7 @@ expression: actual
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 56,
|
||||
"end": 57
|
||||
@ -103,6 +113,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 62,
|
||||
"end": 70,
|
||||
@ -114,6 +125,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 68,
|
||||
"end": 69
|
||||
|
@ -23,6 +23,7 @@ expression: actual
|
||||
"name": "myBox"
|
||||
},
|
||||
"init": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 14,
|
||||
"end": 30,
|
||||
@ -34,6 +35,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 28,
|
||||
"end": 29,
|
||||
|
@ -23,11 +23,13 @@ expression: actual
|
||||
"name": "myBox"
|
||||
},
|
||||
"init": {
|
||||
"type": "PipeExpression",
|
||||
"type": "PipeExpression",
|
||||
"start": 14,
|
||||
"end": 29,
|
||||
"body": [
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 14,
|
||||
"end": 18,
|
||||
@ -39,6 +41,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 16,
|
||||
"end": 17,
|
||||
@ -49,6 +52,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 22,
|
||||
"end": 29,
|
||||
@ -60,6 +64,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 24,
|
||||
"end": 25,
|
||||
@ -67,6 +72,7 @@ expression: actual
|
||||
"raw": "2"
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 27,
|
||||
"end": 28
|
||||
|
@ -23,11 +23,13 @@ expression: actual
|
||||
"name": "myBox"
|
||||
},
|
||||
"init": {
|
||||
"type": "PipeExpression",
|
||||
"type": "PipeExpression",
|
||||
"start": 14,
|
||||
"end": 49,
|
||||
"body": [
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 14,
|
||||
"end": 30,
|
||||
@ -39,6 +41,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 28,
|
||||
"end": 29,
|
||||
@ -48,6 +51,7 @@ expression: actual
|
||||
"optional": false
|
||||
},
|
||||
{
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 34,
|
||||
"end": 49,
|
||||
@ -59,11 +63,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 39,
|
||||
"end": 45,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 40,
|
||||
"end": 41,
|
||||
@ -71,6 +77,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 43,
|
||||
"end": 44,
|
||||
@ -79,6 +86,7 @@ expression: actual
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "PipeSubstitution",
|
||||
"type": "PipeSubstitution",
|
||||
"start": 47,
|
||||
"end": 48
|
||||
|
@ -12,6 +12,7 @@ expression: actual
|
||||
"start": 0,
|
||||
"end": 22,
|
||||
"expression": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 0,
|
||||
"end": 22,
|
||||
@ -23,6 +24,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ObjectExpression",
|
||||
"type": "ObjectExpression",
|
||||
"start": 7,
|
||||
"end": 21,
|
||||
@ -38,11 +40,13 @@ expression: actual
|
||||
"name": "to"
|
||||
},
|
||||
"value": {
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 13,
|
||||
"end": 19,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 14,
|
||||
"end": 15,
|
||||
@ -50,6 +54,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
|
@ -12,6 +12,7 @@ expression: actual
|
||||
"start": 0,
|
||||
"end": 36,
|
||||
"expression": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 0,
|
||||
"end": 36,
|
||||
@ -23,6 +24,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ObjectExpression",
|
||||
"type": "ObjectExpression",
|
||||
"start": 7,
|
||||
"end": 35,
|
||||
@ -38,11 +40,13 @@ expression: actual
|
||||
"name": "to"
|
||||
},
|
||||
"value": {
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 13,
|
||||
"end": 19,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 14,
|
||||
"end": 15,
|
||||
@ -50,6 +54,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
@ -70,11 +75,13 @@ expression: actual
|
||||
"name": "from"
|
||||
},
|
||||
"value": {
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 27,
|
||||
"end": 33,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 28,
|
||||
"end": 29,
|
||||
@ -82,6 +89,7 @@ expression: actual
|
||||
"raw": "3"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 31,
|
||||
"end": 32,
|
||||
|
@ -12,6 +12,7 @@ expression: actual
|
||||
"start": 0,
|
||||
"end": 19,
|
||||
"expression": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 0,
|
||||
"end": 19,
|
||||
@ -23,6 +24,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ObjectExpression",
|
||||
"type": "ObjectExpression",
|
||||
"start": 7,
|
||||
"end": 18,
|
||||
@ -38,11 +40,13 @@ expression: actual
|
||||
"name": "to"
|
||||
},
|
||||
"value": {
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 11,
|
||||
"end": 17,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 12,
|
||||
"end": 13,
|
||||
@ -50,6 +54,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 15,
|
||||
"end": 16,
|
||||
|
@ -12,6 +12,7 @@ expression: actual
|
||||
"start": 0,
|
||||
"end": 35,
|
||||
"expression": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 0,
|
||||
"end": 35,
|
||||
@ -23,6 +24,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ObjectExpression",
|
||||
"type": "ObjectExpression",
|
||||
"start": 7,
|
||||
"end": 34,
|
||||
@ -38,11 +40,13 @@ expression: actual
|
||||
"name": "to"
|
||||
},
|
||||
"value": {
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 13,
|
||||
"end": 19,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 14,
|
||||
"end": 15,
|
||||
@ -50,6 +54,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
@ -70,11 +75,13 @@ expression: actual
|
||||
"name": "from"
|
||||
},
|
||||
"value": {
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 27,
|
||||
"end": 33,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 28,
|
||||
"end": 29,
|
||||
@ -82,6 +89,7 @@ expression: actual
|
||||
"raw": "3"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 31,
|
||||
"end": 32,
|
||||
|
@ -12,6 +12,7 @@ expression: actual
|
||||
"start": 0,
|
||||
"end": 35,
|
||||
"expression": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 0,
|
||||
"end": 35,
|
||||
@ -23,6 +24,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ObjectExpression",
|
||||
"type": "ObjectExpression",
|
||||
"start": 7,
|
||||
"end": 34,
|
||||
@ -38,11 +40,13 @@ expression: actual
|
||||
"name": "to"
|
||||
},
|
||||
"value": {
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 13,
|
||||
"end": 19,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 14,
|
||||
"end": 15,
|
||||
@ -50,6 +54,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
@ -70,11 +75,13 @@ expression: actual
|
||||
"name": "from"
|
||||
},
|
||||
"value": {
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 26,
|
||||
"end": 32,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 27,
|
||||
"end": 28,
|
||||
@ -82,6 +89,7 @@ expression: actual
|
||||
"raw": "3"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 30,
|
||||
"end": 31,
|
||||
|
@ -23,6 +23,7 @@ expression: actual
|
||||
"name": "mySketch"
|
||||
},
|
||||
"init": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 17,
|
||||
"end": 37,
|
||||
@ -34,11 +35,13 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"start": 31,
|
||||
"end": 36,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 32,
|
||||
"end": 33,
|
||||
@ -46,6 +49,7 @@ expression: actual
|
||||
"raw": "0"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 34,
|
||||
"end": 35,
|
||||
|
@ -12,6 +12,7 @@ expression: actual
|
||||
"start": 0,
|
||||
"end": 28,
|
||||
"expression": {
|
||||
"type": "CallExpression",
|
||||
"type": "CallExpression",
|
||||
"start": 0,
|
||||
"end": 28,
|
||||
@ -23,6 +24,7 @@ expression: actual
|
||||
},
|
||||
"arguments": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 4,
|
||||
"end": 5,
|
||||
@ -30,6 +32,7 @@ expression: actual
|
||||
"raw": "5"
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 7,
|
||||
"end": 14,
|
||||
@ -37,6 +40,7 @@ expression: actual
|
||||
"raw": "\"hello\""
|
||||
},
|
||||
{
|
||||
"type": "Identifier",
|
||||
"type": "Identifier",
|
||||
"start": 16,
|
||||
"end": 27,
|
||||
|
@ -12,6 +12,7 @@ expression: actual
|
||||
"start": 0,
|
||||
"end": 7,
|
||||
"expression": {
|
||||
"type": "BinaryExpression",
|
||||
"type": "BinaryExpression",
|
||||
"start": 0,
|
||||
"end": 7,
|
||||
|