Compare commits
1 Commits
v0.15.5
...
kurt-add-s
Author | SHA1 | Date | |
---|---|---|---|
3e0c44e689 |
@ -3,3 +3,4 @@ VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
|||||||
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||||
|
VITE_KC_SENTRY_DSN=
|
||||||
|
@ -3,3 +3,4 @@ VITE_KC_API_BASE_URL=https://api.zoo.dev
|
|||||||
VITE_KC_SITE_BASE_URL=https://zoo.dev
|
VITE_KC_SITE_BASE_URL=https://zoo.dev
|
||||||
VITE_KC_SKIP_AUTH=false
|
VITE_KC_SKIP_AUTH=false
|
||||||
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
||||||
|
VITE_KC_SENTRY_DSN=
|
||||||
|
16
.github/workflows/cargo-test.yml
vendored
@ -40,20 +40,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||||
- name: Install vector
|
|
||||||
run: |
|
|
||||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
|
||||||
chmod +x /tmp/vector.sh
|
|
||||||
/tmp/vector.sh -y -no-modify-path
|
|
||||||
mkdir -p /tmp/vector
|
|
||||||
cp .github/workflows/vector.toml /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
|
|
||||||
sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
|
|
||||||
cat /tmp/vector.toml
|
|
||||||
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
|
|
||||||
- uses: taiki-e/install-action@cargo-llvm-cov
|
- uses: taiki-e/install-action@cargo-llvm-cov
|
||||||
- uses: taiki-e/install-action@nextest
|
- uses: taiki-e/install-action@nextest
|
||||||
- name: Rust Cache
|
- name: Rust Cache
|
||||||
@ -62,7 +48,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |-
|
run: |-
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
cargo nextest run --workspace --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
|
cargo nextest run --workspace --no-fail-fast -P ci
|
||||||
env:
|
env:
|
||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||||
RUST_MIN_STACK: 10485760000
|
RUST_MIN_STACK: 10485760000
|
||||||
|
2
.github/workflows/ci.yml
vendored
@ -336,7 +336,7 @@ jobs:
|
|||||||
cat last_download.json
|
cat last_download.json
|
||||||
|
|
||||||
- name: Authenticate to Google Cloud
|
- name: Authenticate to Google Cloud
|
||||||
uses: 'google-github-actions/auth@v2.1.2'
|
uses: 'google-github-actions/auth@v2.1.1'
|
||||||
with:
|
with:
|
||||||
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
|
||||||
|
|
||||||
|
6
.github/workflows/playwright.yml
vendored
@ -4,11 +4,6 @@ on:
|
|||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
playwright-ubuntu:
|
playwright-ubuntu:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
@ -85,6 +80,7 @@ jobs:
|
|||||||
playwright-macos:
|
playwright-macos:
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
runs-on: macos-14
|
runs-on: macos-14
|
||||||
|
needs: playwright-ubuntu
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
|
21
.github/workflows/vector.toml
vendored
@ -1,21 +0,0 @@
|
|||||||
[sources.github-actions-file]
|
|
||||||
type = "file"
|
|
||||||
data_dir = "/tmp/vector"
|
|
||||||
include = ["/tmp/github-actions.log"]
|
|
||||||
|
|
||||||
# Modify the logs to include the action name.
|
|
||||||
[transforms.add-action-name]
|
|
||||||
type = "remap"
|
|
||||||
inputs = [ "github-actions-file" ]
|
|
||||||
source = '''
|
|
||||||
.action = "GITHUB_WORKFLOW"
|
|
||||||
.repo = "GITHUB_REPOSITORY"
|
|
||||||
.sha = "GITHUB_SHA"
|
|
||||||
.ref = "GITHUB_REF_NAME"
|
|
||||||
'''
|
|
||||||
|
|
||||||
[sinks.axiom]
|
|
||||||
type = "axiom"
|
|
||||||
inputs = ["add-action-name"]
|
|
||||||
token = "GH_ACTIONS_AXIOM_TOKEN"
|
|
||||||
dataset = "github-actions"
|
|
3
.gitignore
vendored
@ -33,7 +33,6 @@ src/wasm-lib/bindings
|
|||||||
src/wasm-lib/kcl/bindings
|
src/wasm-lib/kcl/bindings
|
||||||
public/wasm_lib_bg.wasm
|
public/wasm_lib_bg.wasm
|
||||||
src/wasm-lib/lcov.info
|
src/wasm-lib/lcov.info
|
||||||
src/wasm-lib/grackle/test_json_output
|
|
||||||
|
|
||||||
e2e/playwright/playwright-secrets.env
|
e2e/playwright/playwright-secrets.env
|
||||||
e2e/playwright/temp1.png
|
e2e/playwright/temp1.png
|
||||||
@ -55,5 +54,3 @@ e2e/playwright/export-snapshots/*embedded.gltf
|
|||||||
|
|
||||||
## generated files
|
## generated files
|
||||||
src/**/*.typegen.ts
|
src/**/*.typegen.ts
|
||||||
|
|
||||||
src/wasm-lib/grackle/stdlib_cube_partial.json
|
|
||||||
|
@ -141,7 +141,7 @@ run `./make-release.sh` for a patch update
|
|||||||
run `./make-release.sh "minor"` for minor
|
run `./make-release.sh "minor"` for minor
|
||||||
run `./make-release.sh "major"` for major
|
run `./make-release.sh "major"` for major
|
||||||
|
|
||||||
The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and paste in the following
|
The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and past in the following
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
console.log(
|
console.log(
|
||||||
|
@ -8,6 +8,10 @@ once fixed in engine will just start working here with no language changes.
|
|||||||
model for that sketch and its underlying 3D object.
|
model for that sketch and its underlying 3D object.
|
||||||
If you see a red line around your model, it means this is happening.
|
If you see a red line around your model, it means this is happening.
|
||||||
|
|
||||||
|
- **Patterns**: If you try and pass a pattern to `hole` currently only the first
|
||||||
|
item in the pattern is being subtracted. This is an engine bug that is being
|
||||||
|
worked on.
|
||||||
|
|
||||||
- **Import**: Right now you can import a file, even if that file has brep data
|
- **Import**: Right now you can import a file, even if that file has brep data
|
||||||
you cannot edit it, after v1, the engine will account for this. You also cannot
|
you cannot edit it, after v1, the engine will account for this. You also cannot
|
||||||
currently move or transform the imported objects at all, once we have assemblies
|
currently move or transform the imported objects at all, once we have assemblies
|
||||||
|
10016
docs/kcl/std.json
1611
docs/kcl/std.md
@ -3,8 +3,6 @@ import { secrets } from './secrets'
|
|||||||
import { getUtils } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
import waitOn from 'wait-on'
|
import waitOn from 'wait-on'
|
||||||
import { Themes } from '../../src/lib/theme'
|
import { Themes } from '../../src/lib/theme'
|
||||||
import { roundOff } from 'lib/utils'
|
|
||||||
import { platform } from 'node:os'
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
|
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
|
||||||
@ -17,9 +15,9 @@ document.addEventListener('mousemove', (e) =>
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const commonPoints = {
|
const commonPoints = {
|
||||||
startAt: '[9.06, -12.22]',
|
startAt: '[26.38, -35.59]',
|
||||||
num1: 9.14,
|
num1: 26.63,
|
||||||
num2: 18.2,
|
num2: 53.01,
|
||||||
}
|
}
|
||||||
|
|
||||||
test.beforeEach(async ({ context, page }) => {
|
test.beforeEach(async ({ context, page }) => {
|
||||||
@ -67,8 +65,10 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
|
|
||||||
// click on "Start Sketch" button
|
// click on "Start Sketch" button
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await u.doAndWaitForImageDiff(
|
||||||
await page.waitForTimeout(100)
|
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||||
|
200
|
||||||
|
)
|
||||||
|
|
||||||
// select a plane
|
// select a plane
|
||||||
await page.mouse.click(700, 200)
|
await page.mouse.click(700, 200)
|
||||||
@ -90,6 +90,7 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
const num = 26.63
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
@ -135,126 +136,6 @@ test('Basic sketch', async ({ page }) => {
|
|||||||
|> angledLine([180, segLen('seg01', %)], %)`)
|
|> angledLine([180, segLen('seg01', %)], %)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Can moving camera', async ({ page, context }) => {
|
|
||||||
test.skip(process.platform === 'darwin', 'Can moving camera')
|
|
||||||
const u = getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
|
|
||||||
const camPos: [number, number, number] = [0, 85, 85]
|
|
||||||
const bakeInRetries = async (
|
|
||||||
mouseActions: any,
|
|
||||||
xyz: [number, number, number],
|
|
||||||
cnt = 0
|
|
||||||
) => {
|
|
||||||
// hack that we're implemented our own retry instead of using retries built into playwright.
|
|
||||||
// however each of these camera drags can be flaky, because of udp
|
|
||||||
// and so putting them together means only one needs to fail to make this test extra flaky.
|
|
||||||
// this way we can retry within the test
|
|
||||||
// We could break them out into separate tests, but the longest past of the test is waiting
|
|
||||||
// for the stream to start, so it can be good to bundle related things together.
|
|
||||||
|
|
||||||
await u.updateCamPosition(camPos)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
// rotate
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
// const yo = page.getByTestId('cam-x-position').inputValue()
|
|
||||||
|
|
||||||
await u.doAndWaitForImageDiff(async () => {
|
|
||||||
await mouseActions()
|
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
}, 300)
|
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
const vals = await Promise.all([
|
|
||||||
page.getByTestId('cam-x-position').inputValue(),
|
|
||||||
page.getByTestId('cam-y-position').inputValue(),
|
|
||||||
page.getByTestId('cam-z-position').inputValue(),
|
|
||||||
])
|
|
||||||
const xError = Math.abs(Number(vals[0]) + xyz[0])
|
|
||||||
const yError = Math.abs(Number(vals[1]) + xyz[1])
|
|
||||||
const zError = Math.abs(Number(vals[2]) + xyz[2])
|
|
||||||
|
|
||||||
let shouldRetry = false
|
|
||||||
|
|
||||||
if (xError > 5 || yError > 5 || zError > 5) {
|
|
||||||
if (cnt > 2) {
|
|
||||||
console.log('xVal', vals[0], 'xError', xError)
|
|
||||||
console.log('yVal', vals[1], 'yError', yError)
|
|
||||||
console.log('zVal', vals[2], 'zError', zError)
|
|
||||||
|
|
||||||
throw new Error('Camera position not as expected')
|
|
||||||
}
|
|
||||||
shouldRetry = true
|
|
||||||
}
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
if (shouldRetry) await bakeInRetries(mouseActions, xyz, cnt + 1)
|
|
||||||
}
|
|
||||||
await bakeInRetries(async () => {
|
|
||||||
await page.mouse.move(700, 200)
|
|
||||||
await page.mouse.down({ button: 'right' })
|
|
||||||
await page.mouse.move(600, 303)
|
|
||||||
await page.mouse.up({ button: 'right' })
|
|
||||||
}, [4, -10.5, -120])
|
|
||||||
|
|
||||||
await bakeInRetries(async () => {
|
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await page.mouse.move(600, 200)
|
|
||||||
await page.mouse.down({ button: 'right' })
|
|
||||||
await page.mouse.move(700, 200, { steps: 2 })
|
|
||||||
await page.mouse.up({ button: 'right' })
|
|
||||||
await page.keyboard.up('Shift')
|
|
||||||
}, [-10, -85, -85])
|
|
||||||
|
|
||||||
await u.updateCamPosition(camPos)
|
|
||||||
|
|
||||||
await u.clearCommandLogs()
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
|
|
||||||
// zoom
|
|
||||||
await u.doAndWaitForImageDiff(async () => {
|
|
||||||
await page.keyboard.down('Control')
|
|
||||||
await page.mouse.move(700, 400)
|
|
||||||
await page.mouse.down({ button: 'right' })
|
|
||||||
await page.mouse.move(700, 300)
|
|
||||||
await page.mouse.up({ button: 'right' })
|
|
||||||
await page.keyboard.up('Control')
|
|
||||||
|
|
||||||
await u.openDebugPanel()
|
|
||||||
await page.waitForTimeout(300)
|
|
||||||
await u.clearCommandLogs()
|
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
}, 300)
|
|
||||||
|
|
||||||
// zoom with scroll
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
// TODO, it appears we don't get the cam setting back from the engine when the interaction is zoom into `backInRetries` once the information is sent back on zoom
|
|
||||||
// await expect(Math.abs(Number(await page.getByTestId('cam-x-position').inputValue()) + 12)).toBeLessThan(1.5)
|
|
||||||
// await expect(Math.abs(Number(await page.getByTestId('cam-y-position').inputValue()) - 85)).toBeLessThan(1.5)
|
|
||||||
// await expect(Math.abs(Number(await page.getByTestId('cam-z-position').inputValue()) - 85)).toBeLessThan(1.5)
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
|
|
||||||
await bakeInRetries(async () => {
|
|
||||||
await page.mouse.move(700, 400)
|
|
||||||
await page.mouse.wheel(0, -100)
|
|
||||||
}, [1, -94, -94])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
test('if you write invalid kcl you get inlined errors', async ({ page }) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
await page.setViewportSize({ width: 1000, height: 500 })
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
@ -403,9 +284,10 @@ test('Can create sketches on all planes and their back sides', async ({
|
|||||||
}) => {
|
}) => {
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
|
await u.updateCamPosition(viewCmd)
|
||||||
|
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
await u.updateCamPosition(viewCmd)
|
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
await page.mouse.click(clickCoords.x, clickCoords.y)
|
await page.mouse.click(clickCoords.x, clickCoords.y)
|
||||||
@ -433,7 +315,7 @@ test('Can create sketches on all planes and their back sides', async ({
|
|||||||
const codeTemplate = (
|
const codeTemplate = (
|
||||||
plane = 'XY'
|
plane = 'XY'
|
||||||
) => `const part001 = startSketchOn('${plane}')
|
) => `const part001 = startSketchOn('${plane}')
|
||||||
|> startProfileAt([1.14, -1.54], %)`
|
|> startProfileAt([32.13, -43.34], %)`
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camPos,
|
viewCmd: camPos,
|
||||||
expectedCode: codeTemplate('XY'),
|
expectedCode: codeTemplate('XY'),
|
||||||
@ -443,7 +325,7 @@ test('Can create sketches on all planes and their back sides', async ({
|
|||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camPos,
|
viewCmd: camPos,
|
||||||
expectedCode: codeTemplate('YZ'),
|
expectedCode: codeTemplate('YZ'),
|
||||||
clickCoords: { x: 700, y: 250 }, // green plane
|
clickCoords: { x: 700, y: 300 }, // green plane
|
||||||
})
|
})
|
||||||
await TestSinglePlane({
|
await TestSinglePlane({
|
||||||
viewCmd: camPos,
|
viewCmd: camPos,
|
||||||
@ -504,16 +386,12 @@ test('Auto complete works', async ({ page }) => {
|
|||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
// finish line with comment
|
await page.keyboard.type('(5, %)')
|
||||||
await page.keyboard.type('(5, %) // lin')
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
// there shouldn't be any auto complete options for 'lin' in the comment
|
|
||||||
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('XY')
|
.toHaveText(`const part001 = startSketchOn('XY')
|
||||||
|> startProfileAt([0,0], %)
|
|> startProfileAt([0,0], %)
|
||||||
|> xLine(5, %) // lin`)
|
|> xLine(5, %)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Onboarding tests
|
// Onboarding tests
|
||||||
@ -743,12 +621,12 @@ test('Command bar works and can change a setting', async ({ page }) => {
|
|||||||
const themeOption = page.getByRole('option', { name: 'Set Theme' })
|
const themeOption = page.getByRole('option', { name: 'Set Theme' })
|
||||||
await expect(themeOption).toBeVisible()
|
await expect(themeOption).toBeVisible()
|
||||||
await themeOption.click()
|
await themeOption.click()
|
||||||
const themeInput = page.getByPlaceholder('system')
|
const themeInput = page.getByPlaceholder('Select an option')
|
||||||
await expect(themeInput).toBeVisible()
|
await expect(themeInput).toBeVisible()
|
||||||
await expect(themeInput).toBeFocused()
|
await expect(themeInput).toBeFocused()
|
||||||
// Select dark theme
|
// Select dark theme
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await page.keyboard.press('ArrowUp')
|
await page.keyboard.press('ArrowDown')
|
||||||
await expect(page.getByRole('option', { name: Themes.Dark })).toHaveAttribute(
|
await expect(page.getByRole('option', { name: Themes.Dark })).toHaveAttribute(
|
||||||
'data-headlessui-state',
|
'data-headlessui-state',
|
||||||
'active'
|
'active'
|
||||||
@ -821,8 +699,6 @@ test('Can extrude from the command bar', async ({ page, context }) => {
|
|||||||
).toBeDisabled()
|
).toBeDisabled()
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
|
||||||
|
|
||||||
// Check that the code was updated
|
// Check that the code was updated
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
// Unfortunately this indentation seems to matter for the test
|
// Unfortunately this indentation seems to matter for the test
|
||||||
@ -917,7 +793,7 @@ test('Can add multiple sketches', async ({ page }) => {
|
|||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
const startAt2 = '[0.93,-1.25]'
|
const startAt2 = '[26.23, -35.39]'
|
||||||
await expect(
|
await expect(
|
||||||
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
).toBe(
|
).toBe(
|
||||||
@ -931,7 +807,7 @@ const part002 = startSketchOn('XY')
|
|||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
const num2 = 0.94
|
const num2 = 26.48
|
||||||
await expect(
|
await expect(
|
||||||
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
|
||||||
).toBe(
|
).toBe(
|
||||||
@ -949,7 +825,7 @@ const part002 = startSketchOn('XY')
|
|||||||
const part002 = startSketchOn('XY')
|
const part002 = startSketchOn('XY')
|
||||||
|> startProfileAt(${startAt2}, %)
|
|> startProfileAt(${startAt2}, %)
|
||||||
|> line([${num2}, 0], %)
|
|> line([${num2}, 0], %)
|
||||||
|> line([0, ${roundOff(num2 - 0.01)}], %)`.replace(/\s/g, '')
|
|> line([0, ${num2}], %)`.replace(/\s/g, '')
|
||||||
)
|
)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(
|
await expect(
|
||||||
@ -959,8 +835,8 @@ const part002 = startSketchOn('XY')
|
|||||||
const part002 = startSketchOn('XY')
|
const part002 = startSketchOn('XY')
|
||||||
|> startProfileAt(${startAt2}, %)
|
|> startProfileAt(${startAt2}, %)
|
||||||
|> line([${num2}, 0], %)
|
|> line([${num2}, 0], %)
|
||||||
|> line([0, ${roundOff(num2 - 0.01)}], %)
|
|> line([0, ${num2}], %)
|
||||||
|> line([-1.87, 0], %)`.replace(/\s/g, '')
|
|> line([-52.71, 0], %)`.replace(/\s/g, '')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1116,6 +992,7 @@ test('Deselecting line tool should mean nothing happens on click', async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.goto('/')
|
await page.goto('/')
|
||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -1173,160 +1050,3 @@ test('Deselecting line tool should mean nothing happens on click', async ({
|
|||||||
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
|
||||||
previousCodeContent = await page.locator('.cm-content').innerText()
|
previousCodeContent = await page.locator('.cm-content').innerText()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Can edit segments by dragging their handles', async ({
|
|
||||||
page,
|
|
||||||
context,
|
|
||||||
}) => {
|
|
||||||
const u = getUtils(page)
|
|
||||||
await context.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'persistCode',
|
|
||||||
`const part001 = startSketchOn('-XZ')
|
|
||||||
|> startProfileAt([4.61, -14.01], %)
|
|
||||||
|> line([12.73, -0.09], %)
|
|
||||||
|> tangentialArcTo([24.95, -5.38], %)`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).not.toBeDisabled()
|
|
||||||
|
|
||||||
const startPX = [652, 418]
|
|
||||||
const lineEndPX = [794, 416]
|
|
||||||
const arcEndPX = [893, 318]
|
|
||||||
|
|
||||||
const dragPX = 30
|
|
||||||
|
|
||||||
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
|
||||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
let prevContent = await page.locator('.cm-content').innerText()
|
|
||||||
|
|
||||||
const step5 = { steps: 5 }
|
|
||||||
|
|
||||||
// drag startProfieAt handle
|
|
||||||
await page.mouse.move(startPX[0], startPX[1])
|
|
||||||
await page.mouse.down()
|
|
||||||
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
|
||||||
await page.mouse.up()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
|
||||||
prevContent = await page.locator('.cm-content').innerText()
|
|
||||||
|
|
||||||
// drag line handle
|
|
||||||
await page.mouse.move(lineEndPX[0] + dragPX, lineEndPX[1] - dragPX)
|
|
||||||
await page.mouse.down()
|
|
||||||
await page.mouse.move(
|
|
||||||
lineEndPX[0] + dragPX * 2,
|
|
||||||
lineEndPX[1] - dragPX * 2,
|
|
||||||
step5
|
|
||||||
)
|
|
||||||
await page.mouse.up()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
|
||||||
prevContent = await page.locator('.cm-content').innerText()
|
|
||||||
|
|
||||||
// drag tangentialArcTo handle
|
|
||||||
await page.mouse.move(arcEndPX[0], arcEndPX[1])
|
|
||||||
await page.mouse.down()
|
|
||||||
await page.mouse.move(arcEndPX[0] + dragPX, arcEndPX[1] - dragPX, step5)
|
|
||||||
await page.mouse.up()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
|
||||||
|
|
||||||
// expect the code to have changed
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
||||||
|> startProfileAt([7.01, -11.79], %)
|
|
||||||
|> line([14.69, 2.73], %)
|
|
||||||
|> tangentialArcTo([27.6, -3.25], %)`)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Snap to close works (at any scale)', async ({ page }) => {
|
|
||||||
const u = getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
await u.openDebugPanel()
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).not.toBeDisabled()
|
|
||||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
|
||||||
|
|
||||||
const doSnapAtDifferentScales = async (
|
|
||||||
camPos: [number, number, number],
|
|
||||||
expectedCode: string
|
|
||||||
) => {
|
|
||||||
await u.clearCommandLogs()
|
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
await u.updateCamPosition(camPos)
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
// select a plane
|
|
||||||
await page.mouse.click(700, 200)
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
|
||||||
`const part001 = startSketchOn('XZ')`
|
|
||||||
)
|
|
||||||
|
|
||||||
let prevContent = await page.locator('.cm-content').innerText()
|
|
||||||
|
|
||||||
const pointA = [700, 200]
|
|
||||||
const pointB = [900, 200]
|
|
||||||
const pointC = [900, 400]
|
|
||||||
|
|
||||||
// draw three lines
|
|
||||||
await page.mouse.click(pointA[0], pointA[1])
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
|
||||||
prevContent = await page.locator('.cm-content').innerText()
|
|
||||||
|
|
||||||
await page.mouse.click(pointB[0], pointB[1])
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
|
||||||
prevContent = await page.locator('.cm-content').innerText()
|
|
||||||
|
|
||||||
await page.mouse.click(pointC[0], pointC[1])
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
|
||||||
prevContent = await page.locator('.cm-content').innerText()
|
|
||||||
|
|
||||||
await page.mouse.move(pointA[0] - 12, pointA[1] + 12)
|
|
||||||
const pointNotQuiteA = [pointA[0] - 7, pointA[1] + 7]
|
|
||||||
await page.mouse.move(pointNotQuiteA[0], pointNotQuiteA[1], { steps: 10 })
|
|
||||||
|
|
||||||
await page.mouse.click(pointNotQuiteA[0], pointNotQuiteA[1])
|
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
|
||||||
prevContent = await page.locator('.cm-content').innerText()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(expectedCode)
|
|
||||||
|
|
||||||
// exit sketch
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.removeCurrentCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
const codeTemplate = (
|
|
||||||
scale = 1,
|
|
||||||
fudge = 0
|
|
||||||
) => `const part001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %)
|
|
||||||
|> line([${roundOff(scale * 175.36)}, 0], %)
|
|
||||||
|> line([0, -${roundOff(scale * 175.37) + fudge}], %)
|
|
||||||
|> close(%)`
|
|
||||||
|
|
||||||
await doSnapAtDifferentScales([0, 100, 100], codeTemplate(0.01, 0.01))
|
|
||||||
|
|
||||||
await doSnapAtDifferentScales([0, 10000, 10000], codeTemplate())
|
|
||||||
})
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { test, expect, Download } from '@playwright/test'
|
import { test, expect } from '@playwright/test'
|
||||||
import { secrets } from './secrets'
|
import { secrets } from './secrets'
|
||||||
import { getUtils } from './test-utils'
|
import { getUtils } from './test-utils'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
@ -29,7 +29,96 @@ test.beforeEach(async ({ context, page }) => {
|
|||||||
await page.emulateMedia({ reducedMotion: 'reduce' })
|
await page.emulateMedia({ reducedMotion: 'reduce' })
|
||||||
})
|
})
|
||||||
|
|
||||||
test.setTimeout(60_000)
|
test.setTimeout(60000)
|
||||||
|
|
||||||
|
const commonPoints = {
|
||||||
|
startAt: '[26.38, -35.59]',
|
||||||
|
num1: 26.63,
|
||||||
|
num2: 53.01,
|
||||||
|
}
|
||||||
|
|
||||||
|
test('change camera, show planes', async ({ page, context }) => {
|
||||||
|
const u = getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
|
const camPos: [number, number, number] = [0, 85, 85]
|
||||||
|
await u.updateCamPosition(camPos)
|
||||||
|
|
||||||
|
// rotate
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
await page.mouse.move(700, 200)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.move(600, 300)
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await expect(page).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
|
||||||
|
await u.updateCamPosition(camPos)
|
||||||
|
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
// pan
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.move(600, 200)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.move(700, 200)
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await expect(page).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
|
||||||
|
await u.updateCamPosition(camPos)
|
||||||
|
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
// zoom
|
||||||
|
await page.keyboard.down('Control')
|
||||||
|
await page.mouse.move(700, 400)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.move(700, 300)
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await page.waitForTimeout(300)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await expect(page).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('exports of each format should work', async ({ page, context }) => {
|
test('exports of each format should work', async ({ page, context }) => {
|
||||||
// FYI this test doesn't work with only engine running locally
|
// FYI this test doesn't work with only engine running locally
|
||||||
@ -90,6 +179,8 @@ const part001 = startSketchOn('-XZ')
|
|||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: APP_NAME }).click()
|
||||||
|
|
||||||
interface Paths {
|
interface Paths {
|
||||||
modelPath: string
|
modelPath: string
|
||||||
imagePath: string
|
imagePath: string
|
||||||
@ -98,50 +189,19 @@ const part001 = startSketchOn('-XZ')
|
|||||||
const doExport = async (
|
const doExport = async (
|
||||||
output: Models['OutputFormat_type']
|
output: Models['OutputFormat_type']
|
||||||
): Promise<Paths> => {
|
): Promise<Paths> => {
|
||||||
await page.getByRole('button', { name: APP_NAME }).click()
|
await page.getByRole('button', { name: 'Export Model' }).click()
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Export Part' })
|
const exportSelect = page.getByTestId('export-type')
|
||||||
).toBeVisible()
|
await exportSelect.selectOption({ label: output.type })
|
||||||
await page.getByRole('button', { name: 'Export Part' }).click()
|
|
||||||
await expect(page.getByTestId('command-bar')).toBeVisible()
|
|
||||||
|
|
||||||
// Go through export via command bar
|
|
||||||
await page.getByRole('option', { name: output.type, exact: false }).click()
|
|
||||||
await page.locator('#arg-form').waitFor({ state: 'detached' })
|
|
||||||
if ('storage' in output) {
|
if ('storage' in output) {
|
||||||
await page.getByTestId('arg-name-storage').waitFor({ timeout: 1000 })
|
const storageSelect = page.getByTestId('export-storage')
|
||||||
await page.getByRole('button', { name: 'storage', exact: false }).click()
|
await storageSelect.selectOption({ label: output.storage })
|
||||||
await page
|
|
||||||
.getByRole('option', { name: output.storage, exact: false })
|
|
||||||
.click()
|
|
||||||
await page.locator('#arg-form').waitFor({ state: 'detached' })
|
|
||||||
}
|
|
||||||
await expect(page.getByText('Confirm Export')).toBeVisible()
|
|
||||||
|
|
||||||
const getPromiseAndResolve = () => {
|
|
||||||
let resolve: any = () => {}
|
|
||||||
const promise = new Promise<Download>((r) => {
|
|
||||||
resolve = r
|
|
||||||
})
|
|
||||||
return [promise, resolve]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [downloadPromise1, downloadResolve1] = getPromiseAndResolve()
|
const downloadPromise = page.waitForEvent('download')
|
||||||
const [downloadPromise2, downloadResolve2] = getPromiseAndResolve()
|
await page.getByRole('button', { name: 'Export', exact: true }).click()
|
||||||
let downloadCnt = 0
|
const download = await downloadPromise
|
||||||
|
|
||||||
page.on('download', async (download) => {
|
|
||||||
if (downloadCnt === 0) {
|
|
||||||
downloadResolve1(download)
|
|
||||||
} else if (downloadCnt === 1) {
|
|
||||||
downloadResolve2(download)
|
|
||||||
}
|
|
||||||
downloadCnt++
|
|
||||||
})
|
|
||||||
await page.getByRole('button', { name: 'Submit command' }).click()
|
|
||||||
|
|
||||||
// Handle download
|
|
||||||
const download = await downloadPromise1
|
|
||||||
const downloadLocationer = (extra = '', isImage = false) =>
|
const downloadLocationer = (extra = '', isImage = false) =>
|
||||||
`./e2e/playwright/export-snapshots/${output.type}-${
|
`./e2e/playwright/export-snapshots/${output.type}-${
|
||||||
'storage' in output ? output.storage : ''
|
'storage' in output ? output.storage : ''
|
||||||
@ -151,7 +211,7 @@ const part001 = startSketchOn('-XZ')
|
|||||||
|
|
||||||
if (output.type === 'gltf' && output.storage === 'standard') {
|
if (output.type === 'gltf' && output.storage === 'standard') {
|
||||||
// wait for second download
|
// wait for second download
|
||||||
const download2 = await downloadPromise2
|
const download2 = await page.waitForEvent('download')
|
||||||
await download.saveAs(downloadLocation)
|
await download.saveAs(downloadLocation)
|
||||||
await download2.saveAs(downloadLocation2)
|
await download2.saveAs(downloadLocation2)
|
||||||
|
|
||||||
@ -324,13 +384,13 @@ test('extrude on each default plane should be stable', async ({
|
|||||||
}) => {
|
}) => {
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|
const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|
||||||
|> startProfileAt([7.00, 4.40], %)
|
|> startProfileAt([0.70, 0.44], %)
|
||||||
|> line([6.60, -0.20], %)
|
|> line([0.66, -0.02], %)
|
||||||
|> line([2.80, 5.00], %)
|
|> line([0.28, 0.50], %)
|
||||||
|> line([-5.60, 4.40], %)
|
|> line([-0.56, 0.44], %)
|
||||||
|> line([-5.40, -3.80], %)
|
|> line([-0.54, -0.38], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(10.00, %)
|
|> extrude(1.00, %)
|
||||||
`
|
`
|
||||||
await context.addInitScript(async (code) => {
|
await context.addInitScript(async (code) => {
|
||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem('persistCode', code)
|
||||||
@ -375,23 +435,7 @@ test('extrude on each default plane should be stable', async ({
|
|||||||
await runSnapshotsForOtherPlanes('-YZ')
|
await runSnapshotsForOtherPlanes('-YZ')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Draft segments should look right', async ({ page, context }) => {
|
test('Draft segments should look right', async ({ page }) => {
|
||||||
await context.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'SETTINGS_PERSIST_KEY',
|
|
||||||
JSON.stringify({
|
|
||||||
baseUnit: 'in',
|
|
||||||
cameraControls: 'KittyCAD',
|
|
||||||
defaultDirectory: '',
|
|
||||||
defaultProjectName: 'project-$nnn',
|
|
||||||
onboardingStatus: 'dismissed',
|
|
||||||
showDebugPanel: true,
|
|
||||||
textWrapping: 'On',
|
|
||||||
theme: 'system',
|
|
||||||
unitSystem: 'imperial',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
const u = getUtils(page)
|
const u = getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
@ -424,7 +468,7 @@ test('Draft segments should look right', async ({ page, context }) => {
|
|||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([9.06, -12.22], %)`)
|
|> startProfileAt(${commonPoints.startAt}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
@ -438,8 +482,8 @@ test('Draft segments should look right', async ({ page, context }) => {
|
|||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
.toHaveText(`const part001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([9.06, -12.22], %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([9.14, 0], %)`)
|
|> line([${commonPoints.num1}, 0], %)`)
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
||||||
|
|
||||||
@ -449,202 +493,3 @@ test('Draft segments should look right', async ({ page, context }) => {
|
|||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Client side scene scale should match engine scale inch', async ({
|
|
||||||
page,
|
|
||||||
context,
|
|
||||||
}) => {
|
|
||||||
await context.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'SETTINGS_PERSIST_KEY',
|
|
||||||
JSON.stringify({
|
|
||||||
baseUnit: 'in',
|
|
||||||
cameraControls: 'KittyCAD',
|
|
||||||
defaultDirectory: '',
|
|
||||||
defaultProjectName: 'project-$nnn',
|
|
||||||
onboardingStatus: 'dismissed',
|
|
||||||
showDebugPanel: true,
|
|
||||||
textWrapping: 'On',
|
|
||||||
theme: 'system',
|
|
||||||
unitSystem: 'imperial',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
const u = getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
await u.openDebugPanel()
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).not.toBeDisabled()
|
|
||||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
|
||||||
|
|
||||||
// click on "Start Sketch" button
|
|
||||||
await u.clearCommandLogs()
|
|
||||||
await u.doAndWaitForImageDiff(
|
|
||||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
|
||||||
200
|
|
||||||
)
|
|
||||||
|
|
||||||
// select a plane
|
|
||||||
await page.mouse.click(700, 200)
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
|
||||||
`const part001 = startSketchOn('-XZ')`
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
|
||||||
|
|
||||||
const startXPx = 600
|
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
||||||
|> startProfileAt([9.06, -12.22], %)`)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
||||||
|> startProfileAt([9.06, -12.22], %)
|
|
||||||
|> line([9.14, 0], %)`)
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
||||||
|> startProfileAt([9.06, -12.22], %)
|
|
||||||
|> line([9.14, 0], %)
|
|
||||||
|> tangentialArcTo([27.34, -3.08], %)`)
|
|
||||||
|
|
||||||
// click tangential arc tool again to unequip it
|
|
||||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
// screen shot should show the sketch
|
|
||||||
await expect(page).toHaveScreenshot({
|
|
||||||
maxDiffPixels: 100,
|
|
||||||
})
|
|
||||||
|
|
||||||
// exit sketch
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
|
|
||||||
// wait for execution done
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.clearAndCloseDebugPanel()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
|
|
||||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
|
||||||
await expect(page).toHaveScreenshot({
|
|
||||||
maxDiffPixels: 100,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Client side scene scale should match engine scale mm', async ({
|
|
||||||
page,
|
|
||||||
context,
|
|
||||||
}) => {
|
|
||||||
await context.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'SETTINGS_PERSIST_KEY',
|
|
||||||
JSON.stringify({
|
|
||||||
baseUnit: 'mm',
|
|
||||||
cameraControls: 'KittyCAD',
|
|
||||||
defaultDirectory: '',
|
|
||||||
defaultProjectName: 'project-$nnn',
|
|
||||||
onboardingStatus: 'dismissed',
|
|
||||||
showDebugPanel: true,
|
|
||||||
textWrapping: 'On',
|
|
||||||
theme: 'system',
|
|
||||||
unitSystem: 'metric',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
const u = getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
|
||||||
await page.goto('/')
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
await u.openDebugPanel()
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).not.toBeDisabled()
|
|
||||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
|
|
||||||
|
|
||||||
// click on "Start Sketch" button
|
|
||||||
await u.clearCommandLogs()
|
|
||||||
await u.doAndWaitForImageDiff(
|
|
||||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
|
||||||
200
|
|
||||||
)
|
|
||||||
|
|
||||||
// select a plane
|
|
||||||
await page.mouse.click(700, 200)
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
|
||||||
`const part001 = startSketchOn('-XZ')`
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
|
|
||||||
|
|
||||||
const startXPx = 600
|
|
||||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
||||||
|> startProfileAt([230.03, -310.33], %)`)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
||||||
|> startProfileAt([230.03, -310.33], %)
|
|
||||||
|> line([232.2, 0], %)`)
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content'))
|
|
||||||
.toHaveText(`const part001 = startSketchOn('-XZ')
|
|
||||||
|> startProfileAt([230.03, -310.33], %)
|
|
||||||
|> line([232.2, 0], %)
|
|
||||||
|> tangentialArcTo([694.43, -78.12], %)`)
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Tangential Arc' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
// screen shot should show the sketch
|
|
||||||
await expect(page).toHaveScreenshot({
|
|
||||||
maxDiffPixels: 100,
|
|
||||||
})
|
|
||||||
|
|
||||||
// exit sketch
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
|
|
||||||
// wait for execution done
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
|
||||||
await u.clearAndCloseDebugPanel()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
|
|
||||||
// second screen shot should look almost identical, i.e. scale should be the same.
|
|
||||||
await expect(page).toHaveScreenshot({
|
|
||||||
maxDiffPixels: 100,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 75 KiB |
After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 52 KiB |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.15.5",
|
"version": "0.15.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.10.2",
|
"@codemirror/autocomplete": "^6.10.2",
|
||||||
@ -10,11 +10,12 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.17",
|
"@headlessui/react": "^1.7.17",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^0.0.54",
|
"@kittycad/lib": "^0.0.53",
|
||||||
"@lezer/javascript": "^1.4.9",
|
"@lezer/javascript": "^1.4.9",
|
||||||
"@open-rpc/client-js": "^1.8.1",
|
"@open-rpc/client-js": "^1.8.1",
|
||||||
"@react-hook/resize-observer": "^1.2.6",
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
"@replit/codemirror-interact": "^6.3.0",
|
"@replit/codemirror-interact": "^6.3.0",
|
||||||
|
"@sentry/react": "^7.77.0",
|
||||||
"@tauri-apps/api": "^1.5.1",
|
"@tauri-apps/api": "^1.5.1",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
|
@ -1,34 +1,16 @@
|
|||||||
import re
|
|
||||||
import os
|
|
||||||
import requests
|
import requests
|
||||||
|
import os
|
||||||
|
|
||||||
webhook_url = os.getenv('DISCORD_WEBHOOK_URL')
|
webhook_url = os.getenv('DISCORD_WEBHOOK_URL')
|
||||||
release_version = os.getenv('RELEASE_VERSION')
|
release_version = os.getenv('RELEASE_VERSION')
|
||||||
release_body = os.getenv('RELEASE_BODY')
|
release_body = os.getenv('RELEASE_BODY')
|
||||||
|
|
||||||
# Regular expression to match URLs
|
# message to send to Discord
|
||||||
url_pattern = r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\\(\\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)'
|
|
||||||
|
|
||||||
# Function to encase URLs in <>
|
|
||||||
def encase_urls_with_angle_brackets(match):
|
|
||||||
url = match.group(0)
|
|
||||||
return f'<{url}>'
|
|
||||||
|
|
||||||
# Replace all URLs in the release_body with their <> enclosed version
|
|
||||||
modified_release_body = re.sub(url_pattern, encase_urls_with_angle_brackets, release_body)
|
|
||||||
|
|
||||||
# Ensure the modified_release_body does not exceed Discord's character limit
|
|
||||||
max_length = 500 # Adjust as needed
|
|
||||||
if len(modified_release_body) > max_length:
|
|
||||||
modified_release_body = modified_release_body[:max_length].rsplit(' ', 1)[0] # Avoid cutting off in the middle of a word
|
|
||||||
modified_release_body += "... for full changelog, check out the link above."
|
|
||||||
|
|
||||||
# Message to send to Discord
|
|
||||||
data = {
|
data = {
|
||||||
"content":
|
"content":
|
||||||
f'''
|
f'''
|
||||||
**{release_version}** is now available! Check out the latest features and improvements here: <https://zoo.dev/modeling-app/download>
|
**{release_version}** is now available! Check out the latest features and improvements here: https://zoo.dev/modeling-app/download
|
||||||
{modified_release_body}
|
{release_body}
|
||||||
''',
|
''',
|
||||||
"username": "Modeling App Release Updates",
|
"username": "Modeling App Release Updates",
|
||||||
"avatar_url": "https://raw.githubusercontent.com/KittyCAD/modeling-app/main/public/discord-avatar.png"
|
"avatar_url": "https://raw.githubusercontent.com/KittyCAD/modeling-app/main/public/discord-avatar.png"
|
||||||
@ -41,7 +23,4 @@ response = requests.post(webhook_url, json=data)
|
|||||||
if response.status_code == 204:
|
if response.status_code == 204:
|
||||||
print("Successfully sent the message to Discord.")
|
print("Successfully sent the message to Discord.")
|
||||||
else:
|
else:
|
||||||
print(f"Failed to send the message to Discord. Status code: {response.status_code}, Response: {response.text}")
|
print("Failed to send the message to Discord.")
|
||||||
|
|
||||||
print(modified_release_body)
|
|
||||||
print(data["content"])
|
|
41
src-tauri/Cargo.lock
generated
@ -67,9 +67,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.80"
|
version = "1.0.79"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "app"
|
name = "app"
|
||||||
@ -1664,9 +1664,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad"
|
name = "kittycad"
|
||||||
version = "0.2.59"
|
version = "0.2.53"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4080db4364c103601db486e4a8aa889ea56c011991e4c454373d8050a165d3da"
|
checksum = "a086e1a1bbddb3b38959c0f0ce6de6b3a3b7566e38e0b7d5fb101e91911beed4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1934,9 +1934,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.11"
|
version = "0.8.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||||
@ -3235,9 +3235,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.197"
|
version = "1.0.196"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
|
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@ -3253,9 +3253,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.197"
|
version = "1.0.196"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
|
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -3275,11 +3275,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.112"
|
version = "1.0.113"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4d1bd37ce2324cf3bf85e5a25f96eb4baf0d5aa6eba43e7ae8958870c4ec48ed"
|
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.0.0",
|
|
||||||
"itoa 1.0.6",
|
"itoa 1.0.6",
|
||||||
"ryu",
|
"ryu",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3761,15 +3760,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "1.6.0"
|
version = "1.5.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0da520ff07c0745199204f7a7a62a8c6ee1666313b792b051ca170eca04649aa"
|
checksum = "fd27c04b9543776a972c86ccf70660b517ecabbeced9fb58d8b961a13ad129af"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bytes",
|
"bytes",
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"dirs-next",
|
"dirs-next",
|
||||||
"dunce",
|
|
||||||
"embed_plist",
|
"embed_plist",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"flate2",
|
"flate2",
|
||||||
@ -3780,7 +3778,6 @@ dependencies = [
|
|||||||
"heck 0.4.1",
|
"heck 0.4.1",
|
||||||
"http",
|
"http",
|
||||||
"ignore",
|
"ignore",
|
||||||
"indexmap 1.9.3",
|
|
||||||
"objc",
|
"objc",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"open",
|
"open",
|
||||||
@ -3875,7 +3872,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs-extra"
|
name = "tauri-plugin-fs-extra"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#19aa2204115e7304681cb40faf7512aba525bc5e"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#01211ff0759d578e0e9ac8c98c31fdf09077eb34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3907,9 +3904,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime-wry"
|
name = "tauri-runtime-wry"
|
||||||
version = "0.14.5"
|
version = "0.14.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "067c56fc153b3caf406d7cd6de4486c80d1d66c0f414f39e94cb2f5543f6445f"
|
checksum = "6cae61fbc731f690a4899681c9052dde6d05b159b44563ace8186fc1bfb7d158"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cocoa",
|
"cocoa",
|
||||||
"gtk",
|
"gtk",
|
||||||
@ -3927,9 +3924,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-utils"
|
name = "tauri-utils"
|
||||||
version = "1.5.3"
|
version = "1.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "75ad0bbb31fccd1f4c56275d0a5c3abdf1f59999f72cb4ef8b79b4ed42082a21"
|
checksum = "ece74810b1d3d44f29f732a7ae09a63183d63949bbdd59c61f8ed2a1b70150db"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"brotli",
|
"brotli",
|
||||||
"ctor",
|
"ctor",
|
||||||
|
@ -16,11 +16,11 @@ tauri-build = { version = "1.5.1", features = [] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
kittycad = "0.2.59"
|
kittycad = "0.2.53"
|
||||||
oauth2 = "4.4.2"
|
oauth2 = "4.4.2"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tauri = { version = "1.6.0", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "devtools"] }
|
tauri = { version = "1.5.4", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "devtools"] }
|
||||||
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
tokio = { version = "1.36.0", features = ["time"] }
|
tokio = { version = "1.36.0", features = ["time"] }
|
||||||
toml = "0.8.2"
|
toml = "0.8.2"
|
||||||
|
@ -7,6 +7,7 @@ use std::io::Read;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use oauth2::TokenResponse;
|
use oauth2::TokenResponse;
|
||||||
|
use std::process::Command;
|
||||||
use tauri::{InvokeError, Manager};
|
use tauri::{InvokeError, Manager};
|
||||||
const DEFAULT_HOST: &str = "https://api.kittycad.io";
|
const DEFAULT_HOST: &str = "https://api.kittycad.io";
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "zoo-modeling-app",
|
"productName": "zoo-modeling-app",
|
||||||
"version": "0.15.5"
|
"version": "0.15.2"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
@ -3,8 +3,15 @@ import {
|
|||||||
createBrowserRouter,
|
createBrowserRouter,
|
||||||
Outlet,
|
Outlet,
|
||||||
redirect,
|
redirect,
|
||||||
|
useLocation,
|
||||||
RouterProvider,
|
RouterProvider,
|
||||||
} from 'react-router-dom'
|
} from 'react-router-dom'
|
||||||
|
import {
|
||||||
|
matchRoutes,
|
||||||
|
createRoutesFromChildren,
|
||||||
|
useNavigationType,
|
||||||
|
} from 'react-router'
|
||||||
|
import { useEffect } from 'react'
|
||||||
import { ErrorPage } from './components/ErrorPage'
|
import { ErrorPage } from './components/ErrorPage'
|
||||||
import { Settings } from './routes/Settings'
|
import { Settings } from './routes/Settings'
|
||||||
import Onboarding, { onboardingRoutes } from './routes/Onboarding'
|
import Onboarding, { onboardingRoutes } from './routes/Onboarding'
|
||||||
@ -31,6 +38,8 @@ import { ContextFrom } from 'xstate'
|
|||||||
import CommandBarProvider, {
|
import CommandBarProvider, {
|
||||||
CommandBar,
|
CommandBar,
|
||||||
} from 'components/CommandBar/CommandBar'
|
} from 'components/CommandBar/CommandBar'
|
||||||
|
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
||||||
|
import * as Sentry from '@sentry/react'
|
||||||
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
||||||
import { KclContextProvider, kclManager } from 'lang/KclSingleton'
|
import { KclContextProvider, kclManager } from 'lang/KclSingleton'
|
||||||
import FileMachineProvider from 'components/FileMachineProvider'
|
import FileMachineProvider from 'components/FileMachineProvider'
|
||||||
@ -39,6 +48,38 @@ import { paths } from 'lib/paths'
|
|||||||
import { IndexLoaderData, HomeLoaderData } from 'lib/types'
|
import { IndexLoaderData, HomeLoaderData } from 'lib/types'
|
||||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||||
|
|
||||||
|
if (VITE_KC_SENTRY_DSN && !TEST) {
|
||||||
|
Sentry.init({
|
||||||
|
dsn: VITE_KC_SENTRY_DSN,
|
||||||
|
// TODO(paultag): pass in the right env here.
|
||||||
|
// environment: "production",
|
||||||
|
integrations: [
|
||||||
|
new Sentry.BrowserTracing({
|
||||||
|
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
|
||||||
|
useEffect,
|
||||||
|
useLocation,
|
||||||
|
useNavigationType,
|
||||||
|
createRoutesFromChildren,
|
||||||
|
matchRoutes
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
new Sentry.Replay(),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Set tracesSampleRate to 1.0 to capture 100%
|
||||||
|
// of transactions for performance monitoring.
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
|
|
||||||
|
// TODO: Add in kittycad.io endpoints
|
||||||
|
tracePropagationTargets: ['localhost'],
|
||||||
|
|
||||||
|
// Capture Replay for 10% of all sessions,
|
||||||
|
// plus for 100% of sessions with an error
|
||||||
|
replaysSessionSampleRate: 0.1,
|
||||||
|
replaysOnErrorSampleRate: 1.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const BROWSER_FILE_NAME = 'new'
|
export const BROWSER_FILE_NAME = 'new'
|
||||||
|
|
||||||
type CreateBrowserRouterArg = Parameters<typeof createBrowserRouter>[0]
|
type CreateBrowserRouterArg = Parameters<typeof createBrowserRouter>[0]
|
||||||
|
@ -16,11 +16,7 @@ import {
|
|||||||
SKETCH_LAYER,
|
SKETCH_LAYER,
|
||||||
ZOOM_MAGIC_NUMBER,
|
ZOOM_MAGIC_NUMBER,
|
||||||
} from './sceneInfra'
|
} from './sceneInfra'
|
||||||
import {
|
import { EngineCommand, engineCommandManager } from 'lang/std/engineConnection'
|
||||||
EngineCommand,
|
|
||||||
Subscription,
|
|
||||||
engineCommandManager,
|
|
||||||
} from 'lang/std/engineConnection'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { deg2Rad } from 'lib/utils2d'
|
import { deg2Rad } from 'lib/utils2d'
|
||||||
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
||||||
@ -32,12 +28,6 @@ const FRAMES_TO_ANIMATE_IN = 30
|
|||||||
|
|
||||||
const tempQuaternion = new Quaternion() // just used for maths
|
const tempQuaternion = new Quaternion() // just used for maths
|
||||||
|
|
||||||
type interactionType = 'pan' | 'rotate' | 'zoom'
|
|
||||||
|
|
||||||
const throttledEngCmd = throttle((cmd: EngineCommand) => {
|
|
||||||
engineCommandManager.sendSceneCommand(cmd)
|
|
||||||
}, 1000 / 15)
|
|
||||||
|
|
||||||
interface ThreeCamValues {
|
interface ThreeCamValues {
|
||||||
position: Vector3
|
position: Vector3
|
||||||
quaternion: Quaternion
|
quaternion: Quaternion
|
||||||
@ -120,11 +110,10 @@ const throttledUpdateEngineFov = throttle(
|
|||||||
lastCmdDelay
|
lastCmdDelay
|
||||||
) as any as number
|
) as any as number
|
||||||
},
|
},
|
||||||
1000 / 30
|
1000 / 15
|
||||||
)
|
)
|
||||||
|
|
||||||
export class CameraControls {
|
export class CameraControls {
|
||||||
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
|
|
||||||
camera: PerspectiveCamera | OrthographicCamera
|
camera: PerspectiveCamera | OrthographicCamera
|
||||||
target: Vector3
|
target: Vector3
|
||||||
domElement: HTMLCanvasElement
|
domElement: HTMLCanvasElement
|
||||||
@ -162,17 +151,6 @@ export class CameraControls {
|
|||||||
get isPerspective() {
|
get isPerspective() {
|
||||||
return this.camera instanceof PerspectiveCamera
|
return this.camera instanceof PerspectiveCamera
|
||||||
}
|
}
|
||||||
private debounceTimer = 0
|
|
||||||
|
|
||||||
handleStart = () => {
|
|
||||||
if (this.debounceTimer) clearTimeout(this.debounceTimer)
|
|
||||||
this._isCamMovingCallback(true, false)
|
|
||||||
}
|
|
||||||
handleEnd = () => {
|
|
||||||
this.debounceTimer = setTimeout(() => {
|
|
||||||
this._isCamMovingCallback(false, false)
|
|
||||||
}, 400) as any as number
|
|
||||||
}
|
|
||||||
|
|
||||||
// reacts hooks into some of this singleton's properties
|
// reacts hooks into some of this singleton's properties
|
||||||
reactCameraProperties: ReactCameraProperties = {
|
reactCameraProperties: ReactCameraProperties = {
|
||||||
@ -209,7 +187,6 @@ export class CameraControls {
|
|||||||
this.camera.zoom = camProps.zoom || 1
|
this.camera.zoom = camProps.zoom || 1
|
||||||
}
|
}
|
||||||
this.camera.updateProjectionMatrix()
|
this.camera.updateProjectionMatrix()
|
||||||
console.log('doing this thing', camProps)
|
|
||||||
this.update(true)
|
this.update(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,46 +209,6 @@ export class CameraControls {
|
|||||||
this.onWindowResize()
|
this.onWindowResize()
|
||||||
|
|
||||||
this.update()
|
this.update()
|
||||||
this._usePerspectiveCamera()
|
|
||||||
|
|
||||||
const cb: Subscription<
|
|
||||||
'default_camera_zoom' | 'camera_drag_end' | 'default_camera_get_settings'
|
|
||||||
>['callback'] = ({ data, type }) => {
|
|
||||||
const camSettings = data.settings
|
|
||||||
this.camera.position.set(
|
|
||||||
camSettings.pos.x,
|
|
||||||
camSettings.pos.y,
|
|
||||||
camSettings.pos.z
|
|
||||||
)
|
|
||||||
this.target.set(
|
|
||||||
camSettings.center.x,
|
|
||||||
camSettings.center.y,
|
|
||||||
camSettings.center.z
|
|
||||||
)
|
|
||||||
if (this.camera instanceof PerspectiveCamera && camSettings.fov_y) {
|
|
||||||
this.camera.fov = camSettings.fov_y
|
|
||||||
} else if (
|
|
||||||
this.camera instanceof OrthographicCamera &&
|
|
||||||
camSettings.ortho_scale
|
|
||||||
) {
|
|
||||||
this.camera.zoom = camSettings.ortho_scale
|
|
||||||
}
|
|
||||||
this.onCameraChange()
|
|
||||||
}
|
|
||||||
setTimeout(() => {
|
|
||||||
engineCommandManager.subscribeTo({
|
|
||||||
event: 'camera_drag_end',
|
|
||||||
callback: cb,
|
|
||||||
})
|
|
||||||
engineCommandManager.subscribeTo({
|
|
||||||
event: 'default_camera_zoom',
|
|
||||||
callback: cb,
|
|
||||||
})
|
|
||||||
engineCommandManager.subscribeTo({
|
|
||||||
event: 'default_camera_get_settings',
|
|
||||||
callback: cb,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _isCamMovingCallback: (isMoving: boolean, isTween: boolean) => void =
|
private _isCamMovingCallback: (isMoving: boolean, isTween: boolean) => void =
|
||||||
@ -305,21 +242,6 @@ export class CameraControls {
|
|||||||
onMouseDown = (event: MouseEvent) => {
|
onMouseDown = (event: MouseEvent) => {
|
||||||
this.isDragging = true
|
this.isDragging = true
|
||||||
this.mouseDownPosition.set(event.clientX, event.clientY)
|
this.mouseDownPosition.set(event.clientX, event.clientY)
|
||||||
let interaction = this.getInteractionType(event)
|
|
||||||
if (interaction === 'none') return
|
|
||||||
this.handleStart()
|
|
||||||
|
|
||||||
if (this.syncDirection === 'engineToClient') {
|
|
||||||
void engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'camera_drag_start',
|
|
||||||
interaction,
|
|
||||||
window: { x: event.clientX, y: event.clientY },
|
|
||||||
},
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseMove = (event: MouseEvent) => {
|
onMouseMove = (event: MouseEvent) => {
|
||||||
@ -330,34 +252,36 @@ export class CameraControls {
|
|||||||
.sub(this.mouseDownPosition)
|
.sub(this.mouseDownPosition)
|
||||||
this.mouseDownPosition.copy(this.mouseNewPosition)
|
this.mouseDownPosition.copy(this.mouseNewPosition)
|
||||||
|
|
||||||
const interaction = this.getInteractionType(event)
|
let state: 'pan' | 'rotate' | 'zoom' = 'pan'
|
||||||
if (interaction === 'none') return
|
|
||||||
|
|
||||||
if (this.syncDirection === 'engineToClient') {
|
if (this.interactionGuards.pan.callback(event as any)) {
|
||||||
throttledEngCmd({
|
if (this.enablePan === false) return
|
||||||
type: 'modeling_cmd_req',
|
// handleMouseDownPan(event)
|
||||||
cmd: {
|
state = 'pan'
|
||||||
type: 'camera_drag_move',
|
} else if (this.interactionGuards.rotate.callback(event as any)) {
|
||||||
interaction,
|
if (this.enableRotate === false) return
|
||||||
window: { x: event.clientX, y: event.clientY },
|
// handleMouseDownRotate(event)
|
||||||
},
|
state = 'rotate'
|
||||||
cmd_id: uuidv4(),
|
} else if (this.interactionGuards.zoom.dragCallback(event as any)) {
|
||||||
})
|
if (this.enableZoom === false) return
|
||||||
|
// handleMouseDownDolly(event)
|
||||||
|
state = 'zoom'
|
||||||
|
} else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement camera movement logic here based on deltaMove
|
// Implement camera movement logic here based on deltaMove
|
||||||
// For example, for rotating the camera around the target:
|
// For example, for rotating the camera around the target:
|
||||||
if (interaction === 'rotate') {
|
if (state === 'rotate') {
|
||||||
this.pendingRotation = this.pendingRotation
|
this.pendingRotation = this.pendingRotation
|
||||||
? this.pendingRotation
|
? this.pendingRotation
|
||||||
: new Vector2()
|
: new Vector2()
|
||||||
this.pendingRotation.x += deltaMove.x
|
this.pendingRotation.x += deltaMove.x
|
||||||
this.pendingRotation.y += deltaMove.y
|
this.pendingRotation.y += deltaMove.y
|
||||||
} else if (interaction === 'zoom') {
|
} else if (state === 'zoom') {
|
||||||
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
||||||
this.pendingZoom *= 1 + deltaMove.y * 0.01
|
this.pendingZoom *= 1 + deltaMove.y * 0.01
|
||||||
} else if (interaction === 'pan') {
|
} else if (state === 'pan') {
|
||||||
this.pendingPan = this.pendingPan ? this.pendingPan : new Vector2()
|
this.pendingPan = this.pendingPan ? this.pendingPan : new Vector2()
|
||||||
let distance = this.camera.position.distanceTo(this.target)
|
let distance = this.camera.position.distanceTo(this.target)
|
||||||
if (this.camera instanceof OrthographicCamera) {
|
if (this.camera instanceof OrthographicCamera) {
|
||||||
@ -373,52 +297,15 @@ export class CameraControls {
|
|||||||
|
|
||||||
onMouseUp = (event: MouseEvent) => {
|
onMouseUp = (event: MouseEvent) => {
|
||||||
this.isDragging = false
|
this.isDragging = false
|
||||||
this.handleEnd()
|
|
||||||
if (this.syncDirection === 'engineToClient') {
|
|
||||||
const interaction = this.getInteractionType(event)
|
|
||||||
if (interaction === 'none') return
|
|
||||||
void engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'camera_drag_end',
|
|
||||||
interaction,
|
|
||||||
window: { x: event.clientX, y: event.clientY },
|
|
||||||
},
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseWheel = (event: WheelEvent) => {
|
onMouseWheel = (event: WheelEvent) => {
|
||||||
// Assume trackpad if the deltas are small and integers
|
// Assume trackpad if the deltas are small and integers
|
||||||
this.handleStart()
|
|
||||||
|
|
||||||
if (this.syncDirection === 'engineToClient') {
|
|
||||||
const interactions = this.interactionGuards.zoom.scrollCallback(
|
|
||||||
event as any
|
|
||||||
)
|
|
||||||
if (!interactions) {
|
|
||||||
this.handleEnd()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
throttledEngCmd({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_zoom',
|
|
||||||
magnitude: -event.deltaY * 0.4,
|
|
||||||
},
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
})
|
|
||||||
this.handleEnd()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTrackpad = Math.abs(event.deltaY) <= 1 || event.deltaY % 1 === 0
|
const isTrackpad = Math.abs(event.deltaY) <= 1 || event.deltaY % 1 === 0
|
||||||
|
|
||||||
const zoomSpeed = isTrackpad ? 0.02 : 0.1 // Reduced zoom speed for trackpad
|
const zoomSpeed = isTrackpad ? 0.02 : 0.1 // Reduced zoom speed for trackpad
|
||||||
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
||||||
this.pendingZoom *= 1 + (event.deltaY > 0 ? zoomSpeed : -zoomSpeed)
|
this.pendingZoom *= 1 + (event.deltaY > 0 ? zoomSpeed : -zoomSpeed)
|
||||||
this.handleEnd()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useOrthographicCamera = () => {
|
useOrthographicCamera = () => {
|
||||||
@ -471,7 +358,7 @@ export class CameraControls {
|
|||||||
|
|
||||||
return this.camera
|
return this.camera
|
||||||
}
|
}
|
||||||
_usePerspectiveCamera = () => {
|
usePerspectiveCamera = () => {
|
||||||
const { x: px, y: py, z: pz } = this.camera.position
|
const { x: px, y: py, z: pz } = this.camera.position
|
||||||
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
||||||
const zoom = this.camera.zoom
|
const zoom = this.camera.zoom
|
||||||
@ -487,17 +374,14 @@ export class CameraControls {
|
|||||||
)
|
)
|
||||||
direction.normalize()
|
direction.normalize()
|
||||||
this.camera.position.copy(this.target).addScaledVector(direction, distance)
|
this.camera.position.copy(this.target).addScaledVector(direction, distance)
|
||||||
}
|
|
||||||
usePerspectiveCamera = () => {
|
|
||||||
this._usePerspectiveCamera()
|
|
||||||
engineCommandManager.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_set_perspective',
|
type: 'default_camera_set_perspective',
|
||||||
parameters: {
|
parameters: {
|
||||||
fov_y:
|
fov_y: this.camera.fov,
|
||||||
this.camera instanceof PerspectiveCamera ? this.camera.fov : 45,
|
|
||||||
...calculateNearFarFromFOV(this.lastPerspectiveFov),
|
...calculateNearFarFromFOV(this.lastPerspectiveFov),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -570,7 +454,7 @@ export class CameraControls {
|
|||||||
update = (forceUpdate = false) => {
|
update = (forceUpdate = false) => {
|
||||||
// If there are any changes that need to be applied to the camera, apply them here.
|
// If there are any changes that need to be applied to the camera, apply them here.
|
||||||
|
|
||||||
let didChange = false
|
let didChange = forceUpdate
|
||||||
if (this.pendingRotation) {
|
if (this.pendingRotation) {
|
||||||
this.rotateCamera(this.pendingRotation.x, this.pendingRotation.y)
|
this.rotateCamera(this.pendingRotation.x, this.pendingRotation.y)
|
||||||
this.pendingRotation = null // Clear the pending rotation after applying it
|
this.pendingRotation = null // Clear the pending rotation after applying it
|
||||||
@ -622,8 +506,8 @@ export class CameraControls {
|
|||||||
|
|
||||||
// Update the camera's matrices
|
// Update the camera's matrices
|
||||||
this.camera.updateMatrixWorld()
|
this.camera.updateMatrixWorld()
|
||||||
if (didChange || forceUpdate) {
|
if (didChange) {
|
||||||
this.onCameraChange(forceUpdate)
|
this.onCameraChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
// damping would be implemented here in update if we choose to add it.
|
// damping would be implemented here in update if we choose to add it.
|
||||||
@ -734,10 +618,6 @@ export class CameraControls {
|
|||||||
duration = 500,
|
duration = 500,
|
||||||
toOrthographic = true
|
toOrthographic = true
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (this.syncDirection === 'engineToClient')
|
|
||||||
console.warn(
|
|
||||||
'tweenCameraToQuaternion not design to work with engineToClient syncDirection.'
|
|
||||||
)
|
|
||||||
const isVertical = isQuaternionVertical(targetQuaternion)
|
const isVertical = isQuaternionVertical(targetQuaternion)
|
||||||
let remainingDuration = duration
|
let remainingDuration = duration
|
||||||
if (isVertical) {
|
if (isVertical) {
|
||||||
@ -820,10 +700,6 @@ export class CameraControls {
|
|||||||
|
|
||||||
animateToOrthographic = () =>
|
animateToOrthographic = () =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
if (this.syncDirection === 'engineToClient')
|
|
||||||
console.warn(
|
|
||||||
'animate To Orthographic not design to work with engineToClient syncDirection.'
|
|
||||||
)
|
|
||||||
this.isFovAnimationInProgress = true
|
this.isFovAnimationInProgress = true
|
||||||
let currentFov = this.lastPerspectiveFov
|
let currentFov = this.lastPerspectiveFov
|
||||||
this.fovBeforeOrtho = currentFov
|
this.fovBeforeOrtho = currentFov
|
||||||
@ -857,10 +733,6 @@ export class CameraControls {
|
|||||||
})
|
})
|
||||||
animateToPerspective = () =>
|
animateToPerspective = () =>
|
||||||
new Promise((resolve) => {
|
new Promise((resolve) => {
|
||||||
if (this.syncDirection === 'engineToClient')
|
|
||||||
console.warn(
|
|
||||||
'animate To Perspective not design to work with engineToClient syncDirection.'
|
|
||||||
)
|
|
||||||
this.isFovAnimationInProgress = true
|
this.isFovAnimationInProgress = true
|
||||||
// Immediately set the camera to perspective with a very low FOV
|
// Immediately set the camera to perspective with a very low FOV
|
||||||
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
||||||
@ -899,7 +771,7 @@ export class CameraControls {
|
|||||||
this.reactCameraPropertiesCallback(a)
|
this.reactCameraPropertiesCallback(a)
|
||||||
}, 200)
|
}, 200)
|
||||||
|
|
||||||
onCameraChange = (forceUpdate = false) => {
|
onCameraChange = () => {
|
||||||
const distance = this.target.distanceTo(this.camera.position)
|
const distance = this.target.distanceTo(this.camera.position)
|
||||||
if (this.camera.far / 2.1 < distance || this.camera.far / 1.9 > distance) {
|
if (this.camera.far / 2.1 < distance || this.camera.far / 1.9 > distance) {
|
||||||
this.camera.far = distance * 2
|
this.camera.far = distance * 2
|
||||||
@ -907,7 +779,6 @@ export class CameraControls {
|
|||||||
this.camera.updateProjectionMatrix()
|
this.camera.updateProjectionMatrix()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.syncDirection === 'clientToEngine' || forceUpdate)
|
|
||||||
throttledUpdateEngineCamera({
|
throttledUpdateEngineCamera({
|
||||||
quaternion: this.camera.quaternion,
|
quaternion: this.camera.quaternion,
|
||||||
position: this.camera.position,
|
position: this.camera.position,
|
||||||
@ -935,18 +806,9 @@ export class CameraControls {
|
|||||||
})
|
})
|
||||||
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
||||||
}
|
}
|
||||||
getInteractionType = (event: any) =>
|
|
||||||
_getInteractionType(
|
|
||||||
this.interactionGuards,
|
|
||||||
event,
|
|
||||||
this.enablePan,
|
|
||||||
this.enableRotate,
|
|
||||||
this.enableZoom
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pure function helpers
|
// currently duplicated, delete one
|
||||||
|
|
||||||
function calculateNearFarFromFOV(fov: number) {
|
function calculateNearFarFromFOV(fov: number) {
|
||||||
const nearFarRatio = (fov - 3) / (45 - 3)
|
const nearFarRatio = (fov - 3) / (45 - 3)
|
||||||
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
|
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
|
||||||
@ -954,6 +816,7 @@ function calculateNearFarFromFOV(fov: number) {
|
|||||||
return { z_near: 0.1, z_far }
|
return { z_near: 0.1, z_far }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// currently duplicated, delete one
|
||||||
function convertThreeCamValuesToEngineCam({
|
function convertThreeCamValuesToEngineCam({
|
||||||
target,
|
target,
|
||||||
position,
|
position,
|
||||||
@ -994,6 +857,8 @@ function convertThreeCamValuesToEngineCam({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pure function helpers
|
||||||
|
|
||||||
function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
|
function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
|
||||||
// Direction from position to target, normalized.
|
// Direction from position to target, normalized.
|
||||||
let direction = new Vector3().subVectors(target, position).normalize()
|
let direction = new Vector3().subVectors(target, position).normalize()
|
||||||
@ -1012,17 +877,3 @@ function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
|
|||||||
|
|
||||||
return quaternion
|
return quaternion
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getInteractionType(
|
|
||||||
interactionGuards: MouseGuard,
|
|
||||||
event: any,
|
|
||||||
enablePan: boolean,
|
|
||||||
enableRotate: boolean,
|
|
||||||
enableZoom: boolean
|
|
||||||
): interactionType | 'none' {
|
|
||||||
let state: interactionType | 'none' = 'none'
|
|
||||||
if (enablePan && interactionGuards.pan.callback(event)) return 'pan'
|
|
||||||
if (enableRotate && interactionGuards.rotate.callback(event)) return 'rotate'
|
|
||||||
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
|
@ -3,19 +3,17 @@ import {
|
|||||||
DoubleSide,
|
DoubleSide,
|
||||||
ExtrudeGeometry,
|
ExtrudeGeometry,
|
||||||
Group,
|
Group,
|
||||||
Intersection,
|
|
||||||
LineCurve3,
|
LineCurve3,
|
||||||
Matrix4,
|
Matrix4,
|
||||||
Mesh,
|
Mesh,
|
||||||
MeshBasicMaterial,
|
MeshBasicMaterial,
|
||||||
Object3D,
|
|
||||||
Object3DEventMap,
|
|
||||||
OrthographicCamera,
|
OrthographicCamera,
|
||||||
PerspectiveCamera,
|
PerspectiveCamera,
|
||||||
PlaneGeometry,
|
PlaneGeometry,
|
||||||
Quaternion,
|
Quaternion,
|
||||||
Scene,
|
Scene,
|
||||||
Shape,
|
Shape,
|
||||||
|
SphereGeometry,
|
||||||
Vector2,
|
Vector2,
|
||||||
Vector3,
|
Vector3,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
@ -27,7 +25,6 @@ import {
|
|||||||
defaultPlaneColor,
|
defaultPlaneColor,
|
||||||
getSceneScale,
|
getSceneScale,
|
||||||
INTERSECTION_PLANE_LAYER,
|
INTERSECTION_PLANE_LAYER,
|
||||||
OnMouseEnterLeaveArgs,
|
|
||||||
RAYCASTABLE_PLANE,
|
RAYCASTABLE_PLANE,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
SKETCH_GROUP_SEGMENTS,
|
SKETCH_GROUP_SEGMENTS,
|
||||||
@ -60,7 +57,6 @@ import { engineCommandManager } from 'lang/std/engineConnection'
|
|||||||
import {
|
import {
|
||||||
createArcGeometry,
|
createArcGeometry,
|
||||||
dashedStraight,
|
dashedStraight,
|
||||||
profileStart,
|
|
||||||
straightSegment,
|
straightSegment,
|
||||||
tangentialArcToSegment,
|
tangentialArcToSegment,
|
||||||
} from './segments'
|
} from './segments'
|
||||||
@ -68,7 +64,7 @@ import {
|
|||||||
addCloseToPipe,
|
addCloseToPipe,
|
||||||
addNewSketchLn,
|
addNewSketchLn,
|
||||||
changeSketchArguments,
|
changeSketchArguments,
|
||||||
updateStartProfileAtArgs,
|
compareVec2Epsilon2,
|
||||||
} from 'lang/std/sketch'
|
} from 'lang/std/sketch'
|
||||||
import { isReducedMotion, throttle } from 'lib/utils'
|
import { isReducedMotion, throttle } from 'lib/utils'
|
||||||
import {
|
import {
|
||||||
@ -90,7 +86,7 @@ export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
|
|||||||
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
|
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
|
||||||
export const TANGENTIAL_ARC_TO__SEGMENT_DASH =
|
export const TANGENTIAL_ARC_TO__SEGMENT_DASH =
|
||||||
'tangential-arc-to-segment-body-dashed'
|
'tangential-arc-to-segment-body-dashed'
|
||||||
export const PROFILE_START = 'profile-start'
|
export const EXTRA_SEGMENT_HANDLE = 'extraSegmentHandle'
|
||||||
|
|
||||||
// This singleton Class is responsible for all of the things the user sees and interacts with.
|
// This singleton Class is responsible for all of the things the user sees and interacts with.
|
||||||
// That mostly mean sketch elements.
|
// That mostly mean sketch elements.
|
||||||
@ -112,10 +108,9 @@ class SceneEntities {
|
|||||||
|
|
||||||
Object.values(this.activeSegments).forEach((segment) => {
|
Object.values(this.activeSegments).forEach((segment) => {
|
||||||
const factor =
|
const factor =
|
||||||
(sceneInfra.camControls.camera instanceof OrthographicCamera
|
sceneInfra.camControls.camera instanceof OrthographicCamera
|
||||||
? orthoFactor
|
? orthoFactor
|
||||||
: perspScale(sceneInfra.camControls.camera, segment)) /
|
: perspScale(sceneInfra.camControls.camera, segment)
|
||||||
sceneInfra._baseUnitMultiplier
|
|
||||||
if (
|
if (
|
||||||
segment.userData.from &&
|
segment.userData.from &&
|
||||||
segment.userData.to &&
|
segment.userData.to &&
|
||||||
@ -143,9 +138,6 @@ class SceneEntities {
|
|||||||
scale: factor,
|
scale: factor,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (segment.name === PROFILE_START) {
|
|
||||||
segment.scale.set(factor, factor, factor)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
if (this.axisGroup) {
|
if (this.axisGroup) {
|
||||||
const factor =
|
const factor =
|
||||||
@ -153,9 +145,9 @@ class SceneEntities {
|
|||||||
? orthoFactor
|
? orthoFactor
|
||||||
: perspScale(sceneInfra.camControls.camera, this.axisGroup)
|
: perspScale(sceneInfra.camControls.camera, this.axisGroup)
|
||||||
const x = this.axisGroup.getObjectByName(X_AXIS)
|
const x = this.axisGroup.getObjectByName(X_AXIS)
|
||||||
x?.scale.set(1, factor / sceneInfra._baseUnitMultiplier, 1)
|
x?.scale.set(1, factor, 1)
|
||||||
const y = this.axisGroup.getObjectByName(Y_AXIS)
|
const y = this.axisGroup.getObjectByName(Y_AXIS)
|
||||||
y?.scale.set(factor / sceneInfra._baseUnitMultiplier, 1, 1)
|
y?.scale.set(factor, 1, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +171,6 @@ class SceneEntities {
|
|||||||
this.scene.add(this.intersectionPlane)
|
this.scene.add(this.intersectionPlane)
|
||||||
}
|
}
|
||||||
createSketchAxis(sketchPathToNode: PathToNode) {
|
createSketchAxis(sketchPathToNode: PathToNode) {
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
|
||||||
const baseXColor = 0x000055
|
const baseXColor = 0x000055
|
||||||
const baseYColor = 0x550000
|
const baseYColor = 0x550000
|
||||||
const xAxisGeometry = new BoxGeometry(100000, 0.3, 0.01)
|
const xAxisGeometry = new BoxGeometry(100000, 0.3, 0.01)
|
||||||
@ -219,14 +210,6 @@ class SceneEntities {
|
|||||||
sceneInfra.camControls.target
|
sceneInfra.camControls.target
|
||||||
)
|
)
|
||||||
gridHelper.scale.set(sceneScale, sceneScale, sceneScale)
|
gridHelper.scale.set(sceneScale, sceneScale, sceneScale)
|
||||||
|
|
||||||
const factor =
|
|
||||||
sceneInfra.camControls.camera instanceof OrthographicCamera
|
|
||||||
? orthoFactor
|
|
||||||
: perspScale(sceneInfra.camControls.camera, this.axisGroup)
|
|
||||||
xAxisMesh?.scale.set(1, factor / sceneInfra._baseUnitMultiplier, 1)
|
|
||||||
yAxisMesh?.scale.set(factor / sceneInfra._baseUnitMultiplier, 1, 1)
|
|
||||||
|
|
||||||
this.axisGroup.add(xAxisMesh, yAxisMesh, gridHelper)
|
this.axisGroup.add(xAxisMesh, yAxisMesh, gridHelper)
|
||||||
this.currentSketchQuaternion &&
|
this.currentSketchQuaternion &&
|
||||||
this.axisGroup.setRotationFromQuaternion(this.currentSketchQuaternion)
|
this.axisGroup.setRotationFromQuaternion(this.currentSketchQuaternion)
|
||||||
@ -258,12 +241,16 @@ class SceneEntities {
|
|||||||
ast,
|
ast,
|
||||||
// is draft line assumes the last segment is a draft line, and mods it as the user moves the mouse
|
// is draft line assumes the last segment is a draft line, and mods it as the user moves the mouse
|
||||||
draftSegment,
|
draftSegment,
|
||||||
|
skipListeners,
|
||||||
}: {
|
}: {
|
||||||
sketchPathToNode: PathToNode
|
sketchPathToNode: PathToNode
|
||||||
ast?: Program
|
ast?: Program
|
||||||
draftSegment?: DraftSegment
|
draftSegment?: DraftSegment
|
||||||
|
skipListeners?: boolean
|
||||||
}) {
|
}) {
|
||||||
|
if (!skipListeners) {
|
||||||
sceneInfra.resetMouseListeners()
|
sceneInfra.resetMouseListeners()
|
||||||
|
}
|
||||||
this.createIntersectionPlane()
|
this.createIntersectionPlane()
|
||||||
|
|
||||||
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
||||||
@ -298,55 +285,17 @@ class SceneEntities {
|
|||||||
)
|
)
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
const factor =
|
const factor =
|
||||||
(sceneInfra.camControls.camera instanceof OrthographicCamera
|
sceneInfra.camControls.camera instanceof OrthographicCamera
|
||||||
? orthoFactor
|
? orthoFactor
|
||||||
: perspScale(sceneInfra.camControls.camera, dummy)) /
|
: perspScale(sceneInfra.camControls.camera, dummy)
|
||||||
sceneInfra._baseUnitMultiplier
|
|
||||||
|
|
||||||
const segPathToNode = getNodePathFromSourceRange(
|
|
||||||
kclManager.ast,
|
|
||||||
sketchGroup.start.__geoMeta.sourceRange
|
|
||||||
)
|
|
||||||
const _profileStart = profileStart({
|
|
||||||
from: sketchGroup.start.from,
|
|
||||||
id: sketchGroup.start.__geoMeta.id,
|
|
||||||
pathToNode: segPathToNode,
|
|
||||||
scale: factor,
|
|
||||||
})
|
|
||||||
_profileStart.layers.set(SKETCH_LAYER)
|
|
||||||
_profileStart.traverse((child) => {
|
|
||||||
child.layers.set(SKETCH_LAYER)
|
|
||||||
})
|
|
||||||
group.add(_profileStart)
|
|
||||||
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
|
|
||||||
|
|
||||||
sketchGroup.value.forEach((segment, index) => {
|
sketchGroup.value.forEach((segment, index) => {
|
||||||
let segPathToNode = getNodePathFromSourceRange(
|
let segPathToNode = getNodePathFromSourceRange(
|
||||||
kclManager.ast,
|
draftSegment ? truncatedAst : kclManager.ast,
|
||||||
segment.__geoMeta.sourceRange
|
segment.__geoMeta.sourceRange
|
||||||
)
|
)
|
||||||
if (draftSegment && (sketchGroup.value[index - 1] || sketchGroup.start)) {
|
|
||||||
const previousSegment =
|
|
||||||
sketchGroup.value[index - 1] || sketchGroup.start
|
|
||||||
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
|
||||||
kclManager.ast,
|
|
||||||
previousSegment.__geoMeta.sourceRange
|
|
||||||
)
|
|
||||||
const bodyIndex = previousSegmentPathToNode[1][0]
|
|
||||||
segPathToNode = getNodePathFromSourceRange(
|
|
||||||
truncatedAst,
|
|
||||||
segment.__geoMeta.sourceRange
|
|
||||||
)
|
|
||||||
segPathToNode[1][0] = bodyIndex
|
|
||||||
}
|
|
||||||
const isDraftSegment =
|
const isDraftSegment =
|
||||||
draftSegment && index === sketchGroup.value.length - 1
|
draftSegment && index === sketchGroup.value.length - 1
|
||||||
let seg
|
let seg
|
||||||
const callExpName = getNodeFromPath<CallExpression>(
|
|
||||||
kclManager.ast,
|
|
||||||
segPathToNode,
|
|
||||||
'CallExpression'
|
|
||||||
)?.node?.callee?.name
|
|
||||||
if (segment.type === 'TangentialArcTo') {
|
if (segment.type === 'TangentialArcTo') {
|
||||||
seg = tangentialArcToSegment({
|
seg = tangentialArcToSegment({
|
||||||
prevSegment: sketchGroup.value[index - 1],
|
prevSegment: sketchGroup.value[index - 1],
|
||||||
@ -365,7 +314,6 @@ class SceneEntities {
|
|||||||
pathToNode: segPathToNode,
|
pathToNode: segPathToNode,
|
||||||
isDraftSegment,
|
isDraftSegment,
|
||||||
scale: factor,
|
scale: factor,
|
||||||
callExpName,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
seg.layers.set(SKETCH_LAYER)
|
seg.layers.set(SKETCH_LAYER)
|
||||||
@ -384,22 +332,69 @@ class SceneEntities {
|
|||||||
this.currentSketchQuaternion
|
this.currentSketchQuaternion
|
||||||
)
|
)
|
||||||
|
|
||||||
|
let addingNewSegmentStatus: 'nothing' | 'pending' | 'added' = 'nothing'
|
||||||
|
|
||||||
this.scene.add(group)
|
this.scene.add(group)
|
||||||
if (!draftSegment) {
|
if (!draftSegment && !skipListeners) {
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onDrag: ({ selected, intersectionPoint, mouseEvent, intersects }) => {
|
onDragEnd: async () => {
|
||||||
if (mouseEvent.which !== 1) return
|
if (addingNewSegmentStatus !== 'nothing') {
|
||||||
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
|
this.setupSketch({ sketchPathToNode })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDrag: async (args) => {
|
||||||
|
if (args.event.which !== 1) return
|
||||||
|
const group = getParentGroup(args.object, [EXTRA_SEGMENT_HANDLE])
|
||||||
|
if (group?.name === EXTRA_SEGMENT_HANDLE) {
|
||||||
|
const segGroup = getParentGroup(args.object)
|
||||||
|
const pathToNode: PathToNode = segGroup?.userData?.pathToNode
|
||||||
|
const pathToNodeIndex = pathToNode.findIndex(
|
||||||
|
(x) => x[1] === 'PipeExpression'
|
||||||
|
)
|
||||||
|
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
|
||||||
|
if (addingNewSegmentStatus === 'nothing') {
|
||||||
|
const prevSegment = sketchGroup.value[pipeIndex - 2]
|
||||||
|
const yo = addNewSketchLn({
|
||||||
|
node: kclManager.ast,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
to: [args.intersection2d.x, args.intersection2d.y],
|
||||||
|
from: [prevSegment.from[0], prevSegment.from[1]],
|
||||||
|
fnName:
|
||||||
|
prevSegment.type === 'TangentialArcTo'
|
||||||
|
? 'tangentialArcTo'
|
||||||
|
: 'line',
|
||||||
|
pathToNode: pathToNode,
|
||||||
|
})
|
||||||
|
addingNewSegmentStatus = 'pending'
|
||||||
|
await kclManager.executeAstMock(yo.modifiedAst, {
|
||||||
|
updates: 'code',
|
||||||
|
})
|
||||||
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
|
this.setupSketch({ sketchPathToNode, skipListeners: true })
|
||||||
|
addingNewSegmentStatus = 'added'
|
||||||
|
} else if (addingNewSegmentStatus === 'added') {
|
||||||
|
const pathToNodeForNewSegment = pathToNode.slice(
|
||||||
|
0,
|
||||||
|
pathToNodeIndex
|
||||||
|
)
|
||||||
|
pathToNodeForNewSegment.push([pipeIndex - 2, 'index'])
|
||||||
this.onDragSegment({
|
this.onDragSegment({
|
||||||
object: selected,
|
...args,
|
||||||
intersection2d: intersectionPoint.twoD,
|
sketchPathToNode: pathToNodeForNewSegment,
|
||||||
intersects,
|
})
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.onDragSegment({
|
||||||
|
...args,
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onMove: () => {},
|
onMove: () => {},
|
||||||
onClick: (args) => {
|
onClick: (args) => {
|
||||||
if (args?.mouseEvent.which !== 1) return
|
if (args?.event.which !== 1) return
|
||||||
if (!args || !args.selected) {
|
if (!args || !args.object) {
|
||||||
sceneInfra.modelingSend({
|
sceneInfra.modelingSend({
|
||||||
type: 'Set selection',
|
type: 'Set selection',
|
||||||
data: {
|
data: {
|
||||||
@ -408,32 +403,73 @@ class SceneEntities {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const { selected } = args
|
const { object } = args
|
||||||
const event = getEventForSegmentSelection(selected)
|
const event = getEventForSegmentSelection(object)
|
||||||
if (!event) return
|
if (!event) return
|
||||||
sceneInfra.modelingSend(event)
|
sceneInfra.modelingSend(event)
|
||||||
},
|
},
|
||||||
...mouseEnterLeaveCallbacks(),
|
onMouseEnter: ({ object }) => {
|
||||||
|
// TODO change the color of the segment to yellow?
|
||||||
|
// Give a few pixels grace around each of the segments
|
||||||
|
// for hover.
|
||||||
|
if ([X_AXIS, Y_AXIS].includes(object?.userData?.type)) {
|
||||||
|
const obj = object as Mesh
|
||||||
|
const mat = obj.material as MeshBasicMaterial
|
||||||
|
mat.color.set(obj.userData.baseColor)
|
||||||
|
mat.color.offsetHSL(0, 0, 0.5)
|
||||||
|
}
|
||||||
|
const parent = getParentGroup(object)
|
||||||
|
if (parent?.userData?.pathToNode) {
|
||||||
|
const updatedAst = parse(recast(kclManager.ast))
|
||||||
|
const node = getNodeFromPath<CallExpression>(
|
||||||
|
updatedAst,
|
||||||
|
parent.userData.pathToNode,
|
||||||
|
'CallExpression'
|
||||||
|
).node
|
||||||
|
sceneInfra.highlightCallback([node.start, node.end])
|
||||||
|
const yellow = 0xffff00
|
||||||
|
colorSegment(object, yellow)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sceneInfra.highlightCallback([0, 0])
|
||||||
|
},
|
||||||
|
onMouseLeave: ({ object }) => {
|
||||||
|
sceneInfra.highlightCallback([0, 0])
|
||||||
|
const parent = getParentGroup(object)
|
||||||
|
const isSelected = parent?.userData?.isSelected
|
||||||
|
colorSegment(object, isSelected ? 0x0000ff : 0xffffff)
|
||||||
|
if ([X_AXIS, Y_AXIS].includes(object?.userData?.type)) {
|
||||||
|
const obj = object as Mesh
|
||||||
|
const mat = obj.material as MeshBasicMaterial
|
||||||
|
mat.color.set(obj.userData.baseColor)
|
||||||
|
if (obj.userData.isSelected) mat.color.offsetHSL(0, 0, 0.2)
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else if (draftSegment && !skipListeners) {
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
|
onDrag: () => {},
|
||||||
onClick: async (args) => {
|
onClick: async (args) => {
|
||||||
if (!args) return
|
if (!args) return
|
||||||
if (args.mouseEvent.which !== 1) return
|
if (args.event.which !== 1) return
|
||||||
const { intersectionPoint } = args
|
const { intersection2d } = args
|
||||||
let intersection2d = intersectionPoint?.twoD
|
if (!intersection2d) return
|
||||||
const profileStart = args.intersects
|
|
||||||
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
|
|
||||||
.find((a) => a?.name === PROFILE_START)
|
|
||||||
|
|
||||||
|
const firstSeg = sketchGroup.value[0]
|
||||||
|
const isClosingSketch = compareVec2Epsilon2(
|
||||||
|
firstSeg.from,
|
||||||
|
[intersection2d.x, intersection2d.y],
|
||||||
|
1
|
||||||
|
)
|
||||||
let modifiedAst
|
let modifiedAst
|
||||||
if (profileStart) {
|
if (isClosingSketch) {
|
||||||
|
// TODO close needs a better UX
|
||||||
modifiedAst = addCloseToPipe({
|
modifiedAst = addCloseToPipe({
|
||||||
node: kclManager.ast,
|
node: kclManager.ast,
|
||||||
programMemory: kclManager.programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
pathToNode: sketchPathToNode,
|
pathToNode: sketchPathToNode,
|
||||||
})
|
})
|
||||||
} else if (intersection2d) {
|
} else {
|
||||||
const lastSegment = sketchGroup.value.slice(-1)[0]
|
const lastSegment = sketchGroup.value.slice(-1)[0]
|
||||||
modifiedAst = addNewSketchLn({
|
modifiedAst = addNewSketchLn({
|
||||||
node: kclManager.ast,
|
node: kclManager.ast,
|
||||||
@ -446,9 +482,6 @@ class SceneEntities {
|
|||||||
: 'line',
|
: 'line',
|
||||||
pathToNode: sketchPathToNode,
|
pathToNode: sketchPathToNode,
|
||||||
}).modifiedAst
|
}).modifiedAst
|
||||||
} else {
|
|
||||||
// return early as we didn't modify the ast
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
kclManager.executeAstMock(modifiedAst, { updates: 'code' })
|
||||||
@ -457,9 +490,8 @@ class SceneEntities {
|
|||||||
},
|
},
|
||||||
onMove: (args) => {
|
onMove: (args) => {
|
||||||
this.onDragSegment({
|
this.onDragSegment({
|
||||||
intersection2d: args.intersectionPoint.twoD,
|
...args,
|
||||||
object: Object.values(this.activeSegments).slice(-1)[0],
|
object: Object.values(this.activeSegments).slice(-1)[0],
|
||||||
intersects: args.intersects,
|
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
draftInfo: {
|
draftInfo: {
|
||||||
draftSegment,
|
draftSegment,
|
||||||
@ -469,7 +501,6 @@ class SceneEntities {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
...mouseEnterLeaveCallbacks(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
sceneInfra.camControls.enableRotate = false
|
sceneInfra.camControls.enableRotate = false
|
||||||
@ -506,15 +537,17 @@ class SceneEntities {
|
|||||||
)
|
)
|
||||||
onDragSegment({
|
onDragSegment({
|
||||||
object,
|
object,
|
||||||
intersection2d: _intersection2d,
|
event,
|
||||||
|
intersectPoint,
|
||||||
|
intersection2d,
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
draftInfo,
|
draftInfo,
|
||||||
intersects,
|
|
||||||
}: {
|
}: {
|
||||||
object: any
|
object: any
|
||||||
|
event: any
|
||||||
|
intersectPoint: Vector3
|
||||||
intersection2d: Vector2
|
intersection2d: Vector2
|
||||||
sketchPathToNode: PathToNode
|
sketchPathToNode: PathToNode
|
||||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
|
||||||
draftInfo?: {
|
draftInfo?: {
|
||||||
draftSegment: DraftSegment
|
draftSegment: DraftSegment
|
||||||
truncatedAst: Program
|
truncatedAst: Program
|
||||||
@ -522,20 +555,8 @@ class SceneEntities {
|
|||||||
variableDeclarationName: string
|
variableDeclarationName: string
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
const profileStart =
|
if (object.name === STRAIGHT_SEGMENT_BODY) return
|
||||||
draftInfo &&
|
const group = getParentGroup(object)
|
||||||
intersects
|
|
||||||
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
|
|
||||||
.find((a) => a?.name === PROFILE_START)
|
|
||||||
const intersection2d = profileStart
|
|
||||||
? new Vector2(profileStart.position.x, profileStart.position.y)
|
|
||||||
: _intersection2d
|
|
||||||
|
|
||||||
const group = getParentGroup(object, [
|
|
||||||
STRAIGHT_SEGMENT,
|
|
||||||
TANGENTIAL_ARC_TO_SEGMENT,
|
|
||||||
PROFILE_START,
|
|
||||||
])
|
|
||||||
if (!group) return
|
if (!group) return
|
||||||
const pathToNode: PathToNode = JSON.parse(
|
const pathToNode: PathToNode = JSON.parse(
|
||||||
JSON.stringify(group.userData.pathToNode)
|
JSON.stringify(group.userData.pathToNode)
|
||||||
@ -559,28 +580,13 @@ class SceneEntities {
|
|||||||
).node
|
).node
|
||||||
if (node.type !== 'CallExpression') return
|
if (node.type !== 'CallExpression') return
|
||||||
|
|
||||||
let modded: {
|
const modded = changeSketchArguments(
|
||||||
modifiedAst: Program
|
|
||||||
pathToNode: PathToNode
|
|
||||||
}
|
|
||||||
if (group.name === PROFILE_START) {
|
|
||||||
modded = updateStartProfileAtArgs({
|
|
||||||
node: modifiedAst,
|
|
||||||
pathToNode,
|
|
||||||
to,
|
|
||||||
from,
|
|
||||||
previousProgramMemory: kclManager.programMemory,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
modded = changeSketchArguments(
|
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
kclManager.programMemory,
|
kclManager.programMemory,
|
||||||
[node.start, node.end],
|
[node.start, node.end],
|
||||||
to,
|
to,
|
||||||
from
|
from
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
modifiedAst = modded.modifiedAst
|
modifiedAst = modded.modifiedAst
|
||||||
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
||||||
draftInfo
|
draftInfo
|
||||||
@ -599,16 +605,10 @@ class SceneEntities {
|
|||||||
programMemoryOverride,
|
programMemoryOverride,
|
||||||
})
|
})
|
||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
const sketchGroup = programMemory.root[
|
const sketchGroup = programMemory.root[variableDeclarationName]
|
||||||
variableDeclarationName
|
.value as Path[]
|
||||||
] as SketchGroup
|
|
||||||
const sgPaths = sketchGroup.value
|
|
||||||
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
sketchGroup.forEach((segment, index) => {
|
||||||
const updateSegment = (
|
|
||||||
segment: Path | SketchGroup['start'],
|
|
||||||
index: number
|
|
||||||
) => {
|
|
||||||
const segPathToNode = getNodePathFromSourceRange(
|
const segPathToNode = getNodePathFromSourceRange(
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
segment.__geoMeta.sourceRange
|
segment.__geoMeta.sourceRange
|
||||||
@ -623,13 +623,12 @@ class SceneEntities {
|
|||||||
// const prevSegment = sketchGroup.slice(index - 1)[0]
|
// const prevSegment = sketchGroup.slice(index - 1)[0]
|
||||||
const type = group?.userData?.type
|
const type = group?.userData?.type
|
||||||
const factor =
|
const factor =
|
||||||
(sceneInfra.camControls.camera instanceof OrthographicCamera
|
sceneInfra.camControls.camera instanceof OrthographicCamera
|
||||||
? orthoFactor
|
? orthoFactor
|
||||||
: perspScale(sceneInfra.camControls.camera, group)) /
|
: perspScale(sceneInfra.camControls.camera, group)
|
||||||
sceneInfra._baseUnitMultiplier
|
|
||||||
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
|
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
|
||||||
this.updateTangentialArcToSegment({
|
this.updateTangentialArcToSegment({
|
||||||
prevSegment: sgPaths[index - 1],
|
prevSegment: sketchGroup[index - 1],
|
||||||
from: segment.from,
|
from: segment.from,
|
||||||
to: segment.to,
|
to: segment.to,
|
||||||
group: group,
|
group: group,
|
||||||
@ -642,13 +641,8 @@ class SceneEntities {
|
|||||||
group: group,
|
group: group,
|
||||||
scale: factor,
|
scale: factor,
|
||||||
})
|
})
|
||||||
} else if (type === PROFILE_START) {
|
|
||||||
group.position.set(segment.from[0], segment.from[1], 0)
|
|
||||||
group.scale.set(factor, factor, factor)
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
updateSegment(sketchGroup.start, 0)
|
|
||||||
sgPaths.forEach(updateSegment)
|
|
||||||
})()
|
})()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -745,7 +739,6 @@ class SceneEntities {
|
|||||||
shape.lineTo(0, 0.08 * scale) // The width of the line
|
shape.lineTo(0, 0.08 * scale) // The width of the line
|
||||||
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
||||||
|
|
||||||
if (arrowGroup) {
|
|
||||||
arrowGroup.position.set(to[0], to[1], 0)
|
arrowGroup.position.set(to[0], to[1], 0)
|
||||||
|
|
||||||
const dir = new Vector3()
|
const dir = new Vector3()
|
||||||
@ -756,7 +749,32 @@ class SceneEntities {
|
|||||||
.normalize()
|
.normalize()
|
||||||
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
||||||
arrowGroup.scale.set(scale, scale, scale)
|
arrowGroup.scale.set(scale, scale, scale)
|
||||||
}
|
|
||||||
|
// TODO this should be created in setupSketch, not updateStraightSegment
|
||||||
|
// it should only be updated here
|
||||||
|
const extraSegmentHandle = (group.getObjectByName(EXTRA_SEGMENT_HANDLE) ||
|
||||||
|
(() => {
|
||||||
|
const mat = new MeshBasicMaterial({ color: 0xffffff })
|
||||||
|
const sphereMesh = new Mesh(new SphereGeometry(0.6, 12, 12), mat)
|
||||||
|
|
||||||
|
const handleGroup = new Group()
|
||||||
|
handleGroup.userData.type = EXTRA_SEGMENT_HANDLE
|
||||||
|
handleGroup.name = EXTRA_SEGMENT_HANDLE
|
||||||
|
handleGroup.add(sphereMesh)
|
||||||
|
handleGroup.layers.set(SKETCH_LAYER)
|
||||||
|
handleGroup.traverse((child) => {
|
||||||
|
child.layers.set(SKETCH_LAYER)
|
||||||
|
})
|
||||||
|
return handleGroup
|
||||||
|
})()) as Group
|
||||||
|
|
||||||
|
extraSegmentHandle.position.set(
|
||||||
|
from[0] + 0.08 * (to[0] - from[0]),
|
||||||
|
from[1] + 0.08 * (to[1] - from[1]),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
extraSegmentHandle.scale.set(scale, scale, scale)
|
||||||
|
group.add(extraSegmentHandle)
|
||||||
|
|
||||||
const straightSegmentBody = group.children.find(
|
const straightSegmentBody = group.children.find(
|
||||||
(child) => child.userData.type === STRAIGHT_SEGMENT_BODY
|
(child) => child.userData.type === STRAIGHT_SEGMENT_BODY
|
||||||
@ -841,24 +859,22 @@ class SceneEntities {
|
|||||||
}
|
}
|
||||||
setupDefaultPlaneHover() {
|
setupDefaultPlaneHover() {
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onMouseEnter: ({ selected }) => {
|
onMouseEnter: ({ object }) => {
|
||||||
if (!(selected instanceof Mesh && selected.parent)) return
|
if (object.parent.userData.type !== DEFAULT_PLANES) return
|
||||||
if (selected.parent.userData.type !== DEFAULT_PLANES) return
|
const type: DefaultPlane = object.userData.type
|
||||||
const type: DefaultPlane = selected.userData.type
|
object.material.color = defaultPlaneColor(type, 0.5, 1)
|
||||||
selected.material.color = defaultPlaneColor(type, 0.5, 1)
|
|
||||||
},
|
},
|
||||||
onMouseLeave: ({ selected }) => {
|
onMouseLeave: ({ object }) => {
|
||||||
if (!(selected instanceof Mesh && selected.parent)) return
|
if (object.parent.userData.type !== DEFAULT_PLANES) return
|
||||||
if (selected.parent.userData.type !== DEFAULT_PLANES) return
|
const type: DefaultPlane = object.userData.type
|
||||||
const type: DefaultPlane = selected.userData.type
|
object.material.color = defaultPlaneColor(type)
|
||||||
selected.material.color = defaultPlaneColor(type)
|
|
||||||
},
|
},
|
||||||
onClick: (args) => {
|
onClick: (args) => {
|
||||||
if (!args || !args.intersects?.[0]) return
|
if (!args || !args.object) return
|
||||||
if (args.mouseEvent.which !== 1) return
|
if (args.event.which !== 1) return
|
||||||
const { intersects } = args
|
const { intersection } = args
|
||||||
const type = intersects?.[0].object.name || ''
|
const type = intersection.object.name || ''
|
||||||
const posNorm = Number(intersects?.[0]?.normal?.z) > 0
|
const posNorm = Number(intersection.normal?.z) > 0
|
||||||
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
|
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
|
||||||
let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
|
let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
|
||||||
if (type === YZ_PLANE) {
|
if (type === YZ_PLANE) {
|
||||||
@ -1030,9 +1046,9 @@ export function quaternionFromSketchGroup(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function colorSegment(object: any, color: number) {
|
function colorSegment(object: any, color: number) {
|
||||||
const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START])
|
const arrowHead = getParentGroup(object, [ARROWHEAD])
|
||||||
if (segmentHead) {
|
if (arrowHead) {
|
||||||
segmentHead.traverse((child) => {
|
arrowHead.traverse((child) => {
|
||||||
if (child instanceof Mesh) {
|
if (child instanceof Mesh) {
|
||||||
child.material.color.set(color)
|
child.material.color.set(color)
|
||||||
}
|
}
|
||||||
@ -1088,53 +1104,3 @@ function massageFormats(a: any): Vector3 {
|
|||||||
? new Vector3(a[0], a[1], a[2])
|
? new Vector3(a[0], a[1], a[2])
|
||||||
: new Vector3(a.x, a.y, a.z)
|
: new Vector3(a.x, a.y, a.z)
|
||||||
}
|
}
|
||||||
|
|
||||||
function mouseEnterLeaveCallbacks() {
|
|
||||||
return {
|
|
||||||
onMouseEnter: ({ selected }: OnMouseEnterLeaveArgs) => {
|
|
||||||
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
|
|
||||||
const obj = selected as Mesh
|
|
||||||
const mat = obj.material as MeshBasicMaterial
|
|
||||||
mat.color.set(obj.userData.baseColor)
|
|
||||||
mat.color.offsetHSL(0, 0, 0.5)
|
|
||||||
}
|
|
||||||
const parent = getParentGroup(selected, [
|
|
||||||
STRAIGHT_SEGMENT,
|
|
||||||
TANGENTIAL_ARC_TO_SEGMENT,
|
|
||||||
PROFILE_START,
|
|
||||||
])
|
|
||||||
if (parent?.userData?.pathToNode) {
|
|
||||||
const updatedAst = parse(recast(kclManager.ast))
|
|
||||||
const node = getNodeFromPath<CallExpression>(
|
|
||||||
updatedAst,
|
|
||||||
parent.userData.pathToNode,
|
|
||||||
'CallExpression'
|
|
||||||
).node
|
|
||||||
sceneInfra.highlightCallback([node.start, node.end])
|
|
||||||
const yellow = 0xffff00
|
|
||||||
colorSegment(selected, yellow)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sceneInfra.highlightCallback([0, 0])
|
|
||||||
},
|
|
||||||
onMouseLeave: ({ selected }: OnMouseEnterLeaveArgs) => {
|
|
||||||
sceneInfra.highlightCallback([0, 0])
|
|
||||||
const parent = getParentGroup(selected, [
|
|
||||||
STRAIGHT_SEGMENT,
|
|
||||||
TANGENTIAL_ARC_TO_SEGMENT,
|
|
||||||
PROFILE_START,
|
|
||||||
])
|
|
||||||
const isSelected = parent?.userData?.isSelected
|
|
||||||
colorSegment(
|
|
||||||
selected,
|
|
||||||
isSelected ? 0x0000ff : parent?.userData?.baseColor || 0xffffff
|
|
||||||
)
|
|
||||||
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
|
|
||||||
const obj = selected as Mesh
|
|
||||||
const mat = obj.material as MeshBasicMaterial
|
|
||||||
mat.color.set(obj.userData.baseColor)
|
|
||||||
if (obj.userData.isSelected) mat.color.offsetHSL(0, 0, 0.2)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
Intersection,
|
Intersection,
|
||||||
Object3D,
|
Object3D,
|
||||||
Object3DEventMap,
|
Object3DEventMap,
|
||||||
|
BoxGeometry,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
import { compareVec2Epsilon2 } from 'lang/std/sketch'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
@ -48,36 +49,31 @@ export const AXIS_GROUP = 'axisGroup'
|
|||||||
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
|
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
|
||||||
export const ARROWHEAD = 'arrowhead'
|
export const ARROWHEAD = 'arrowhead'
|
||||||
|
|
||||||
export interface OnMouseEnterLeaveArgs {
|
interface BaseCallbackArgs2 {
|
||||||
selected: Object3D<Object3DEventMap>
|
object: any
|
||||||
mouseEvent: MouseEvent
|
event: any
|
||||||
|
}
|
||||||
|
interface BaseCallbackArgs {
|
||||||
|
event: any
|
||||||
|
}
|
||||||
|
interface OnDragCallbackArgs extends BaseCallbackArgs {
|
||||||
|
object: any
|
||||||
|
intersection2d: Vector2
|
||||||
|
intersectPoint: Vector3
|
||||||
|
intersection: Intersection<Object3D<Object3DEventMap>>
|
||||||
|
}
|
||||||
|
interface OnClickCallbackArgs extends BaseCallbackArgs {
|
||||||
|
intersection2d?: Vector2
|
||||||
|
intersectPoint: Vector3
|
||||||
|
intersection: Intersection<Object3D<Object3DEventMap>>
|
||||||
|
object?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
|
interface onMoveCallbackArgs {
|
||||||
intersectionPoint: {
|
event: any
|
||||||
twoD: Vector2
|
intersection2d: Vector2
|
||||||
threeD: Vector3
|
intersectPoint: Vector3
|
||||||
}
|
intersection: Intersection<Object3D<Object3DEventMap>>
|
||||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
|
||||||
}
|
|
||||||
interface OnClickCallbackArgs {
|
|
||||||
mouseEvent: MouseEvent
|
|
||||||
intersectionPoint?: {
|
|
||||||
twoD: Vector2
|
|
||||||
threeD: Vector3
|
|
||||||
}
|
|
||||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
|
||||||
selected?: Object3D<Object3DEventMap>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OnMoveCallbackArgs {
|
|
||||||
mouseEvent: MouseEvent
|
|
||||||
intersectionPoint: {
|
|
||||||
twoD: Vector2
|
|
||||||
threeD: Vector3
|
|
||||||
}
|
|
||||||
intersects: Intersection<Object3D<Object3DEventMap>>[]
|
|
||||||
selected?: Object3D<Object3DEventMap>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This singleton class is responsible for all of the under the hood setup for the client side scene.
|
// This singleton class is responsible for all of the under the hood setup for the client side scene.
|
||||||
@ -92,20 +88,25 @@ class SceneInfra {
|
|||||||
fov = 45
|
fov = 45
|
||||||
fovBeforeAnimate = 45
|
fovBeforeAnimate = 45
|
||||||
isFovAnimationInProgress = false
|
isFovAnimationInProgress = false
|
||||||
_baseUnit: BaseUnit = 'mm'
|
onDragStartCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||||
_baseUnitMultiplier = 1
|
onDragEndCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||||
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
|
||||||
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
|
onMoveCallback: (arg: onMoveCallbackArgs) => void = () => {}
|
||||||
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
|
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
|
||||||
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
onMouseEnter: (arg: BaseCallbackArgs2) => void = () => {}
|
||||||
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
|
onMouseLeave: (arg: BaseCallbackArgs2) => void = () => {}
|
||||||
setCallbacks = (callbacks: {
|
setCallbacks = (callbacks: {
|
||||||
|
onDragStart?: (arg: OnDragCallbackArgs) => void
|
||||||
|
onDragEnd?: (arg: OnDragCallbackArgs) => void
|
||||||
onDrag?: (arg: OnDragCallbackArgs) => void
|
onDrag?: (arg: OnDragCallbackArgs) => void
|
||||||
onMove?: (arg: OnMoveCallbackArgs) => void
|
onMove?: (arg: onMoveCallbackArgs) => void
|
||||||
onClick?: (arg?: OnClickCallbackArgs) => void
|
onClick?: (arg?: OnClickCallbackArgs) => void
|
||||||
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
|
onMouseEnter?: (arg: BaseCallbackArgs2) => void
|
||||||
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
|
onMouseLeave?: (arg: BaseCallbackArgs2) => void
|
||||||
}) => {
|
}) => {
|
||||||
|
console.trace('setting callbacks')
|
||||||
|
this.onDragStartCallback = callbacks.onDragStart || this.onDragStartCallback
|
||||||
|
this.onDragEndCallback = callbacks.onDragEnd || this.onDragEndCallback
|
||||||
this.onDragCallback = callbacks.onDrag || this.onDragCallback
|
this.onDragCallback = callbacks.onDrag || this.onDragCallback
|
||||||
this.onMoveCallback = callbacks.onMove || this.onMoveCallback
|
this.onMoveCallback = callbacks.onMove || this.onMoveCallback
|
||||||
this.onClickCallback = callbacks.onClick || this.onClickCallback
|
this.onClickCallback = callbacks.onClick || this.onClickCallback
|
||||||
@ -113,17 +114,10 @@ class SceneInfra {
|
|||||||
this.onMouseLeave = callbacks.onMouseLeave || this.onMouseLeave
|
this.onMouseLeave = callbacks.onMouseLeave || this.onMouseLeave
|
||||||
this.selected = null // following selections between callbacks being set is too tricky
|
this.selected = null // following selections between callbacks being set is too tricky
|
||||||
}
|
}
|
||||||
set baseUnit(unit: BaseUnit) {
|
|
||||||
this._baseUnit = unit
|
|
||||||
this._baseUnitMultiplier = baseUnitTomm(unit)
|
|
||||||
this.scene.scale.set(
|
|
||||||
this._baseUnitMultiplier,
|
|
||||||
this._baseUnitMultiplier,
|
|
||||||
this._baseUnitMultiplier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
resetMouseListeners = () => {
|
resetMouseListeners = () => {
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
|
onDragStart: () => {},
|
||||||
|
onDragEnd: () => {},
|
||||||
onDrag: () => {},
|
onDrag: () => {},
|
||||||
onMove: () => {},
|
onMove: () => {},
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
@ -147,9 +141,10 @@ class SceneInfra {
|
|||||||
currentMouseVector = new Vector2()
|
currentMouseVector = new Vector2()
|
||||||
selected: {
|
selected: {
|
||||||
mouseDownVector: Vector2
|
mouseDownVector: Vector2
|
||||||
object: Object3D<Object3DEventMap>
|
object: any
|
||||||
hasBeenDragged: boolean
|
hasBeenDragged: boolean
|
||||||
} | null = null
|
} | null = null
|
||||||
|
selectedObject: null | any = null
|
||||||
mouseDownVector: null | Vector2 = null
|
mouseDownVector: null | Vector2 = null
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -216,12 +211,7 @@ class SceneInfra {
|
|||||||
const axisGroup = this.scene
|
const axisGroup = this.scene
|
||||||
.getObjectByName(AXIS_GROUP)
|
.getObjectByName(AXIS_GROUP)
|
||||||
?.getObjectByName('gridHelper')
|
?.getObjectByName('gridHelper')
|
||||||
planesGroup &&
|
planesGroup && planesGroup.scale.set(scale, scale, scale)
|
||||||
planesGroup.scale.set(
|
|
||||||
scale / sceneInfra._baseUnitMultiplier,
|
|
||||||
scale / sceneInfra._baseUnitMultiplier,
|
|
||||||
scale / sceneInfra._baseUnitMultiplier
|
|
||||||
)
|
|
||||||
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
|
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,8 +236,8 @@ class SceneInfra {
|
|||||||
// Dispose of any other resources like geometries, materials, textures
|
// Dispose of any other resources like geometries, materials, textures
|
||||||
}
|
}
|
||||||
getPlaneIntersectPoint = (): {
|
getPlaneIntersectPoint = (): {
|
||||||
twoD?: Vector2
|
intersection2d?: Vector2
|
||||||
threeD?: Vector3
|
intersectPoint: Vector3
|
||||||
intersection: Intersection<Object3D<Object3DEventMap>>
|
intersection: Intersection<Object3D<Object3DEventMap>>
|
||||||
} | null => {
|
} | null => {
|
||||||
this.planeRaycaster.setFromCamera(
|
this.planeRaycaster.setFromCamera(
|
||||||
@ -258,11 +248,23 @@ class SceneInfra {
|
|||||||
this.scene.children,
|
this.scene.children,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
const recastablePlaneIntersect = planeIntersects.find(
|
if (
|
||||||
(intersect) => intersect.object.name === RAYCASTABLE_PLANE
|
planeIntersects.length > 0 &&
|
||||||
|
planeIntersects[0].object.userData.type !== RAYCASTABLE_PLANE
|
||||||
|
) {
|
||||||
|
const intersect = planeIntersects[0]
|
||||||
|
return {
|
||||||
|
intersectPoint: intersect.point,
|
||||||
|
intersection: intersect,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
planeIntersects.length > 0 &&
|
||||||
|
planeIntersects[0].object.userData.type === RAYCASTABLE_PLANE
|
||||||
)
|
)
|
||||||
if (!planeIntersects.length) return null
|
)
|
||||||
if (!recastablePlaneIntersect) return { intersection: planeIntersects[0] }
|
return null
|
||||||
const planePosition = planeIntersects[0].object.position
|
const planePosition = planeIntersects[0].object.position
|
||||||
const inversePlaneQuaternion = planeIntersects[0].object.quaternion
|
const inversePlaneQuaternion = planeIntersects[0].object.quaternion
|
||||||
.clone()
|
.clone()
|
||||||
@ -277,21 +279,16 @@ class SceneInfra {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
twoD: new Vector2(
|
intersection2d: new Vector2(transformedPoint.x, transformedPoint.y), // z should be 0
|
||||||
transformedPoint.x / this._baseUnitMultiplier,
|
intersectPoint,
|
||||||
transformedPoint.y / this._baseUnitMultiplier
|
|
||||||
), // z should be 0
|
|
||||||
threeD: intersectPoint.divideScalar(this._baseUnitMultiplier),
|
|
||||||
intersection: planeIntersects[0],
|
intersection: planeIntersects[0],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onMouseMove = (mouseEvent: MouseEvent) => {
|
onMouseMove = (event: MouseEvent) => {
|
||||||
this.currentMouseVector.x = (mouseEvent.clientX / window.innerWidth) * 2 - 1
|
this.currentMouseVector.x = (event.clientX / window.innerWidth) * 2 - 1
|
||||||
this.currentMouseVector.y =
|
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1
|
||||||
-(mouseEvent.clientY / window.innerHeight) * 2 + 1
|
|
||||||
|
|
||||||
const planeIntersectPoint = this.getPlaneIntersectPoint()
|
const planeIntersectPoint = this.getPlaneIntersectPoint()
|
||||||
const intersects = this.raycastRing()
|
|
||||||
|
|
||||||
if (this.selected) {
|
if (this.selected) {
|
||||||
const hasBeenDragged = !compareVec2Epsilon2(
|
const hasBeenDragged = !compareVec2Epsilon2(
|
||||||
@ -307,56 +304,47 @@ class SceneInfra {
|
|||||||
if (
|
if (
|
||||||
hasBeenDragged &&
|
hasBeenDragged &&
|
||||||
planeIntersectPoint &&
|
planeIntersectPoint &&
|
||||||
planeIntersectPoint.twoD &&
|
planeIntersectPoint.intersection2d
|
||||||
planeIntersectPoint.threeD
|
|
||||||
) {
|
) {
|
||||||
// // console.log('onDrag', this.selected)
|
// // console.log('onDrag', this.selected)
|
||||||
|
|
||||||
this.onDragCallback({
|
this.onDragCallback({
|
||||||
mouseEvent,
|
object: this.selected.object,
|
||||||
intersectionPoint: {
|
event,
|
||||||
twoD: planeIntersectPoint.twoD,
|
intersection2d: planeIntersectPoint.intersection2d,
|
||||||
threeD: planeIntersectPoint.threeD,
|
...planeIntersectPoint,
|
||||||
},
|
|
||||||
intersects,
|
|
||||||
selected: this.selected.object,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (planeIntersectPoint && planeIntersectPoint.intersection2d) {
|
||||||
planeIntersectPoint &&
|
|
||||||
planeIntersectPoint.twoD &&
|
|
||||||
planeIntersectPoint.threeD
|
|
||||||
) {
|
|
||||||
this.onMoveCallback({
|
this.onMoveCallback({
|
||||||
mouseEvent,
|
event,
|
||||||
intersectionPoint: {
|
intersection2d: planeIntersectPoint.intersection2d,
|
||||||
twoD: planeIntersectPoint.twoD,
|
...planeIntersectPoint,
|
||||||
threeD: planeIntersectPoint.threeD,
|
|
||||||
},
|
|
||||||
intersects,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intersects[0]) {
|
const intersect = this.raycastRing()
|
||||||
const firstIntersectObject = intersects[0].object
|
|
||||||
|
if (intersect) {
|
||||||
|
const firstIntersectObject = intersect.object
|
||||||
if (this.hoveredObject !== firstIntersectObject) {
|
if (this.hoveredObject !== firstIntersectObject) {
|
||||||
if (this.hoveredObject) {
|
if (this.hoveredObject) {
|
||||||
this.onMouseLeave({
|
this.onMouseLeave({
|
||||||
selected: this.hoveredObject,
|
object: this.hoveredObject,
|
||||||
mouseEvent: mouseEvent,
|
event,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
this.hoveredObject = firstIntersectObject
|
this.hoveredObject = firstIntersectObject
|
||||||
this.onMouseEnter({
|
this.onMouseEnter({
|
||||||
selected: this.hoveredObject,
|
object: this.hoveredObject,
|
||||||
mouseEvent: mouseEvent,
|
event,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.hoveredObject) {
|
if (this.hoveredObject) {
|
||||||
this.onMouseLeave({
|
this.onMouseLeave({
|
||||||
selected: this.hoveredObject,
|
object: this.hoveredObject,
|
||||||
mouseEvent: mouseEvent,
|
event,
|
||||||
})
|
})
|
||||||
this.hoveredObject = null
|
this.hoveredObject = null
|
||||||
}
|
}
|
||||||
@ -366,38 +354,41 @@ class SceneInfra {
|
|||||||
raycastRing = (
|
raycastRing = (
|
||||||
pixelRadius = 8,
|
pixelRadius = 8,
|
||||||
rayRingCount = 32
|
rayRingCount = 32
|
||||||
): Intersection<Object3D<Object3DEventMap>>[] => {
|
): Intersection<Object3D<Object3DEventMap>> | undefined => {
|
||||||
const mouseDownVector = this.currentMouseVector.clone()
|
const mouseDownVector = this.currentMouseVector.clone()
|
||||||
const intersectionsMap = new Map<
|
let closestIntersection:
|
||||||
Object3D,
|
| Intersection<Object3D<Object3DEventMap>>
|
||||||
Intersection<Object3D<Object3DEventMap>>
|
| undefined = undefined
|
||||||
>()
|
let closestDistance = Infinity
|
||||||
|
|
||||||
const updateIntersectionsMap = (
|
const updateClosestIntersection = (
|
||||||
intersections: Intersection<Object3D<Object3DEventMap>>[]
|
intersections: Intersection<Object3D<Object3DEventMap>>[]
|
||||||
) => {
|
) => {
|
||||||
intersections.forEach((intersection) => {
|
let intersection = null
|
||||||
if (intersection.object.type !== 'GridHelper') {
|
for (let i = 0; i < intersections.length; i++) {
|
||||||
const existingIntersection = intersectionsMap.get(intersection.object)
|
if (intersections[i].object.type !== 'GridHelper') {
|
||||||
if (
|
intersection = intersections[i]
|
||||||
!existingIntersection ||
|
break
|
||||||
existingIntersection.distance > intersection.distance
|
|
||||||
) {
|
|
||||||
intersectionsMap.set(intersection.object, intersection)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
if (!intersection) return
|
||||||
|
|
||||||
|
if (intersection.distance < closestDistance) {
|
||||||
|
closestDistance = intersection.distance
|
||||||
|
closestIntersection = intersection
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check the center point
|
// Check the center point
|
||||||
this.raycaster.setFromCamera(mouseDownVector, this.camControls.camera)
|
this.raycaster.setFromCamera(mouseDownVector, this.camControls.camera)
|
||||||
updateIntersectionsMap(
|
updateClosestIntersection(
|
||||||
this.raycaster.intersectObjects(this.scene.children, true)
|
this.raycaster.intersectObjects(this.scene.children, true)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check the ring points
|
// Check the ring points
|
||||||
for (let i = 0; i < rayRingCount; i++) {
|
for (let i = 0; i < rayRingCount; i++) {
|
||||||
const angle = (i / rayRingCount) * Math.PI * 2
|
const angle = (i / rayRingCount) * Math.PI * 2
|
||||||
|
|
||||||
const offsetX = ((pixelRadius * Math.cos(angle)) / window.innerWidth) * 2
|
const offsetX = ((pixelRadius * Math.cos(angle)) / window.innerWidth) * 2
|
||||||
const offsetY = ((pixelRadius * Math.sin(angle)) / window.innerHeight) * 2
|
const offsetY = ((pixelRadius * Math.sin(angle)) / window.innerHeight) * 2
|
||||||
const ringVector = new Vector2(
|
const ringVector = new Vector2(
|
||||||
@ -405,15 +396,11 @@ class SceneInfra {
|
|||||||
mouseDownVector.y - offsetY
|
mouseDownVector.y - offsetY
|
||||||
)
|
)
|
||||||
this.raycaster.setFromCamera(ringVector, this.camControls.camera)
|
this.raycaster.setFromCamera(ringVector, this.camControls.camera)
|
||||||
updateIntersectionsMap(
|
updateClosestIntersection(
|
||||||
this.raycaster.intersectObjects(this.scene.children, true)
|
this.raycaster.intersectObjects(this.scene.children, true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
return closestIntersection
|
||||||
// Convert the map values to an array and sort by distance
|
|
||||||
return Array.from(intersectionsMap.values()).sort(
|
|
||||||
(a, b) => a.distance - b.distance
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseDown = (event: MouseEvent) => {
|
onMouseDown = (event: MouseEvent) => {
|
||||||
@ -421,60 +408,50 @@ class SceneInfra {
|
|||||||
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1
|
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1
|
||||||
|
|
||||||
const mouseDownVector = this.currentMouseVector.clone()
|
const mouseDownVector = this.currentMouseVector.clone()
|
||||||
const intersect = this.raycastRing()[0]
|
const intersect = this.raycastRing()
|
||||||
|
|
||||||
if (intersect) {
|
if (intersect) {
|
||||||
const intersectParent = intersect?.object?.parent as Group
|
const intersectParent = intersect?.object?.parent as Group
|
||||||
this.selected = intersectParent.isGroup
|
this.selected = intersectParent.isGroup
|
||||||
? {
|
? {
|
||||||
mouseDownVector,
|
mouseDownVector,
|
||||||
object: intersect.object,
|
object: intersect?.object,
|
||||||
hasBeenDragged: false,
|
hasBeenDragged: false,
|
||||||
}
|
}
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMouseUp = (mouseEvent: MouseEvent) => {
|
onMouseUp = (event: MouseEvent) => {
|
||||||
this.currentMouseVector.x = (mouseEvent.clientX / window.innerWidth) * 2 - 1
|
this.currentMouseVector.x = (event.clientX / window.innerWidth) * 2 - 1
|
||||||
this.currentMouseVector.y =
|
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1
|
||||||
-(mouseEvent.clientY / window.innerHeight) * 2 + 1
|
|
||||||
const planeIntersectPoint = this.getPlaneIntersectPoint()
|
const planeIntersectPoint = this.getPlaneIntersectPoint()
|
||||||
const intersects = this.raycastRing()
|
|
||||||
|
|
||||||
if (this.selected) {
|
if (this.selected) {
|
||||||
if (this.selected.hasBeenDragged) {
|
if (this.selected.hasBeenDragged) {
|
||||||
// this is where we could fire a onDragEnd event
|
// TODO do the types properly here
|
||||||
// console.log('onDragEnd', this.selected)
|
this.onDragEndCallback({
|
||||||
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
|
object: this.selected.object,
|
||||||
|
event,
|
||||||
|
intersection2d: planeIntersectPoint?.intersection2d,
|
||||||
|
...planeIntersectPoint,
|
||||||
|
} as any)
|
||||||
|
} else if (planeIntersectPoint) {
|
||||||
// fire onClick event as there was no drags
|
// fire onClick event as there was no drags
|
||||||
this.onClickCallback({
|
this.onClickCallback({
|
||||||
mouseEvent,
|
object: this.selected?.object,
|
||||||
intersectionPoint: {
|
event,
|
||||||
twoD: planeIntersectPoint.twoD,
|
...planeIntersectPoint,
|
||||||
threeD: planeIntersectPoint.threeD,
|
|
||||||
},
|
|
||||||
intersects,
|
|
||||||
selected: this.selected.object,
|
|
||||||
})
|
|
||||||
} else if (planeIntersectPoint) {
|
|
||||||
this.onClickCallback({
|
|
||||||
mouseEvent,
|
|
||||||
intersects,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.onClickCallback()
|
this.onClickCallback()
|
||||||
}
|
}
|
||||||
// Clear the selected state whether it was dragged or not
|
// Clear the selected state whether it was dragged or not
|
||||||
this.selected = null
|
this.selected = null
|
||||||
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
|
} else if (planeIntersectPoint) {
|
||||||
this.onClickCallback({
|
this.onClickCallback({
|
||||||
mouseEvent,
|
event,
|
||||||
intersectionPoint: {
|
...planeIntersectPoint,
|
||||||
twoD: planeIntersectPoint.twoD,
|
|
||||||
threeD: planeIntersectPoint.threeD,
|
|
||||||
},
|
|
||||||
intersects,
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
this.onClickCallback()
|
this.onClickCallback()
|
||||||
@ -520,11 +497,7 @@ class SceneInfra {
|
|||||||
this.camControls.camera,
|
this.camControls.camera,
|
||||||
this.camControls.target
|
this.camControls.target
|
||||||
)
|
)
|
||||||
planesGroup.scale.set(
|
planesGroup.scale.set(sceneScale, sceneScale, sceneScale)
|
||||||
sceneScale / sceneInfra._baseUnitMultiplier,
|
|
||||||
sceneScale / sceneInfra._baseUnitMultiplier,
|
|
||||||
sceneScale / sceneInfra._baseUnitMultiplier
|
|
||||||
)
|
|
||||||
this.scene.add(planesGroup)
|
this.scene.add(planesGroup)
|
||||||
}
|
}
|
||||||
removeDefaultPlanes() {
|
removeDefaultPlanes() {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Coords2d } from 'lang/std/sketch'
|
import { Coords2d } from 'lang/std/sketch'
|
||||||
import {
|
import {
|
||||||
BoxGeometry,
|
|
||||||
BufferGeometry,
|
BufferGeometry,
|
||||||
CatmullRomCurve3,
|
CatmullRomCurve3,
|
||||||
ConeGeometry,
|
ConeGeometry,
|
||||||
@ -20,7 +19,6 @@ import {
|
|||||||
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
|
||||||
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
|
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
PROFILE_START,
|
|
||||||
STRAIGHT_SEGMENT,
|
STRAIGHT_SEGMENT,
|
||||||
STRAIGHT_SEGMENT_BODY,
|
STRAIGHT_SEGMENT_BODY,
|
||||||
STRAIGHT_SEGMENT_DASH,
|
STRAIGHT_SEGMENT_DASH,
|
||||||
@ -31,38 +29,6 @@ import {
|
|||||||
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
||||||
import { ARROWHEAD } from './sceneInfra'
|
import { ARROWHEAD } from './sceneInfra'
|
||||||
|
|
||||||
export function profileStart({
|
|
||||||
from,
|
|
||||||
id,
|
|
||||||
pathToNode,
|
|
||||||
scale = 1,
|
|
||||||
}: {
|
|
||||||
from: Coords2d
|
|
||||||
id: string
|
|
||||||
pathToNode: PathToNode
|
|
||||||
scale?: number
|
|
||||||
}) {
|
|
||||||
const group = new Group()
|
|
||||||
|
|
||||||
const geometry = new BoxGeometry(0.8, 0.8, 0.8)
|
|
||||||
const body = new MeshBasicMaterial({ color: 0xffffff })
|
|
||||||
const mesh = new Mesh(geometry, body)
|
|
||||||
|
|
||||||
group.add(mesh)
|
|
||||||
|
|
||||||
group.userData = {
|
|
||||||
type: PROFILE_START,
|
|
||||||
id,
|
|
||||||
from,
|
|
||||||
pathToNode,
|
|
||||||
isSelected: false,
|
|
||||||
}
|
|
||||||
group.name = PROFILE_START
|
|
||||||
group.position.set(from[0], from[1], 0)
|
|
||||||
group.scale.set(scale, scale, scale)
|
|
||||||
return group
|
|
||||||
}
|
|
||||||
|
|
||||||
export function straightSegment({
|
export function straightSegment({
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
@ -70,7 +36,6 @@ export function straightSegment({
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
isDraftSegment,
|
isDraftSegment,
|
||||||
scale = 1,
|
scale = 1,
|
||||||
callExpName,
|
|
||||||
}: {
|
}: {
|
||||||
from: Coords2d
|
from: Coords2d
|
||||||
to: Coords2d
|
to: Coords2d
|
||||||
@ -78,7 +43,6 @@ export function straightSegment({
|
|||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
isDraftSegment?: boolean
|
isDraftSegment?: boolean
|
||||||
scale?: number
|
scale?: number
|
||||||
callExpName: string
|
|
||||||
}): Group {
|
}): Group {
|
||||||
const group = new Group()
|
const group = new Group()
|
||||||
|
|
||||||
@ -102,8 +66,7 @@ export function straightSegment({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseColor = callExpName === 'close' ? 0x444444 : 0xffffff
|
const body = new MeshBasicMaterial({ color: 0xffffff })
|
||||||
const body = new MeshBasicMaterial({ color: baseColor })
|
|
||||||
const mesh = new Mesh(geometry, body)
|
const mesh = new Mesh(geometry, body)
|
||||||
mesh.userData.type = isDraftSegment
|
mesh.userData.type = isDraftSegment
|
||||||
? STRAIGHT_SEGMENT_DASH
|
? STRAIGHT_SEGMENT_DASH
|
||||||
@ -117,8 +80,6 @@ export function straightSegment({
|
|||||||
to,
|
to,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
callExpName,
|
|
||||||
baseColor,
|
|
||||||
}
|
}
|
||||||
group.name = STRAIGHT_SEGMENT
|
group.name = STRAIGHT_SEGMENT
|
||||||
|
|
||||||
@ -129,8 +90,7 @@ export function straightSegment({
|
|||||||
.normalize()
|
.normalize()
|
||||||
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
arrowGroup.quaternion.setFromUnitVectors(new Vector3(0, 1, 0), dir)
|
||||||
|
|
||||||
group.add(mesh)
|
group.add(mesh, arrowGroup)
|
||||||
if (callExpName !== 'close') group.add(arrowGroup)
|
|
||||||
|
|
||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Combobox } from '@headlessui/react'
|
import { Combobox } from '@headlessui/react'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
|
import { CommandArgumentOption } from 'lib/commandTypes'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
function CommandArgOptionInput({
|
function CommandArgOptionInput({
|
||||||
options,
|
options,
|
||||||
@ -11,89 +11,51 @@ function CommandArgOptionInput({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
placeholder,
|
placeholder,
|
||||||
}: {
|
}: {
|
||||||
options: (CommandArgument<unknown> & { inputType: 'options' })['options']
|
options: CommandArgumentOption<unknown>[]
|
||||||
argName: string
|
argName: string
|
||||||
stepBack: () => void
|
stepBack: () => void
|
||||||
onSubmit: (data: unknown) => void
|
onSubmit: (data: unknown) => void
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
}) {
|
}) {
|
||||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
const { commandBarSend, commandBarState } = useCommandsContext()
|
||||||
const resolvedOptions = useMemo(
|
|
||||||
() =>
|
|
||||||
typeof options === 'function'
|
|
||||||
? options(commandBarState.context)
|
|
||||||
: options,
|
|
||||||
[argName, options, commandBarState.context]
|
|
||||||
)
|
|
||||||
// The initial current option is either an already-input value or the configured default
|
|
||||||
const currentOption = useMemo(
|
|
||||||
() =>
|
|
||||||
resolvedOptions.find(
|
|
||||||
(o) => o.value === commandBarState.context.argumentsToSubmit[argName]
|
|
||||||
) || resolvedOptions.find((o) => o.isCurrent),
|
|
||||||
[commandBarState.context.argumentsToSubmit, argName, resolvedOptions]
|
|
||||||
)
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
const formRef = useRef<HTMLFormElement>(null)
|
const formRef = useRef<HTMLFormElement>(null)
|
||||||
const [selectedOption, setSelectedOption] = useState<
|
const [argValue, setArgValue] = useState<(typeof options)[number]['value']>(
|
||||||
CommandArgumentOption<unknown>
|
options.find((o) => 'isCurrent' in o && o.isCurrent)?.value ||
|
||||||
>(currentOption || resolvedOptions[0])
|
commandBarState.context.argumentsToSubmit[argName] ||
|
||||||
const initialQuery = useMemo(() => '', [options, argName])
|
options[0].value
|
||||||
const [query, setQuery] = useState(initialQuery)
|
)
|
||||||
const [filteredOptions, setFilteredOptions] =
|
const [query, setQuery] = useState('')
|
||||||
useState<typeof resolvedOptions>()
|
const [filteredOptions, setFilteredOptions] = useState<typeof options>()
|
||||||
|
|
||||||
// Create a new Fuse instance when the options change
|
const fuse = new Fuse(options, {
|
||||||
const fuse = useMemo(
|
|
||||||
() =>
|
|
||||||
new Fuse(resolvedOptions, {
|
|
||||||
keys: ['name', 'description'],
|
keys: ['name', 'description'],
|
||||||
threshold: 0.3,
|
threshold: 0.3,
|
||||||
}),
|
})
|
||||||
[argName, resolvedOptions]
|
|
||||||
)
|
|
||||||
|
|
||||||
// Reset the query and selected option when the argName changes
|
|
||||||
useEffect(() => {
|
|
||||||
setQuery(initialQuery)
|
|
||||||
setSelectedOption(currentOption || resolvedOptions[0])
|
|
||||||
}, [argName])
|
|
||||||
|
|
||||||
// Auto focus and select the input when the component mounts
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
inputRef.current?.focus()
|
inputRef.current?.focus()
|
||||||
inputRef.current?.select()
|
inputRef.current?.select()
|
||||||
}, [inputRef])
|
}, [inputRef])
|
||||||
|
|
||||||
// Filter the options based on the query,
|
|
||||||
// resetting the query when the options change
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const results = fuse.search(query).map((result) => result.item)
|
const results = fuse.search(query).map((result) => result.item)
|
||||||
setFilteredOptions(query.length > 0 ? results : resolvedOptions)
|
setFilteredOptions(query.length > 0 ? results : options)
|
||||||
}, [query, resolvedOptions, fuse])
|
}, [query])
|
||||||
|
|
||||||
function handleSelectOption(option: CommandArgumentOption<unknown>) {
|
function handleSelectOption(option: CommandArgumentOption<unknown>) {
|
||||||
// We deal with the whole option object internally
|
setArgValue(option)
|
||||||
setSelectedOption(option)
|
|
||||||
|
|
||||||
// But we only submit the value
|
|
||||||
onSubmit(option.value)
|
onSubmit(option.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
onSubmit(argValue)
|
||||||
// We submit the value of the selected option, not the whole object
|
|
||||||
onSubmit(selectedOption.value)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form id="arg-form" onSubmit={handleSubmit} ref={formRef}>
|
<form id="arg-form" onSubmit={handleSubmit} ref={formRef}>
|
||||||
<Combobox
|
<Combobox value={argValue} onChange={handleSelectOption} name="options">
|
||||||
value={selectedOption}
|
|
||||||
onChange={handleSelectOption}
|
|
||||||
name="options"
|
|
||||||
>
|
|
||||||
<div className="flex items-center mx-4 mt-4 mb-2">
|
<div className="flex items-center mx-4 mt-4 mb-2">
|
||||||
<label
|
<label
|
||||||
htmlFor="option-input"
|
htmlFor="option-input"
|
||||||
@ -113,12 +75,10 @@ function CommandArgOptionInput({
|
|||||||
stepBack()
|
stepBack()
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
value={query}
|
|
||||||
placeholder={
|
placeholder={
|
||||||
currentOption?.name ||
|
(argValue as CommandArgumentOption<unknown>)?.name ||
|
||||||
placeholder ||
|
placeholder ||
|
||||||
argName ||
|
'Select an option for ' + argName
|
||||||
'Select an option'
|
|
||||||
}
|
}
|
||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
@ -138,7 +98,7 @@ function CommandArgOptionInput({
|
|||||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-energy-10/50 dark:ui-active:bg-chalkboard-90"
|
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-energy-10/50 dark:ui-active:bg-chalkboard-90"
|
||||||
>
|
>
|
||||||
<p className="flex-grow">{option.name} </p>
|
<p className="flex-grow">{option.name} </p>
|
||||||
{option.value === currentOption?.value && (
|
{'isCurrent' in option && option.isCurrent && (
|
||||||
<small className="text-chalkboard-70 dark:text-chalkboard-50">
|
<small className="text-chalkboard-70 dark:text-chalkboard-50">
|
||||||
current
|
current
|
||||||
</small>
|
</small>
|
||||||
|
@ -29,6 +29,12 @@ export const CommandBarProvider = ({
|
|||||||
const [commandBarState, commandBarSend] = useMachine(commandBarMachine, {
|
const [commandBarState, commandBarSend] = useMachine(commandBarMachine, {
|
||||||
devTools: true,
|
devTools: true,
|
||||||
guards: {
|
guards: {
|
||||||
|
'Arguments are ready': (context, _) => {
|
||||||
|
return context.selectedCommand?.args
|
||||||
|
? context.argumentsToSubmit.length ===
|
||||||
|
Object.keys(context.selectedCommand.args)?.length
|
||||||
|
: false
|
||||||
|
},
|
||||||
'Command has no arguments': (context, _event) => {
|
'Command has no arguments': (context, _event) => {
|
||||||
return (
|
return (
|
||||||
!context.selectedCommand?.args ||
|
!context.selectedCommand?.args ||
|
||||||
@ -75,12 +81,7 @@ export const CommandBar = () => {
|
|||||||
function stepBack() {
|
function stepBack() {
|
||||||
if (!currentArgument) {
|
if (!currentArgument) {
|
||||||
if (commandBarState.matches('Review')) {
|
if (commandBarState.matches('Review')) {
|
||||||
const entries = Object.entries(selectedCommand?.args || {}).filter(
|
const entries = Object.entries(selectedCommand?.args || {})
|
||||||
([_, argConfig]) =>
|
|
||||||
typeof argConfig.required === 'function'
|
|
||||||
? argConfig.required(commandBarState.context)
|
|
||||||
: argConfig.required
|
|
||||||
)
|
|
||||||
|
|
||||||
const currentArgName = entries[entries.length - 1][0]
|
const currentArgName = entries[entries.length - 1][0]
|
||||||
const currentArg = {
|
const currentArg = {
|
||||||
@ -88,12 +89,19 @@ export const CommandBar = () => {
|
|||||||
...entries[entries.length - 1][1],
|
...entries[entries.length - 1][1],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (commandBarState.matches('Review')) {
|
||||||
commandBarSend({
|
commandBarSend({
|
||||||
type: 'Edit argument',
|
type: 'Edit argument',
|
||||||
data: {
|
data: {
|
||||||
arg: currentArg,
|
arg: currentArg,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
commandBarSend({
|
||||||
|
type: 'Remove argument',
|
||||||
|
data: { [currentArgName]: currentArg },
|
||||||
|
})
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
commandBarSend({ type: 'Deselect command' })
|
commandBarSend({ type: 'Deselect command' })
|
||||||
}
|
}
|
||||||
@ -116,6 +124,11 @@ export const CommandBar = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => console.log(commandBarState.context.argumentsToSubmit),
|
||||||
|
[commandBarState.context.argumentsToSubmit]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root
|
<Transition.Root
|
||||||
show={!commandBarState.matches('Closed') || false}
|
show={!commandBarState.matches('Closed') || false}
|
||||||
@ -146,7 +159,6 @@ export const CommandBar = () => {
|
|||||||
<WrapperComponent.Panel
|
<WrapperComponent.Panel
|
||||||
className="relative z-50 pointer-events-auto w-full max-w-xl py-2 mx-auto border rounded shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100 dark:border-chalkboard-70"
|
className="relative z-50 pointer-events-auto w-full max-w-xl py-2 mx-auto border rounded shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100 dark:border-chalkboard-70"
|
||||||
as="div"
|
as="div"
|
||||||
data-testid="command-bar"
|
|
||||||
>
|
>
|
||||||
{commandBarState.matches('Selecting command') ? (
|
{commandBarState.matches('Selecting command') ? (
|
||||||
<CommandComboBox options={commands} />
|
<CommandComboBox options={commands} />
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { CustomIcon } from '../CustomIcon'
|
import { CustomIcon } from '../CustomIcon'
|
||||||
import React, { useState } from 'react'
|
import React, { ReactNode, useState } from 'react'
|
||||||
import { ActionButton } from '../ActionButton'
|
import { ActionButton } from '../ActionButton'
|
||||||
import { Selections, getSelectionTypeDisplayText } from 'lib/selections'
|
import { Selections, getSelectionTypeDisplayText } from 'lib/selections'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
@ -76,21 +76,8 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
|||||||
)}
|
)}
|
||||||
{selectedCommand?.name}
|
{selectedCommand?.name}
|
||||||
</p>
|
</p>
|
||||||
{Object.entries(selectedCommand?.args || {})
|
{Object.entries(selectedCommand?.args || {}).map(
|
||||||
.filter(([_, argConfig]) =>
|
([argName, arg], i) => (
|
||||||
typeof argConfig.required === 'function'
|
|
||||||
? argConfig.required(commandBarState.context)
|
|
||||||
: argConfig.required
|
|
||||||
)
|
|
||||||
.map(([argName, arg], i) => {
|
|
||||||
const argValue =
|
|
||||||
(typeof argumentsToSubmit[argName] === 'function'
|
|
||||||
? (argumentsToSubmit[argName] as Function)(
|
|
||||||
commandBarState.context
|
|
||||||
)
|
|
||||||
: argumentsToSubmit[argName]) || ''
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
<button
|
||||||
disabled={!isReviewing && currentArgument?.name === argName}
|
disabled={!isReviewing && currentArgument?.name === argName}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@ -108,24 +95,24 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
|||||||
: 'bg-chalkboard-20/50 dark:bg-chalkboard-80/50 border-chalkboard-20 dark:border-chalkboard-80'
|
: 'bg-chalkboard-20/50 dark:bg-chalkboard-80/50 border-chalkboard-20 dark:border-chalkboard-80'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span
|
<span className="capitalize">{argName}</span>
|
||||||
data-testid={`arg-name-${argName.toLowerCase()}`}
|
{argumentsToSubmit[argName] ? (
|
||||||
className="capitalize"
|
|
||||||
>
|
|
||||||
{argName}
|
|
||||||
</span>
|
|
||||||
{argValue ? (
|
|
||||||
arg.inputType === 'selection' ? (
|
arg.inputType === 'selection' ? (
|
||||||
getSelectionTypeDisplayText(argValue as Selections)
|
getSelectionTypeDisplayText(
|
||||||
|
argumentsToSubmit[argName] as Selections
|
||||||
|
)
|
||||||
) : arg.inputType === 'kcl' ? (
|
) : arg.inputType === 'kcl' ? (
|
||||||
roundOff(
|
roundOff(
|
||||||
Number((argValue as KclCommandValue).valueCalculated),
|
Number(
|
||||||
|
(argumentsToSubmit[argName] as KclCommandValue)
|
||||||
|
.valueCalculated
|
||||||
|
),
|
||||||
4
|
4
|
||||||
)
|
)
|
||||||
) : typeof argValue === 'object' ? (
|
) : typeof argumentsToSubmit[argName] === 'object' ? (
|
||||||
JSON.stringify(argValue)
|
JSON.stringify(argumentsToSubmit[argName])
|
||||||
) : (
|
) : (
|
||||||
<em>{argValue}</em>
|
<em>{argumentsToSubmit[argName] as ReactNode}</em>
|
||||||
)
|
)
|
||||||
) : null}
|
) : null}
|
||||||
{showShortcuts && (
|
{showShortcuts && (
|
||||||
@ -135,13 +122,11 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
|||||||
</small>
|
</small>
|
||||||
)}
|
)}
|
||||||
{arg.inputType === 'kcl' &&
|
{arg.inputType === 'kcl' &&
|
||||||
!!argValue &&
|
!!argumentsToSubmit[argName] &&
|
||||||
'variableName' in (argValue as KclCommandValue) && (
|
'variableName' in
|
||||||
|
(argumentsToSubmit[argName] as KclCommandValue) && (
|
||||||
<>
|
<>
|
||||||
<CustomIcon
|
<CustomIcon name="make-variable" className="w-4 h-4" />
|
||||||
name="make-variable"
|
|
||||||
className="w-4 h-4"
|
|
||||||
/>
|
|
||||||
<Tooltip position="blockEnd">
|
<Tooltip position="blockEnd">
|
||||||
New variable:{' '}
|
New variable:{' '}
|
||||||
{
|
{
|
||||||
@ -156,7 +141,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
})}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{isReviewing ? <ReviewingButton /> : <GatheringArgsButton />}
|
{isReviewing ? <ReviewingButton /> : <GatheringArgsButton />}
|
||||||
</div>
|
</div>
|
||||||
|
@ -48,8 +48,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
|||||||
if (!arg) return
|
if (!arg) return
|
||||||
})
|
})
|
||||||
|
|
||||||
function submitCommand(e: React.FormEvent<HTMLFormElement>) {
|
function submitCommand() {
|
||||||
e.preventDefault()
|
|
||||||
commandBarSend({
|
commandBarSend({
|
||||||
type: 'Submit command',
|
type: 'Submit command',
|
||||||
data: argumentsToSubmit,
|
data: argumentsToSubmit,
|
||||||
|
@ -29,7 +29,7 @@ function CommandBarSelectionInput({
|
|||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
const { commandBarState, commandBarSend } = useCommandsContext()
|
||||||
const [hasSubmitted, setHasSubmitted] = useState(false)
|
const [hasSubmitted, setHasSubmitted] = useState(false)
|
||||||
const selection = useSelector(arg.machineActor, selectionSelector)
|
const selection = useSelector(arg.actor, selectionSelector)
|
||||||
const [selectionsByType, setSelectionsByType] = useState<
|
const [selectionsByType, setSelectionsByType] = useState<
|
||||||
'none' | ResolvedSelectionType[]
|
'none' | ResolvedSelectionType[]
|
||||||
>(
|
>(
|
||||||
|
@ -9,7 +9,6 @@ export type CustomIconName =
|
|||||||
| 'clipboardCheckmark'
|
| 'clipboardCheckmark'
|
||||||
| 'close'
|
| 'close'
|
||||||
| 'equal'
|
| 'equal'
|
||||||
| 'exportFile'
|
|
||||||
| 'extrude'
|
| 'extrude'
|
||||||
| 'file'
|
| 'file'
|
||||||
| 'filePlus'
|
| 'filePlus'
|
||||||
@ -18,7 +17,6 @@ export type CustomIconName =
|
|||||||
| 'gear'
|
| 'gear'
|
||||||
| 'horizontal'
|
| 'horizontal'
|
||||||
| 'horizontalDash'
|
| 'horizontalDash'
|
||||||
| 'kcl'
|
|
||||||
| 'line'
|
| 'line'
|
||||||
| 'make-variable'
|
| 'make-variable'
|
||||||
| 'move'
|
| 'move'
|
||||||
@ -196,22 +194,6 @@ export const CustomIcon = ({
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
case 'exportFile':
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
{...props}
|
|
||||||
viewBox="0 0 20 20"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
clipRule="evenodd"
|
|
||||||
d="M4 3H4.5H11H11.2071L11.3536 3.14645L15.8536 7.64646L16 7.7929V8.00001V11.3773C15.6992 11.1362 15.3628 10.9376 15 10.7908V8.50001H11H10.5V8.00001V4H5V16H9.79076C9.93763 16.3628 10.1362 16.6992 10.3773 17H4.5H4V16.5V3.5V3ZM11.5 4.70711L14.2929 7.50001H11.5V4.70711ZM16.3904 14.1877L14.3904 11.6877L13.6096 12.3124L14.9597 14H11V15H14.9597L13.6096 16.6877L14.3904 17.3124L16.3904 14.8124L16.6403 14.5L16.3904 14.1877Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
case 'extrude':
|
case 'extrude':
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
@ -340,22 +322,6 @@ export const CustomIcon = ({
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
case 'kcl':
|
|
||||||
return (
|
|
||||||
<svg
|
|
||||||
{...props}
|
|
||||||
viewBox="0 0 40 40"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
clipRule="evenodd"
|
|
||||||
d="M40 0H0V40H40V0ZM7.34715 27.2143V15.6577L2.976 15.987V36.7949H7.34715V32.0645L8.00582 31.5256C8.24533 31.326 8.47487 31.1264 8.69442 30.9268L12.1075 36.7949H17.0475C16.1893 35.3978 15.311 33.9906 14.4128 32.5735C13.5346 31.1563 12.6664 29.7392 11.8081 28.3221L15.8499 24.9389C15.4308 24.4399 15.0017 23.931 14.5625 23.412L13.3051 21.8552L7.34715 27.2143ZM22.2581 26.6754C22.8769 25.9169 23.6753 25.5377 24.6533 25.5377C25.272 25.5377 25.8309 25.6175 26.3299 25.7772C26.8289 25.9169 27.4177 26.1465 28.0963 26.4658L29.3238 23.3521C28.5853 22.7933 27.7371 22.4041 26.779 22.1845C25.8409 21.9649 25.0625 21.8552 24.4437 21.8552C22.0885 21.8552 20.2223 22.5537 18.845 23.9509C17.4878 25.3281 16.8092 27.1944 16.8092 29.5496C16.8092 31.9048 17.4878 33.7611 18.845 35.1183C20.2223 36.4756 22.0885 37.1542 24.4437 37.1542C25.0625 37.1542 25.8509 37.0444 26.8089 36.8249C27.767 36.6053 28.6053 36.2161 29.3238 35.6572L28.0963 32.5435C27.4177 32.8629 26.8289 33.0924 26.3299 33.2321C25.8309 33.3718 25.272 33.4417 24.6533 33.4417C23.6753 33.4417 22.8769 33.0924 22.2581 32.3938C21.6594 31.6753 21.36 30.7272 21.36 29.5496C21.36 28.372 21.6594 27.4139 22.2581 26.6754ZM36.2796 36.7949V15.6577L31.9085 15.987V36.7949H36.2796Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
)
|
|
||||||
case 'line':
|
case 'line':
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
|
238
src/components/ExportButton.tsx
Normal file
@ -0,0 +1,238 @@
|
|||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { faFileExport, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
import { ActionButton } from './ActionButton'
|
||||||
|
import Modal from 'react-modal'
|
||||||
|
import React from 'react'
|
||||||
|
import { useFormik } from 'formik'
|
||||||
|
import { Models } from '@kittycad/lib'
|
||||||
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
|
||||||
|
type OutputFormat = Models['OutputFormat_type']
|
||||||
|
type OutputTypeKey = OutputFormat['type']
|
||||||
|
type ExtractStorageTypes<T> = T extends { storage: infer U } ? U : never
|
||||||
|
type StorageUnion = ExtractStorageTypes<OutputFormat>
|
||||||
|
|
||||||
|
export interface ExportButtonProps extends React.PropsWithChildren {
|
||||||
|
className?: {
|
||||||
|
button?: string
|
||||||
|
icon?: string
|
||||||
|
bg?: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
||||||
|
const [modalIsOpen, setIsOpen] = React.useState(false)
|
||||||
|
const {
|
||||||
|
settings: {
|
||||||
|
state: {
|
||||||
|
context: { baseUnit },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
|
const defaultType = 'gltf'
|
||||||
|
const [type, setType] = React.useState<OutputTypeKey>(defaultType)
|
||||||
|
const defaultStorage = 'embedded'
|
||||||
|
const [storage, setStorage] = React.useState<StorageUnion>(defaultStorage)
|
||||||
|
|
||||||
|
function openModal() {
|
||||||
|
setIsOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
setIsOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to gltf and embedded.
|
||||||
|
const initialValues: OutputFormat = {
|
||||||
|
type: defaultType,
|
||||||
|
storage: defaultStorage,
|
||||||
|
presentation: 'pretty',
|
||||||
|
}
|
||||||
|
const formik = useFormik({
|
||||||
|
initialValues,
|
||||||
|
onSubmit: (values: OutputFormat) => {
|
||||||
|
// Set the default coords.
|
||||||
|
if (
|
||||||
|
values.type === 'obj' ||
|
||||||
|
values.type === 'ply' ||
|
||||||
|
values.type === 'step' ||
|
||||||
|
values.type === 'stl'
|
||||||
|
) {
|
||||||
|
// Set the default coords.
|
||||||
|
// In the future we can make this configurable.
|
||||||
|
// But for now, its probably best to keep it consistent with the
|
||||||
|
// UI.
|
||||||
|
values.coords = {
|
||||||
|
forward: {
|
||||||
|
axis: 'y',
|
||||||
|
direction: 'negative',
|
||||||
|
},
|
||||||
|
up: {
|
||||||
|
axis: 'z',
|
||||||
|
direction: 'positive',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
values.type === 'obj' ||
|
||||||
|
values.type === 'stl' ||
|
||||||
|
values.type === 'ply'
|
||||||
|
) {
|
||||||
|
values.units = baseUnit
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
values.type === 'ply' ||
|
||||||
|
values.type === 'stl' ||
|
||||||
|
values.type === 'gltf'
|
||||||
|
) {
|
||||||
|
// Set the storage type.
|
||||||
|
values.storage = storage
|
||||||
|
}
|
||||||
|
if (values.type === 'ply' || values.type === 'stl') {
|
||||||
|
values.selection = { type: 'default_scene' }
|
||||||
|
}
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'export',
|
||||||
|
// By default let's leave this blank to export the whole scene.
|
||||||
|
// In the future we might want to let the user choose which entities
|
||||||
|
// in the scene to export. In that case, you'd pass the IDs thru here.
|
||||||
|
entity_ids: [],
|
||||||
|
format: values,
|
||||||
|
source_unit: baseUnit,
|
||||||
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
})
|
||||||
|
|
||||||
|
closeModal()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ActionButton
|
||||||
|
onClick={openModal}
|
||||||
|
Element="button"
|
||||||
|
icon={{
|
||||||
|
icon: faFileExport,
|
||||||
|
className: 'p-1',
|
||||||
|
size: 'sm',
|
||||||
|
iconClassName: className?.icon,
|
||||||
|
bgClassName: className?.bg,
|
||||||
|
}}
|
||||||
|
className={className?.button}
|
||||||
|
>
|
||||||
|
{children || 'Export'}
|
||||||
|
</ActionButton>
|
||||||
|
<Modal
|
||||||
|
isOpen={modalIsOpen}
|
||||||
|
onRequestClose={closeModal}
|
||||||
|
contentLabel="Export"
|
||||||
|
overlayClassName="z-40 fixed inset-0 grid place-items-center"
|
||||||
|
className="rounded p-4 bg-chalkboard-10 dark:bg-chalkboard-100 border max-w-xl w-full"
|
||||||
|
>
|
||||||
|
<h1 className="text-2xl font-bold">Export your design</h1>
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<div className="flex flex-wrap justify-between gap-8 items-center w-full my-8">
|
||||||
|
<label htmlFor="type" className="flex-1">
|
||||||
|
<p className="mb-2">Type</p>
|
||||||
|
<select
|
||||||
|
id="type"
|
||||||
|
name="type"
|
||||||
|
data-testid="export-type"
|
||||||
|
onChange={(e) => {
|
||||||
|
setType(e.target.value as OutputTypeKey)
|
||||||
|
if (e.target.value === 'gltf') {
|
||||||
|
// Set default to embedded.
|
||||||
|
setStorage('embedded')
|
||||||
|
} else if (e.target.value === 'ply') {
|
||||||
|
// Set default to ascii.
|
||||||
|
setStorage('ascii')
|
||||||
|
} else if (e.target.value === 'stl') {
|
||||||
|
// Set default to ascii.
|
||||||
|
setStorage('ascii')
|
||||||
|
}
|
||||||
|
formik.handleChange(e)
|
||||||
|
}}
|
||||||
|
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
|
||||||
|
>
|
||||||
|
<option value="gltf">gltf</option>
|
||||||
|
<option value="obj">obj</option>
|
||||||
|
<option value="ply">ply</option>
|
||||||
|
<option value="step">step</option>
|
||||||
|
<option value="stl">stl</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
{(type === 'gltf' || type === 'ply' || type === 'stl') && (
|
||||||
|
<label htmlFor="storage" className="flex-1">
|
||||||
|
<p className="mb-2">Storage</p>
|
||||||
|
<select
|
||||||
|
id="storage"
|
||||||
|
name="storage"
|
||||||
|
data-testid="export-storage"
|
||||||
|
onChange={(e) => {
|
||||||
|
setStorage(e.target.value as StorageUnion)
|
||||||
|
formik.handleChange(e)
|
||||||
|
}}
|
||||||
|
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
|
||||||
|
>
|
||||||
|
{type === 'gltf' && (
|
||||||
|
<>
|
||||||
|
<option value="embedded">embedded</option>
|
||||||
|
<option value="binary">binary</option>
|
||||||
|
<option value="standard">standard</option>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{type === 'stl' && (
|
||||||
|
<>
|
||||||
|
<option value="ascii">ascii</option>
|
||||||
|
<option value="binary">binary</option>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{type === 'ply' && (
|
||||||
|
<>
|
||||||
|
<option value="ascii">ascii</option>
|
||||||
|
<option value="binary_little_endian">
|
||||||
|
binary_little_endian
|
||||||
|
</option>
|
||||||
|
<option value="binary_big_endian">
|
||||||
|
binary_big_endian
|
||||||
|
</option>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex justify-between mt-6">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={closeModal}
|
||||||
|
icon={{
|
||||||
|
icon: faXmark,
|
||||||
|
className: 'p-1',
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName:
|
||||||
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
type="submit"
|
||||||
|
icon={{ icon: faFileExport, className: 'p-1' }}
|
||||||
|
>
|
||||||
|
Export
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -50,7 +50,10 @@ export const FileMachineProvider = ({
|
|||||||
selectedDirectory: project,
|
selectedDirectory: project,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
navigateToFile: (context, event) => {
|
navigateToFile: (
|
||||||
|
context: ContextFrom<typeof fileMachine>,
|
||||||
|
event: EventFrom<typeof fileMachine>
|
||||||
|
) => {
|
||||||
if (event.data && 'name' in event.data) {
|
if (event.data && 'name' in event.data) {
|
||||||
commandBarSend({ type: 'Close' })
|
commandBarSend({ type: 'Close' })
|
||||||
navigate(
|
navigate(
|
||||||
@ -74,7 +77,10 @@ export const FileMachineProvider = ({
|
|||||||
children: newFiles,
|
children: newFiles,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
createFile: async (context, event) => {
|
createFile: async (
|
||||||
|
context: ContextFrom<typeof fileMachine>,
|
||||||
|
event: EventFrom<typeof fileMachine, 'Create file'>
|
||||||
|
) => {
|
||||||
let name = event.data.name.trim() || DEFAULT_FILE_NAME
|
let name = event.data.name.trim() || DEFAULT_FILE_NAME
|
||||||
|
|
||||||
if (event.data.makeDir) {
|
if (event.data.makeDir) {
|
||||||
|
@ -3,7 +3,7 @@ import { paths } from 'lib/paths'
|
|||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import Tooltip from './Tooltip'
|
import Tooltip from './Tooltip'
|
||||||
import { FileEntry } from '@tauri-apps/api/fs'
|
import { FileEntry } from '@tauri-apps/api/fs'
|
||||||
import { Dispatch, useEffect, useRef, useState } from 'react'
|
import { Dispatch, useRef, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Dialog, Disclosure } from '@headlessui/react'
|
import { Dialog, Disclosure } from '@headlessui/react'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
@ -11,10 +11,7 @@ import { faChevronRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons'
|
|||||||
import { useFileContext } from 'hooks/useFileContext'
|
import { useFileContext } from 'hooks/useFileContext'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import styles from './FileTree.module.css'
|
import styles from './FileTree.module.css'
|
||||||
import { FILE_EXT, sortProject } from 'lib/tauriFS'
|
import { sortProject } from 'lib/tauriFS'
|
||||||
import { CustomIcon } from './CustomIcon'
|
|
||||||
import { kclManager } from 'lang/KclSingleton'
|
|
||||||
import { useDocumentHasFocus } from 'hooks/useDocumentHasFocus'
|
|
||||||
|
|
||||||
function getIndentationCSS(level: number) {
|
function getIndentationCSS(level: number) {
|
||||||
return `calc(1rem * ${level + 1})`
|
return `calc(1rem * ${level + 1})`
|
||||||
@ -160,23 +157,13 @@ const FileTreeItem = ({
|
|||||||
// Show the renaming form
|
// Show the renaming form
|
||||||
setIsRenaming(true)
|
setIsRenaming(true)
|
||||||
} else if (e.code === 'Space') {
|
} else if (e.code === 'Space') {
|
||||||
handleDoubleClick()
|
openFile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDoubleClick() {
|
function openFile() {
|
||||||
if (fileOrDir.children !== undefined) return // Don't open directories
|
if (fileOrDir.children !== undefined) return // Don't open directories
|
||||||
|
|
||||||
if (fileOrDir.name?.endsWith(FILE_EXT) === false && project?.path) {
|
|
||||||
// Import non-kcl files
|
|
||||||
kclManager.setCodeAndExecute(
|
|
||||||
`import("${fileOrDir.path.replace(project.path, '.')}")\n` +
|
|
||||||
kclManager.code
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Open kcl files
|
|
||||||
navigate(`${paths.FILE}/${encodeURIComponent(fileOrDir.path)}`)
|
navigate(`${paths.FILE}/${encodeURIComponent(fileOrDir.path)}`)
|
||||||
}
|
|
||||||
closePanel()
|
closePanel()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,12 +180,11 @@ const FileTreeItem = ({
|
|||||||
<button
|
<button
|
||||||
className="flex gap-1 items-center py-0.5 rounded-none border-none p-0 m-0 text-sm w-full hover:!bg-transparent text-left !text-inherit"
|
className="flex gap-1 items-center py-0.5 rounded-none border-none p-0 m-0 text-sm w-full hover:!bg-transparent text-left !text-inherit"
|
||||||
style={{ paddingInlineStart: getIndentationCSS(level) }}
|
style={{ paddingInlineStart: getIndentationCSS(level) }}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={openFile}
|
||||||
onClick={(e) => e.currentTarget.focus()}
|
onClick={(e) => e.currentTarget.focus()}
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
>
|
>
|
||||||
<CustomIcon
|
<KclIcon
|
||||||
name={fileOrDir.name?.endsWith(FILE_EXT) ? 'kcl' : 'file'}
|
|
||||||
className={
|
className={
|
||||||
'inline-block w-3 ' +
|
'inline-block w-3 ' +
|
||||||
(isCurrentFile
|
(isCurrentFile
|
||||||
@ -327,15 +313,9 @@ export const FileTree = ({
|
|||||||
closePanel,
|
closePanel,
|
||||||
}: FileTreeProps) => {
|
}: FileTreeProps) => {
|
||||||
const { send, context } = useFileContext()
|
const { send, context } = useFileContext()
|
||||||
const docuemntHasFocus = useDocumentHasFocus()
|
|
||||||
useHotkeys('meta + n', createFile)
|
useHotkeys('meta + n', createFile)
|
||||||
useHotkeys('meta + shift + n', createFolder)
|
useHotkeys('meta + shift + n', createFolder)
|
||||||
|
|
||||||
// Refresh the file tree when the document gets focus
|
|
||||||
useEffect(() => {
|
|
||||||
send({ type: 'Refresh' })
|
|
||||||
}, [docuemntHasFocus])
|
|
||||||
|
|
||||||
async function createFile() {
|
async function createFile() {
|
||||||
send({ type: 'Create file', data: { name: '', makeDir: false } })
|
send({ type: 'Create file', data: { name: '', makeDir: false } })
|
||||||
}
|
}
|
||||||
@ -401,3 +381,21 @@ export const FileTree = ({
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function KclIcon({ className = '' }: { className?: string }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
className={className}
|
||||||
|
viewBox="0 0 40 40"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M40 0H0V40H40V0ZM7.34715 27.2143V15.6577L2.976 15.987V36.7949H7.34715V32.0645L8.00582 31.5256C8.24533 31.326 8.47487 31.1264 8.69442 30.9268L12.1075 36.7949H17.0475C16.1893 35.3978 15.311 33.9906 14.4128 32.5735C13.5346 31.1563 12.6664 29.7392 11.8081 28.3221L15.8499 24.9389C15.4308 24.4399 15.0017 23.931 14.5625 23.412L13.3051 21.8552L7.34715 27.2143ZM22.2581 26.6754C22.8769 25.9169 23.6753 25.5377 24.6533 25.5377C25.272 25.5377 25.8309 25.6175 26.3299 25.7772C26.8289 25.9169 27.4177 26.1465 28.0963 26.4658L29.3238 23.3521C28.5853 22.7933 27.7371 22.4041 26.779 22.1845C25.8409 21.9649 25.0625 21.8552 24.4437 21.8552C22.0885 21.8552 20.2223 22.5537 18.845 23.9509C17.4878 25.3281 16.8092 27.1944 16.8092 29.5496C16.8092 31.9048 17.4878 33.7611 18.845 35.1183C20.2223 36.4756 22.0885 37.1542 24.4437 37.1542C25.0625 37.1542 25.8509 37.0444 26.8089 36.8249C27.767 36.6053 28.6053 36.2161 29.3238 35.6572L28.0963 32.5435C27.4177 32.8629 26.8289 33.0924 26.3299 33.2321C25.8309 33.3718 25.272 33.4417 24.6533 33.4417C23.6753 33.4417 22.8769 33.0924 22.2581 32.3938C21.6594 31.6753 21.36 30.7272 21.36 29.5496C21.36 28.372 21.6594 27.4139 22.2581 26.6754ZM36.2796 36.7949V15.6577L31.9085 15.987V36.7949H36.2796Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
@ -18,7 +18,6 @@ import {
|
|||||||
import { isTauri } from 'lib/isTauri'
|
import { isTauri } from 'lib/isTauri'
|
||||||
import { settingsCommandBarConfig } from 'lib/commandBarConfigs/settingsCommandConfig'
|
import { settingsCommandBarConfig } from 'lib/commandBarConfigs/settingsCommandConfig'
|
||||||
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
|
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
|
||||||
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -57,17 +56,15 @@ export const GlobalStateProvider = ({
|
|||||||
>
|
>
|
||||||
)
|
)
|
||||||
|
|
||||||
const [settingsState, settingsSend, settingsActor] = useMachine(
|
const [settingsState, settingsSend] = useMachine(settingsMachine, {
|
||||||
settingsMachine,
|
|
||||||
{
|
|
||||||
context: persistedSettings,
|
context: persistedSettings,
|
||||||
actions: {
|
actions: {
|
||||||
toastSuccess: (context, event) => {
|
toastSuccess: (context, event) => {
|
||||||
const truncatedNewValue =
|
const truncatedNewValue =
|
||||||
'data' in event && event.data instanceof Object
|
'data' in event && event.data instanceof Object
|
||||||
? (String(
|
? (context[Object.keys(event.data)[0] as keyof typeof context]
|
||||||
context[Object.keys(event.data)[0] as keyof typeof context]
|
.toString()
|
||||||
).substring(0, 28) as any)
|
.substring(0, 28) as any)
|
||||||
: undefined
|
: undefined
|
||||||
toast.success(
|
toast.success(
|
||||||
event.type +
|
event.type +
|
||||||
@ -79,8 +76,7 @@ export const GlobalStateProvider = ({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
)
|
|
||||||
settingsStateRef = settingsState.context
|
settingsStateRef = settingsState.context
|
||||||
|
|
||||||
useStateMachineCommands({
|
useStateMachineCommands({
|
||||||
@ -88,7 +84,6 @@ export const GlobalStateProvider = ({
|
|||||||
state: settingsState,
|
state: settingsState,
|
||||||
send: settingsSend,
|
send: settingsSend,
|
||||||
commandBarConfig: settingsCommandBarConfig,
|
commandBarConfig: settingsCommandBarConfig,
|
||||||
actor: settingsActor,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Listen for changes to the system theme and update the app theme accordingly
|
// Listen for changes to the system theme and update the app theme accordingly
|
||||||
@ -102,14 +97,13 @@ export const GlobalStateProvider = ({
|
|||||||
if (settingsState.context.theme !== 'system') return
|
if (settingsState.context.theme !== 'system') return
|
||||||
setThemeClass(e.matches ? Themes.Dark : Themes.Light)
|
setThemeClass(e.matches ? Themes.Dark : Themes.Light)
|
||||||
}
|
}
|
||||||
sceneInfra.baseUnit = settingsState?.context?.baseUnit || 'mm'
|
|
||||||
|
|
||||||
matcher.addEventListener('change', listener)
|
matcher.addEventListener('change', listener)
|
||||||
return () => matcher.removeEventListener('change', listener)
|
return () => matcher.removeEventListener('change', listener)
|
||||||
}, [settingsState.context])
|
}, [settingsState.context])
|
||||||
|
|
||||||
// Auth machine setup
|
// Auth machine setup
|
||||||
const [authState, authSend, authActor] = useMachine(authMachine, {
|
const [authState, authSend] = useMachine(authMachine, {
|
||||||
actions: {
|
actions: {
|
||||||
goToSignInPage: () => {
|
goToSignInPage: () => {
|
||||||
navigate(paths.SIGN_IN)
|
navigate(paths.SIGN_IN)
|
||||||
@ -129,7 +123,6 @@ export const GlobalStateProvider = ({
|
|||||||
state: authState,
|
state: authState,
|
||||||
send: authSend,
|
send: authSend,
|
||||||
commandBarConfig: authCommandBarConfig,
|
commandBarConfig: authCommandBarConfig,
|
||||||
actor: authActor,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -25,7 +25,8 @@ describe('processMemory', () => {
|
|||||||
|> lineTo([-3.35, 0.17], %)
|
|> lineTo([-3.35, 0.17], %)
|
||||||
|> lineTo([0.98, 5.16], %)
|
|> lineTo([0.98, 5.16], %)
|
||||||
|> lineTo([2.15, 4.32], %)
|
|> lineTo([2.15, 4.32], %)
|
||||||
// |> rx(90, %)`
|
// |> rx(90, %)
|
||||||
|
show(theExtrude, theSketch)`
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast, {
|
const programMemory = await enginelessExecutor(ast, {
|
||||||
root: {},
|
root: {},
|
||||||
|
@ -38,10 +38,6 @@ import { getSketchQuaternion } from 'clientSideScene/sceneEntities'
|
|||||||
import { startSketchOnDefault } from 'lang/modifyAst'
|
import { startSketchOnDefault } from 'lang/modifyAst'
|
||||||
import { Program } from 'lang/wasm'
|
import { Program } from 'lang/wasm'
|
||||||
import { isSingleCursorInPipe } from 'lang/queryAst'
|
import { isSingleCursorInPipe } from 'lang/queryAst'
|
||||||
import { TEST } from 'env'
|
|
||||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
|
||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
|
||||||
import toast from 'react-hot-toast'
|
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -58,12 +54,7 @@ export const ModelingMachineProvider = ({
|
|||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const { auth } = useGlobalStateContext()
|
||||||
auth,
|
|
||||||
settings: {
|
|
||||||
context: { baseUnit },
|
|
||||||
},
|
|
||||||
} = useGlobalStateContext()
|
|
||||||
const { code } = useKclContext()
|
const { code } = useKclContext()
|
||||||
const token = auth?.context?.token
|
const token = auth?.context?.token
|
||||||
const streamRef = useRef<HTMLDivElement>(null)
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
@ -179,56 +170,6 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
return { selectionRangeTypeMap }
|
return { selectionRangeTypeMap }
|
||||||
}),
|
}),
|
||||||
'Engine export': (_, event) => {
|
|
||||||
if (event.type !== 'Export' || TEST) return
|
|
||||||
const format = {
|
|
||||||
...event.data,
|
|
||||||
} as Partial<Models['OutputFormat_type']>
|
|
||||||
|
|
||||||
// Set all the un-configurable defaults here.
|
|
||||||
if (format.type === 'gltf') {
|
|
||||||
format.presentation = 'pretty'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
format.type === 'obj' ||
|
|
||||||
format.type === 'ply' ||
|
|
||||||
format.type === 'step' ||
|
|
||||||
format.type === 'stl'
|
|
||||||
) {
|
|
||||||
// Set the default coords.
|
|
||||||
// In the future we can make this configurable.
|
|
||||||
// But for now, its probably best to keep it consistent with the
|
|
||||||
// UI.
|
|
||||||
format.coords = {
|
|
||||||
forward: {
|
|
||||||
axis: 'y',
|
|
||||||
direction: 'negative',
|
|
||||||
},
|
|
||||||
up: {
|
|
||||||
axis: 'z',
|
|
||||||
direction: 'positive',
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
format.type === 'obj' ||
|
|
||||||
format.type === 'stl' ||
|
|
||||||
format.type === 'ply'
|
|
||||||
) {
|
|
||||||
format.units = baseUnit
|
|
||||||
}
|
|
||||||
|
|
||||||
if (format.type === 'ply' || format.type === 'stl') {
|
|
||||||
format.selection = { type: 'default_scene' }
|
|
||||||
}
|
|
||||||
|
|
||||||
exportFromEngine({
|
|
||||||
source_unit: baseUnit,
|
|
||||||
format: format as Models['OutputFormat_type'],
|
|
||||||
}).catch((e) => toast.error('Error while exporting', e)) // TODO I think we need to throw the error from engineCommandManager
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
guards: {
|
guards: {
|
||||||
'has valid extrude selection': ({ selectionRanges }) => {
|
'has valid extrude selection': ({ selectionRanges }) => {
|
||||||
@ -251,8 +192,6 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges
|
selectionRanges
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Has exportable geometry': () =>
|
|
||||||
kclManager.kclErrors.length === 0 && kclManager.ast.body.length > 0,
|
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
'AST-undo-startSketchOn': async ({ sketchPathToNode }) => {
|
'AST-undo-startSketchOn': async ({ sketchPathToNode }) => {
|
||||||
|
@ -5,6 +5,7 @@ import { type ProjectWithEntryPointMetadata } from 'lib/types'
|
|||||||
import { GlobalStateProvider } from './GlobalStateProvider'
|
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
import { vi } from 'vitest'
|
import { vi } from 'vitest'
|
||||||
|
import { ExportButtonProps } from './ExportButton'
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
const projectWellFormed = {
|
const projectWellFormed = {
|
||||||
@ -37,6 +38,15 @@ const projectWellFormed = {
|
|||||||
},
|
},
|
||||||
} satisfies ProjectWithEntryPointMetadata
|
} satisfies ProjectWithEntryPointMetadata
|
||||||
|
|
||||||
|
const mockExportButton = vi.fn()
|
||||||
|
vi.mock('/src/components/ExportButton', () => ({
|
||||||
|
// engineCommandManager method call in ExportButton causes vitest to hang
|
||||||
|
ExportButton: (props: ExportButtonProps) => {
|
||||||
|
mockExportButton(props)
|
||||||
|
return <button>Fake export button</button>
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
|
||||||
describe('ProjectSidebarMenu tests', () => {
|
describe('ProjectSidebarMenu tests', () => {
|
||||||
test('Renders the project name', () => {
|
test('Renders the project name', () => {
|
||||||
render(
|
render(
|
||||||
|
@ -5,12 +5,12 @@ import { type IndexLoaderData } from 'lib/types'
|
|||||||
import { paths } from 'lib/paths'
|
import { paths } from 'lib/paths'
|
||||||
import { isTauri } from '../lib/isTauri'
|
import { isTauri } from '../lib/isTauri'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
import { ExportButton } from './ExportButton'
|
||||||
import { Fragment } from 'react'
|
import { Fragment } from 'react'
|
||||||
import { FileTree } from './FileTree'
|
import { FileTree } from './FileTree'
|
||||||
import { sep } from '@tauri-apps/api/path'
|
import { sep } from '@tauri-apps/api/path'
|
||||||
import { Logo } from './Logo'
|
import { Logo } from './Logo'
|
||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
|
|
||||||
const ProjectSidebarMenu = ({
|
const ProjectSidebarMenu = ({
|
||||||
project,
|
project,
|
||||||
@ -21,8 +21,6 @@ const ProjectSidebarMenu = ({
|
|||||||
project?: IndexLoaderData['project']
|
project?: IndexLoaderData['project']
|
||||||
file?: IndexLoaderData['file']
|
file?: IndexLoaderData['file']
|
||||||
}) => {
|
}) => {
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
|
|
||||||
return renderAsLink ? (
|
return renderAsLink ? (
|
||||||
<Link
|
<Link
|
||||||
to={paths.HOME}
|
to={paths.HOME}
|
||||||
@ -114,19 +112,13 @@ const ProjectSidebarMenu = ({
|
|||||||
<div className="flex-1 overflow-hidden" />
|
<div className="flex-1 overflow-hidden" />
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
|
<div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
|
||||||
<ActionButton
|
<ExportButton
|
||||||
Element="button"
|
className={{
|
||||||
icon={{ icon: 'exportFile', className: 'p-1' }}
|
button: 'border-transparent dark:border-transparent',
|
||||||
className="border-transparent dark:border-transparent"
|
}}
|
||||||
onClick={() =>
|
|
||||||
commandBarSend({
|
|
||||||
type: 'Find and select command',
|
|
||||||
data: { name: 'Export', ownerMachine: 'modeling' },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
Export Part
|
Export Model
|
||||||
</ActionButton>
|
</ExportButton>
|
||||||
{isTauri() && (
|
{isTauri() && (
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="link"
|
Element="link"
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
import {
|
||||||
|
MouseEventHandler,
|
||||||
|
WheelEventHandler,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { getNormalisedCoordinates } from '../lib/utils'
|
import { getNormalisedCoordinates, throttle } from '../lib/utils'
|
||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
@ -29,6 +36,7 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
|||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
}))
|
}))
|
||||||
const { settings } = useGlobalStateContext()
|
const { settings } = useGlobalStateContext()
|
||||||
|
const cameraControls = settings?.context?.cameraControls
|
||||||
const { state } = useModelingContext()
|
const { state } = useModelingContext()
|
||||||
const { isExecuting } = useKclContext()
|
const { isExecuting } = useKclContext()
|
||||||
const { overallState } = useNetworkStatus()
|
const { overallState } = useNetworkStatus()
|
||||||
@ -60,6 +68,19 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
|||||||
setClickCoords({ x, y })
|
setClickCoords({ x, y })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fps = 60
|
||||||
|
const handleScroll: WheelEventHandler<HTMLVideoElement> = throttle((e) => {
|
||||||
|
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_zoom',
|
||||||
|
magnitude: e.deltaY * 0.4,
|
||||||
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
})
|
||||||
|
}, Math.round(1000 / fps))
|
||||||
|
|
||||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
|
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
|
||||||
clientX,
|
clientX,
|
||||||
clientY,
|
clientY,
|
||||||
@ -138,13 +159,14 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
|||||||
muted
|
muted
|
||||||
autoPlay
|
autoPlay
|
||||||
controls={false}
|
controls={false}
|
||||||
|
onWheel={handleScroll}
|
||||||
onPlay={() => setIsLoading(false)}
|
onPlay={() => setIsLoading(false)}
|
||||||
onMouseMoveCapture={handleMouseMove}
|
onMouseMoveCapture={handleMouseMove}
|
||||||
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
|
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
|
||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
||||||
/>
|
/>
|
||||||
<ClientSideScene cameraControls={settings.context?.cameraControls} />
|
<ClientSideScene cameraControls={settings.context.cameraControls} />
|
||||||
{!isNetworkOkay && !isLoading && (
|
{!isNetworkOkay && !isLoading && (
|
||||||
<div className="text-center absolute inset-0">
|
<div className="text-center absolute inset-0">
|
||||||
<Loading>
|
<Loading>
|
||||||
|
@ -108,8 +108,8 @@ export const TextEditor = ({
|
|||||||
state,
|
state,
|
||||||
} = useModelingContext()
|
} = useModelingContext()
|
||||||
|
|
||||||
const { settings, auth } = useGlobalStateContext()
|
const { settings: { context: { textWrapping } = {} } = {}, auth } =
|
||||||
const textWrapping = settings.context?.textWrapping ?? 'On'
|
useGlobalStateContext()
|
||||||
const { commandBarSend } = useCommandsContext()
|
const { commandBarSend } = useCommandsContext()
|
||||||
const {
|
const {
|
||||||
context: { project },
|
context: { project },
|
||||||
|
@ -27,8 +27,6 @@ describe('UserSidebarMenu tests', () => {
|
|||||||
phone: '555-555-5555',
|
phone: '555-555-5555',
|
||||||
first_name: 'Test',
|
first_name: 'Test',
|
||||||
last_name: 'User',
|
last_name: 'User',
|
||||||
can_train_on_data: false,
|
|
||||||
is_service_account: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(
|
render(
|
||||||
@ -59,8 +57,6 @@ describe('UserSidebarMenu tests', () => {
|
|||||||
first_name: '',
|
first_name: '',
|
||||||
last_name: '',
|
last_name: '',
|
||||||
name: '',
|
name: '',
|
||||||
can_train_on_data: false,
|
|
||||||
is_service_account: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(
|
render(
|
||||||
@ -88,8 +84,6 @@ describe('UserSidebarMenu tests', () => {
|
|||||||
first_name: 'Test',
|
first_name: 'Test',
|
||||||
last_name: 'User',
|
last_name: 'User',
|
||||||
image: '',
|
image: '',
|
||||||
can_train_on_data: false,
|
|
||||||
is_service_account: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render(
|
render(
|
||||||
|
@ -4,7 +4,6 @@ import { ViewPlugin, hoverTooltip, tooltips } from '@codemirror/view'
|
|||||||
import { CompletionTriggerKind } from 'vscode-languageserver-protocol'
|
import { CompletionTriggerKind } from 'vscode-languageserver-protocol'
|
||||||
import { offsetToPos } from 'editor/plugins/lsp/util'
|
import { offsetToPos } from 'editor/plugins/lsp/util'
|
||||||
import { LanguageServerOptions } from 'editor/plugins/lsp'
|
import { LanguageServerOptions } from 'editor/plugins/lsp'
|
||||||
import { syntaxTree } from '@codemirror/language'
|
|
||||||
import {
|
import {
|
||||||
LanguageServerPlugin,
|
LanguageServerPlugin,
|
||||||
documentUri,
|
documentUri,
|
||||||
@ -41,14 +40,6 @@ export function kclPlugin(options: LanguageServerOptions): Extension {
|
|||||||
if (plugin == null) return null
|
if (plugin == null) return null
|
||||||
|
|
||||||
const { state, pos, explicit } = context
|
const { state, pos, explicit } = context
|
||||||
|
|
||||||
let nodeBefore = syntaxTree(state).resolveInner(pos, -1)
|
|
||||||
if (
|
|
||||||
nodeBefore.name === 'BlockComment' ||
|
|
||||||
nodeBefore.name === 'LineComment'
|
|
||||||
)
|
|
||||||
return null
|
|
||||||
|
|
||||||
const line = state.doc.lineAt(pos)
|
const line = state.doc.lineAt(pos)
|
||||||
let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked
|
let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked
|
||||||
let trigChar: string | undefined
|
let trigChar: string | undefined
|
||||||
@ -69,7 +60,6 @@ export function kclPlugin(options: LanguageServerOptions): Extension {
|
|||||||
) {
|
) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return await plugin.requestCompletion(
|
return await plugin.requestCompletion(
|
||||||
context,
|
context,
|
||||||
offsetToPos(state.doc, pos),
|
offsetToPos(state.doc, pos),
|
||||||
|
@ -7,5 +7,6 @@ export const VITE_KC_API_BASE_URL = import.meta.env.VITE_KC_API_BASE_URL
|
|||||||
export const VITE_KC_SITE_BASE_URL = import.meta.env.VITE_KC_SITE_BASE_URL
|
export const VITE_KC_SITE_BASE_URL = import.meta.env.VITE_KC_SITE_BASE_URL
|
||||||
export const VITE_KC_CONNECTION_TIMEOUT_MS = import.meta.env
|
export const VITE_KC_CONNECTION_TIMEOUT_MS = import.meta.env
|
||||||
.VITE_KC_CONNECTION_TIMEOUT_MS
|
.VITE_KC_CONNECTION_TIMEOUT_MS
|
||||||
|
export const VITE_KC_SENTRY_DSN = import.meta.env.VITE_KC_SENTRY_DSN
|
||||||
export const TEST = import.meta.env.TEST
|
export const TEST = import.meta.env.TEST
|
||||||
export const DEV = import.meta.env.DEV
|
export const DEV = import.meta.env.DEV
|
||||||
|
@ -1,31 +0,0 @@
|
|||||||
// Based on https://learnersbucket.com/examples/interview/usehasfocus-hook-in-react/
|
|
||||||
import { useState, useEffect } from 'react'
|
|
||||||
|
|
||||||
export const useDocumentHasFocus = () => {
|
|
||||||
// get the initial state
|
|
||||||
const [focus, setFocus] = useState(document.hasFocus())
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// helper functions to update the status
|
|
||||||
const onFocus = () => setFocus(true)
|
|
||||||
const onBlur = () => setFocus(false)
|
|
||||||
|
|
||||||
// assign the listener
|
|
||||||
// update the status on the event
|
|
||||||
if (globalThis.window && typeof globalThis.window !== 'undefined') {
|
|
||||||
globalThis.window.addEventListener('focus', onFocus)
|
|
||||||
globalThis.window.addEventListener('blur', onBlur)
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the listener
|
|
||||||
return () => {
|
|
||||||
if (globalThis.window && typeof globalThis.window !== 'undefined') {
|
|
||||||
globalThis.window.removeEventListener('focus', onFocus)
|
|
||||||
globalThis.window.removeEventListener('blur', onBlur)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// return the status
|
|
||||||
return focus
|
|
||||||
}
|
|
@ -28,7 +28,7 @@ interface UseStateMachineCommandsArgs<
|
|||||||
machineId: T['id']
|
machineId: T['id']
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
send: Function
|
send: Function
|
||||||
actor: InterpreterFrom<T>
|
actor?: InterpreterFrom<T>
|
||||||
commandBarConfig?: CommandSetConfig<T, S>
|
commandBarConfig?: CommandSetConfig<T, S>
|
||||||
allCommandsRequireNetwork?: boolean
|
allCommandsRequireNetwork?: boolean
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
|
@ -93,7 +93,7 @@ class KclManager {
|
|||||||
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
||||||
this._params.id &&
|
this._params.id &&
|
||||||
writeTextFile(this._params.id, code).catch((err) => {
|
writeTextFile(this._params.id, code).catch((err) => {
|
||||||
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
|
// TODO: add Sentry per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
|
||||||
console.error('error saving file', err)
|
console.error('error saving file', err)
|
||||||
toast.error('Error saving file, please check file permissions')
|
toast.error('Error saving file, please check file permissions')
|
||||||
})
|
})
|
||||||
|
@ -11,11 +11,16 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)`
|
// |> rx(45, %)
|
||||||
|
show(mySketch001)`
|
||||||
const programMemory = await enginelessExecutor(parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const sketch001 = programMemory?.root?.mySketch001
|
const shown = programMemory?.return?.map(
|
||||||
expect(sketch001).toEqual({
|
// @ts-ignore
|
||||||
|
(a) => programMemory?.root?.[a.name]
|
||||||
|
)
|
||||||
|
expect(shown).toEqual([
|
||||||
|
{
|
||||||
type: 'SketchGroup',
|
type: 'SketchGroup',
|
||||||
on: expect.any(Object),
|
on: expect.any(Object),
|
||||||
start: {
|
start: {
|
||||||
@ -57,7 +62,8 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
entityId: expect.any(String),
|
entityId: expect.any(String),
|
||||||
__meta: [{ sourceRange: [46, 71] }],
|
__meta: [{ sourceRange: [46, 71] }],
|
||||||
})
|
},
|
||||||
|
])
|
||||||
})
|
})
|
||||||
test('extrude artifacts', async () => {
|
test('extrude artifacts', async () => {
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
@ -67,11 +73,16 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> extrude(2, %)`
|
|> extrude(2, %)
|
||||||
|
show(mySketch001)`
|
||||||
const programMemory = await enginelessExecutor(parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const sketch001 = programMemory?.root?.mySketch001
|
const shown = programMemory?.return?.map(
|
||||||
expect(sketch001).toEqual({
|
// @ts-ignore
|
||||||
|
(a) => programMemory?.root?.[a.name]
|
||||||
|
)
|
||||||
|
expect(shown).toEqual([
|
||||||
|
{
|
||||||
type: 'ExtrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
value: [],
|
value: [],
|
||||||
@ -80,12 +91,12 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
endCapId: null,
|
endCapId: null,
|
||||||
startCapId: null,
|
startCapId: null,
|
||||||
sketchGroupValues: expect.any(Array),
|
|
||||||
xAxis: { x: 1, y: 0, z: 0 },
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
yAxis: { x: 0, y: 1, z: 0 },
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
zAxis: { x: 0, y: 0, z: 1 },
|
zAxis: { x: 0, y: 0, z: 1 },
|
||||||
__meta: [{ sourceRange: [46, 71] }],
|
__meta: [{ sourceRange: [46, 71] }],
|
||||||
})
|
},
|
||||||
|
])
|
||||||
})
|
})
|
||||||
test('sketch extrude and sketch on one of the faces', async () => {
|
test('sketch extrude and sketch on one of the faces', async () => {
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
@ -109,10 +120,14 @@ const sk2 = startSketchOn('XY')
|
|||||||
// |> transform(theTransf, %)
|
// |> transform(theTransf, %)
|
||||||
|> extrude(2, %)
|
|> extrude(2, %)
|
||||||
|
|
||||||
`
|
|
||||||
|
show(theExtrude, sk2)`
|
||||||
const programMemory = await enginelessExecutor(parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const geos = [programMemory?.root?.theExtrude, programMemory?.root?.sk2]
|
const geos = programMemory?.return?.map(
|
||||||
|
// @ts-ignore
|
||||||
|
({ name }) => programMemory?.root?.[name]
|
||||||
|
)
|
||||||
expect(geos).toEqual([
|
expect(geos).toEqual([
|
||||||
{
|
{
|
||||||
type: 'ExtrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
@ -123,7 +138,6 @@ const sk2 = startSketchOn('XY')
|
|||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
endCapId: null,
|
endCapId: null,
|
||||||
startCapId: null,
|
startCapId: null,
|
||||||
sketchGroupValues: expect.any(Array),
|
|
||||||
xAxis: { x: 1, y: 0, z: 0 },
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
yAxis: { x: 0, y: 1, z: 0 },
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
zAxis: { x: 0, y: 0, z: 1 },
|
zAxis: { x: 0, y: 0, z: 1 },
|
||||||
@ -139,7 +153,6 @@ const sk2 = startSketchOn('XY')
|
|||||||
|
|
||||||
endCapId: null,
|
endCapId: null,
|
||||||
startCapId: null,
|
startCapId: null,
|
||||||
sketchGroupValues: expect.any(Array),
|
|
||||||
xAxis: { x: 1, y: 0, z: 0 },
|
xAxis: { x: 1, y: 0, z: 0 },
|
||||||
yAxis: { x: 0, y: 1, z: 0 },
|
yAxis: { x: 0, y: 1, z: 0 },
|
||||||
zAxis: { x: 0, y: 0, z: 1 },
|
zAxis: { x: 0, y: 0, z: 1 },
|
||||||
|
@ -47,8 +47,9 @@ const newVar = myVar + 1`
|
|||||||
|> lineTo([2,3], %)
|
|> lineTo([2,3], %)
|
||||||
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
|
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
|
||||||
// |> close(%)
|
// |> close(%)
|
||||||
|
show(mySketch)
|
||||||
`
|
`
|
||||||
const { root } = await exe(code)
|
const { root, return: _return } = await exe(code)
|
||||||
// geo is three js buffer geometry and is very bloated to have in tests
|
// geo is three js buffer geometry and is very bloated to have in tests
|
||||||
const minusGeo = root.mySketch.value
|
const minusGeo = root.mySketch.value
|
||||||
expect(minusGeo).toEqual([
|
expect(minusGeo).toEqual([
|
||||||
@ -83,6 +84,15 @@ const newVar = myVar + 1`
|
|||||||
name: 'rightPath',
|
name: 'rightPath',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
// expect(root.mySketch.sketch[0]).toEqual(root.mySketch.sketch[4].firstPath)
|
||||||
|
expect(_return).toEqual([
|
||||||
|
{
|
||||||
|
type: 'Identifier',
|
||||||
|
start: 203,
|
||||||
|
end: 211,
|
||||||
|
name: 'mySketch',
|
||||||
|
},
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('pipe binary expression into call expression', async () => {
|
it('pipe binary expression into call expression', async () => {
|
||||||
@ -347,6 +357,7 @@ describe('testing math operators', () => {
|
|||||||
` -legLen(segLen('seg01', %), myVar)`,
|
` -legLen(segLen('seg01', %), myVar)`,
|
||||||
`], %)`,
|
`], %)`,
|
||||||
``,
|
``,
|
||||||
|
`show(part001)`,
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
const sketch = root.part001
|
const sketch = root.part001
|
||||||
@ -381,7 +392,8 @@ const theExtrude = startSketchOn('XY')
|
|||||||
|> line([-0.76], myVarZ, %)
|
|> line([-0.76], myVarZ, %)
|
||||||
|> line([5,5], %)
|
|> line([5,5], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(4, %)`
|
|> extrude(4, %)
|
||||||
|
show(theExtrude)`
|
||||||
await expect(exe(code)).rejects.toEqual(
|
await expect(exe(code)).rejects.toEqual(
|
||||||
new KCLError(
|
new KCLError(
|
||||||
'undefined_value',
|
'undefined_value',
|
||||||
|
@ -122,6 +122,7 @@ describe('Testing addSketchTo', () => {
|
|||||||
expect(str).toBe(`const part001 = startSketchOn('YZ')
|
expect(str).toBe(`const part001 = startSketchOn('YZ')
|
||||||
|> startProfileAt('default', %)
|
|> startProfileAt('default', %)
|
||||||
|> line('default', %)
|
|> line('default', %)
|
||||||
|
show(part001)
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -146,7 +147,8 @@ describe('Testing giveSketchFnCallTag', () => {
|
|||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([-2.57, -0.13], %)
|
|> line([-2.57, -0.13], %)
|
||||||
|> line([0, 0.83], %)
|
|> line([0, 0.83], %)
|
||||||
|> line([0.82, 0.34], %)`
|
|> line([0.82, 0.34], %)
|
||||||
|
show(part001)`
|
||||||
it('Should add tag to a sketch function call', () => {
|
it('Should add tag to a sketch function call', () => {
|
||||||
const { newCode, tag, isTagExisting } = giveSketchFnCallTagTestHelper(
|
const { newCode, tag, isTagExisting } = giveSketchFnCallTagTestHelper(
|
||||||
code,
|
code,
|
||||||
@ -202,7 +204,8 @@ const part001 = startSketchOn('XY')
|
|||||||
|> angledLine([def(yo), 3.09], %)
|
|> angledLine([def(yo), 3.09], %)
|
||||||
|> angledLine([ghi(%), 3.09], %)
|
|> angledLine([ghi(%), 3.09], %)
|
||||||
|> angledLine([jkl(yo) + 2, 3.09], %)
|
|> angledLine([jkl(yo) + 2, 3.09], %)
|
||||||
const yo2 = hmm([identifierGuy + 5])`
|
const yo2 = hmm([identifierGuy + 5])
|
||||||
|
show(part001)`
|
||||||
it('should move a binary expression into a new variable', async () => {
|
it('should move a binary expression into a new variable', async () => {
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
PipeExpression,
|
PipeExpression,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
|
ExpressionStatement,
|
||||||
Value,
|
Value,
|
||||||
Literal,
|
Literal,
|
||||||
PipeSubstitution,
|
PipeSubstitution,
|
||||||
@ -127,8 +128,16 @@ export function addSketchTo(
|
|||||||
createPipeExpression(pipeBody)
|
createPipeExpression(pipeBody)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const showCallIndex = getShowIndex(_node)
|
||||||
|
let sketchIndex = showCallIndex
|
||||||
|
if (showCallIndex === -1) {
|
||||||
_node.body = [...node.body, variableDeclaration]
|
_node.body = [...node.body, variableDeclaration]
|
||||||
let sketchIndex = _node.body.length - 1
|
sketchIndex = _node.body.length - 1
|
||||||
|
} else {
|
||||||
|
const newBody = [...node.body]
|
||||||
|
newBody.splice(showCallIndex, 0, variableDeclaration)
|
||||||
|
_node.body = newBody
|
||||||
|
}
|
||||||
let pathToNode: PathToNode = [
|
let pathToNode: PathToNode = [
|
||||||
['body', ''],
|
['body', ''],
|
||||||
[sketchIndex, 'index'],
|
[sketchIndex, 'index'],
|
||||||
@ -141,7 +150,7 @@ export function addSketchTo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: addToShow(_node, _name),
|
||||||
id: _name,
|
id: _name,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
}
|
}
|
||||||
@ -182,6 +191,44 @@ export function findUniqueName(
|
|||||||
return findUniqueName(searchStr, name, pad, index + 1)
|
return findUniqueName(searchStr, name, pad, index + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addToShow(node: Program, name: string): Program {
|
||||||
|
const _node = { ...node }
|
||||||
|
const dumbyStartend = { start: 0, end: 0 }
|
||||||
|
const showCallIndex = getShowIndex(_node)
|
||||||
|
if (showCallIndex === -1) {
|
||||||
|
const showCall = createCallExpressionStdLib('show', [
|
||||||
|
createIdentifier(name),
|
||||||
|
])
|
||||||
|
const showExpressionStatement: ExpressionStatement = {
|
||||||
|
type: 'ExpressionStatement',
|
||||||
|
...dumbyStartend,
|
||||||
|
expression: showCall,
|
||||||
|
}
|
||||||
|
_node.body = [..._node.body, showExpressionStatement]
|
||||||
|
return _node
|
||||||
|
}
|
||||||
|
const showCall = { ..._node.body[showCallIndex] } as ExpressionStatement
|
||||||
|
const showCallArgs = (showCall.expression as CallExpression).arguments
|
||||||
|
const newShowCallArgs: Value[] = [...showCallArgs, createIdentifier(name)]
|
||||||
|
const newShowExpression = createCallExpressionStdLib('show', newShowCallArgs)
|
||||||
|
|
||||||
|
_node.body[showCallIndex] = {
|
||||||
|
...showCall,
|
||||||
|
expression: newShowExpression,
|
||||||
|
}
|
||||||
|
return _node
|
||||||
|
}
|
||||||
|
|
||||||
|
function getShowIndex(node: Program): number {
|
||||||
|
return node.body.findIndex(
|
||||||
|
(statement) =>
|
||||||
|
statement.type === 'ExpressionStatement' &&
|
||||||
|
statement.expression.type === 'CallExpression' &&
|
||||||
|
statement.expression.callee.type === 'Identifier' &&
|
||||||
|
statement.expression.callee.name === 'show'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function mutateArrExp(
|
export function mutateArrExp(
|
||||||
node: Value,
|
node: Value,
|
||||||
updateWith: ArrayExpression
|
updateWith: ArrayExpression
|
||||||
@ -301,10 +348,15 @@ export function extrudeSketch(
|
|||||||
}
|
}
|
||||||
const name = findUniqueName(node, 'part')
|
const name = findUniqueName(node, 'part')
|
||||||
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
|
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
|
||||||
_node.body.splice(_node.body.length, 0, VariableDeclaration)
|
let showCallIndex = getShowIndex(_node)
|
||||||
|
if (showCallIndex === -1) {
|
||||||
|
// We didn't find a show, so let's just append everything
|
||||||
|
showCallIndex = _node.body.length
|
||||||
|
}
|
||||||
|
_node.body.splice(showCallIndex, 0, VariableDeclaration)
|
||||||
const pathToExtrudeArg: PathToNode = [
|
const pathToExtrudeArg: PathToNode = [
|
||||||
['body', ''],
|
['body', ''],
|
||||||
[_node.body.length, 'index'],
|
[showCallIndex, 'index'],
|
||||||
['declarations', 'VariableDeclaration'],
|
['declarations', 'VariableDeclaration'],
|
||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
['init', 'VariableDeclarator'],
|
['init', 'VariableDeclarator'],
|
||||||
@ -313,7 +365,7 @@ export function extrudeSketch(
|
|||||||
]
|
]
|
||||||
return {
|
return {
|
||||||
modifiedAst: node,
|
modifiedAst: node,
|
||||||
pathToNode: [...pathToNode.slice(0, -1), [-1, 'index']],
|
pathToNode: [...pathToNode.slice(0, -1), [showCallIndex, 'index']],
|
||||||
pathToExtrudeArg,
|
pathToExtrudeArg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -373,7 +425,7 @@ export function sketchOnExtrudedFace(
|
|||||||
_node.body.splice(expressionIndex + 1, 0, newSketch)
|
_node.body.splice(expressionIndex + 1, 0, newSketch)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: addToShow(_node, newSketchName),
|
||||||
pathToNode: [...pathToNode.slice(0, -1), [expressionIndex, 'index']],
|
pathToNode: [...pathToNode.slice(0, -1), [expressionIndex, 'index']],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,8 @@ const part001 = startSketchOn('XY')
|
|||||||
|> xLine(3.84, %) // selection-range-7ish-before-this
|
|> xLine(3.84, %) // selection-range-7ish-before-this
|
||||||
|
|
||||||
const variableBelowShouldNotBeIncluded = 3
|
const variableBelowShouldNotBeIncluded = 3
|
||||||
`
|
|
||||||
|
show(part001)`
|
||||||
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
|
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
@ -68,7 +69,8 @@ describe('testing argIsNotIdentifier', () => {
|
|||||||
|> angledLine([ghi(%), 3.09], %)
|
|> angledLine([ghi(%), 3.09], %)
|
||||||
|> angledLine([jkl('yo') + 2, 3.09], %)
|
|> angledLine([jkl('yo') + 2, 3.09], %)
|
||||||
const yo = 5 + 6
|
const yo = 5 + 6
|
||||||
const yo2 = hmm([identifierGuy + 5])`
|
const yo2 = hmm([identifierGuy + 5])
|
||||||
|
show(part001)`
|
||||||
it('find a safe binaryExpression', () => {
|
it('find a safe binaryExpression', () => {
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
const rangeStart = code.indexOf('100 + 100') + 2
|
const rangeStart = code.indexOf('100 + 100') + 2
|
||||||
@ -199,7 +201,8 @@ describe('testing getNodePathFromSourceRange', () => {
|
|||||||
const code = `const part001 = startSketchOn('XY')
|
const code = `const part001 = startSketchOn('XY')
|
||||||
|> startProfileAt([0.39, -0.05], %)
|
|> startProfileAt([0.39, -0.05], %)
|
||||||
|> line([0.94, 2.61], %)
|
|> line([0.94, 2.61], %)
|
||||||
|> line([-0.21, -1.4], %)`
|
|> line([-0.21, -1.4], %)
|
||||||
|
show(part001)`
|
||||||
it('finds the second line when cursor is put at the end', () => {
|
it('finds the second line when cursor is put at the end', () => {
|
||||||
const searchLn = `line([0.94, 2.61], %)`
|
const searchLn = `line([0.94, 2.61], %)`
|
||||||
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
||||||
|
@ -68,6 +68,8 @@ log(5, myVar)
|
|||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
|> lineTo({ to: [1, 0], tag: "rightPath" }, %)
|
|> lineTo({ to: [1, 0], tag: "rightPath" }, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|
|
||||||
|
show(mySketch)
|
||||||
`
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
@ -329,6 +331,7 @@ describe('it recasts wrapped object expressions in pipe bodies with correct inde
|
|||||||
intersectTag: 'seg01'
|
intersectTag: 'seg01'
|
||||||
}, %)
|
}, %)
|
||||||
|> line([-0.42, -1.72], %)
|
|> line([-0.42, -1.72], %)
|
||||||
|
show(part001)
|
||||||
`
|
`
|
||||||
const { ast } = code2ast(code)
|
const { ast } = code2ast(code)
|
||||||
const recasted = recast(ast)
|
const recasted = recast(ast)
|
||||||
|
@ -3,6 +3,7 @@ import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
|
|||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { exportSave } from 'lib/exportSave'
|
import { exportSave } from 'lib/exportSave'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import * as Sentry from '@sentry/react'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
import { sceneInfra } from 'clientSideScene/sceneInfra'
|
||||||
|
|
||||||
@ -289,6 +290,12 @@ class EngineConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// shouldTrace will return true when Sentry should be used to instrument
|
||||||
|
// the Engine.
|
||||||
|
shouldTrace() {
|
||||||
|
return Sentry.getCurrentHub()?.getClient()?.getOptions()?.sendClientReports
|
||||||
|
}
|
||||||
|
|
||||||
// connect will attempt to connect to the Engine over a WebSocket, and
|
// connect will attempt to connect to the Engine over a WebSocket, and
|
||||||
// establish the WebRTC connections.
|
// establish the WebRTC connections.
|
||||||
//
|
//
|
||||||
@ -301,6 +308,41 @@ class EngineConnection {
|
|||||||
|
|
||||||
// Information on the connect transaction
|
// Information on the connect transaction
|
||||||
|
|
||||||
|
class SpanPromise {
|
||||||
|
span: Sentry.Span
|
||||||
|
promise: Promise<void>
|
||||||
|
resolve?: (v: void) => void
|
||||||
|
|
||||||
|
constructor(span: Sentry.Span) {
|
||||||
|
this.span = span
|
||||||
|
this.promise = new Promise((resolve) => {
|
||||||
|
this.resolve = (v: void) => {
|
||||||
|
// here we're going to invoke finish before resolving the
|
||||||
|
// promise so that a `.then()` will order strictly after
|
||||||
|
// all spans have -- for sure -- been resolved, rather than
|
||||||
|
// doing a `then` on this promise.
|
||||||
|
this.span.finish()
|
||||||
|
resolve(v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let webrtcMediaTransaction: Sentry.Transaction
|
||||||
|
let websocketSpan: SpanPromise
|
||||||
|
let mediaTrackSpan: SpanPromise
|
||||||
|
let dataChannelSpan: SpanPromise
|
||||||
|
let handshakeSpan: SpanPromise
|
||||||
|
let iceSpan: SpanPromise
|
||||||
|
|
||||||
|
const spanStart = (op: string) =>
|
||||||
|
new SpanPromise(webrtcMediaTransaction.startChild({ op }))
|
||||||
|
|
||||||
|
if (this.shouldTrace()) {
|
||||||
|
webrtcMediaTransaction = Sentry.startTransaction({ name: 'webrtc-media' })
|
||||||
|
websocketSpan = spanStart('websocket')
|
||||||
|
}
|
||||||
|
|
||||||
const createPeerConnection = () => {
|
const createPeerConnection = () => {
|
||||||
this.pc = new RTCPeerConnection()
|
this.pc = new RTCPeerConnection()
|
||||||
|
|
||||||
@ -351,6 +393,10 @@ class EngineConnection {
|
|||||||
// From what I understand, only after have we done the ICE song and
|
// From what I understand, only after have we done the ICE song and
|
||||||
// dance is it safest to connect the video tracks / stream
|
// dance is it safest to connect the video tracks / stream
|
||||||
case 'connected':
|
case 'connected':
|
||||||
|
if (this.shouldTrace()) {
|
||||||
|
iceSpan.resolve?.()
|
||||||
|
}
|
||||||
|
|
||||||
// Let the browser attach to the video stream now
|
// Let the browser attach to the video stream now
|
||||||
this.onNewTrack({ conn: this, mediaStream: this.mediaStream! })
|
this.onNewTrack({ conn: this, mediaStream: this.mediaStream! })
|
||||||
break
|
break
|
||||||
@ -383,6 +429,17 @@ class EngineConnection {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.shouldTrace()) {
|
||||||
|
let mediaStreamTrack = mediaStream.getVideoTracks()[0]
|
||||||
|
mediaStreamTrack.addEventListener('unmute', () => {
|
||||||
|
// let settings = mediaStreamTrack.getSettings()
|
||||||
|
// mediaTrackSpan.span.setTag("fps", settings.frameRate)
|
||||||
|
// mediaTrackSpan.span.setTag("width", settings.width)
|
||||||
|
// mediaTrackSpan.span.setTag("height", settings.height)
|
||||||
|
mediaTrackSpan.resolve?.()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
this.webrtcStatsCollector = (): Promise<ClientMetrics> => {
|
this.webrtcStatsCollector = (): Promise<ClientMetrics> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (mediaStream.getVideoTracks().length !== 1) {
|
if (mediaStream.getVideoTracks().length !== 1) {
|
||||||
@ -465,6 +522,10 @@ class EngineConnection {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.shouldTrace()) {
|
||||||
|
dataChannelSpan.resolve?.()
|
||||||
|
}
|
||||||
|
|
||||||
// Everything is now connected.
|
// Everything is now connected.
|
||||||
this.state = { type: EngineConnectionStateType.ConnectionEstablished }
|
this.state = { type: EngineConnectionStateType.ConnectionEstablished }
|
||||||
|
|
||||||
@ -516,6 +577,27 @@ class EngineConnection {
|
|||||||
if (this.token) {
|
if (this.token) {
|
||||||
this.send({ headers: { Authorization: `Bearer ${this.token}` } })
|
this.send({ headers: { Authorization: `Bearer ${this.token}` } })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.shouldTrace()) {
|
||||||
|
websocketSpan.resolve?.()
|
||||||
|
|
||||||
|
handshakeSpan = spanStart('handshake')
|
||||||
|
iceSpan = spanStart('ice')
|
||||||
|
dataChannelSpan = spanStart('data-channel')
|
||||||
|
mediaTrackSpan = spanStart('media-track')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shouldTrace()) {
|
||||||
|
void Promise.all([
|
||||||
|
handshakeSpan.promise,
|
||||||
|
iceSpan.promise,
|
||||||
|
dataChannelSpan.promise,
|
||||||
|
mediaTrackSpan.promise,
|
||||||
|
]).then(() => {
|
||||||
|
console.log('All spans finished, reporting')
|
||||||
|
webrtcMediaTransaction?.finish()
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.websocket.addEventListener('close', (event) => {
|
this.websocket.addEventListener('close', (event) => {
|
||||||
@ -704,6 +786,13 @@ failed cmd type was ${artifactThatFailed?.commandType}`
|
|||||||
type: ConnectingType.WebRTCConnecting,
|
type: ConnectingType.WebRTCConnecting,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.shouldTrace()) {
|
||||||
|
// When both ends have a local and remote SDP, we've been able to
|
||||||
|
// set up successfully. We'll still need to find the right ICE
|
||||||
|
// servers, but this is hand-shook.
|
||||||
|
handshakeSpan.resolve?.()
|
||||||
|
}
|
||||||
break
|
break
|
||||||
|
|
||||||
case 'trickle_ice':
|
case 'trickle_ice':
|
||||||
@ -796,7 +885,7 @@ interface UnreliableSubscription<T extends UnreliableResponses['type']> {
|
|||||||
callback: (data: Extract<UnreliableResponses, { type: T }>) => void
|
callback: (data: Extract<UnreliableResponses, { type: T }>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Subscription<T extends ModelTypes> {
|
interface Subscription<T extends ModelTypes> {
|
||||||
event: T
|
event: T
|
||||||
callback: (
|
callback: (
|
||||||
data: Extract<Models['OkModelingCmdResponse_type'], { type: T }>
|
data: Extract<Models['OkModelingCmdResponse_type'], { type: T }>
|
||||||
@ -926,15 +1015,6 @@ export class EngineCommandManager {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
sceneInfra.camControls.onCameraChange()
|
sceneInfra.camControls.onCameraChange()
|
||||||
this.sendSceneCommand({
|
|
||||||
// CameraControls subscribes to default_camera_get_settings response events
|
|
||||||
// firing this at connection ensure the camera's are synced initially
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'default_camera_get_settings',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
this.initPlanes().then(() => {
|
this.initPlanes().then(() => {
|
||||||
this.resolveReady()
|
this.resolveReady()
|
||||||
|
@ -101,6 +101,7 @@ describe('testing changeSketchArguments', () => {
|
|||||||
|> ${line}
|
|> ${line}
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|
show(mySketch001)
|
||||||
`
|
`
|
||||||
const code = genCode(lineToChange)
|
const code = genCode(lineToChange)
|
||||||
const expectedCode = genCode(lineAfterChange)
|
const expectedCode = genCode(lineAfterChange)
|
||||||
@ -127,7 +128,8 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)`
|
|> lineTo([0.46, -5.82], %)
|
||||||
|
show(mySketch001)`
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const sourceStart = code.indexOf(lineToChange)
|
const sourceStart = code.indexOf(lineToChange)
|
||||||
@ -153,6 +155,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
|> lineTo([2, 3], %)
|
|> lineTo([2, 3], %)
|
||||||
|
show(mySketch001)
|
||||||
`
|
`
|
||||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||||
|
|
||||||
@ -174,6 +177,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|
show(mySketch001)
|
||||||
`
|
`
|
||||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||||
})
|
})
|
||||||
@ -188,6 +192,7 @@ describe('testing addTagForSketchOnFace', () => {
|
|||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> ${line}
|
|> ${line}
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
|
show(mySketch001)
|
||||||
`
|
`
|
||||||
const code = genCode(originalLine)
|
const code = genCode(originalLine)
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
|
@ -91,6 +91,12 @@ export function createFirstArg(
|
|||||||
throw new Error('all sketch line types should have been covered')
|
throw new Error('all sketch line types should have been covered')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
type LineData = {
|
||||||
|
from: [number, number, number]
|
||||||
|
to: [number, number, number]
|
||||||
|
}
|
||||||
|
|
||||||
export const lineTo: SketchLineHelper = {
|
export const lineTo: SketchLineHelper = {
|
||||||
add: ({
|
add: ({
|
||||||
node,
|
node,
|
||||||
@ -175,6 +181,10 @@ export const line: SketchLineHelper = {
|
|||||||
pathToNode,
|
pathToNode,
|
||||||
'PipeExpression'
|
'PipeExpression'
|
||||||
)
|
)
|
||||||
|
const { node: callExpression } = getNodeFromPath<
|
||||||
|
PipeExpression | CallExpression
|
||||||
|
>(_node, pathToNode, 'CallExpression')
|
||||||
|
|
||||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||||
_node,
|
_node,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
@ -184,6 +194,38 @@ export const line: SketchLineHelper = {
|
|||||||
const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
|
const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
|
||||||
const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
|
const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||||
|
|
||||||
|
const isAddingSegmentBetween =
|
||||||
|
callExpression.type === 'CallExpression' &&
|
||||||
|
callExpression.start >= pipe.start &&
|
||||||
|
callExpression.end <= pipe.end
|
||||||
|
if (
|
||||||
|
isAddingSegmentBetween &&
|
||||||
|
!createCallback &&
|
||||||
|
pipe.type === 'PipeExpression'
|
||||||
|
) {
|
||||||
|
const callExp = createCallExpression('line', [
|
||||||
|
createArrayExpression([newXVal, newYVal]),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
])
|
||||||
|
const pathToNodeIndex = pathToNode.findIndex(
|
||||||
|
(x) => x[1] === 'PipeExpression'
|
||||||
|
)
|
||||||
|
const pipeIndex = pathToNode[pathToNodeIndex + 1][0]
|
||||||
|
if (typeof pipeIndex === 'undefined' || typeof pipeIndex === 'string') {
|
||||||
|
console.warn('pipeIndex is undefined')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pipe.body = [
|
||||||
|
...pipe.body.slice(0, pipeIndex),
|
||||||
|
callExp,
|
||||||
|
...pipe.body.slice(pipeIndex),
|
||||||
|
]
|
||||||
|
return {
|
||||||
|
modifiedAst: _node,
|
||||||
|
pathToNode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (replaceExisting && createCallback && pipe.type !== 'CallExpression') {
|
if (replaceExisting && createCallback && pipe.type !== 'CallExpression') {
|
||||||
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
||||||
const { callExp, valueUsedInTransform } = createCallback(
|
const { callExp, valueUsedInTransform } = createCallback(
|
||||||
@ -960,30 +1002,6 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
|||||||
addTag: addTagWithTo('angleTo'), // TODO might be wrong
|
addTag: addTagWithTo('angleTo'), // TODO might be wrong
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
|
|
||||||
node,
|
|
||||||
pathToNode,
|
|
||||||
to,
|
|
||||||
}) => {
|
|
||||||
const _node = { ...node }
|
|
||||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
|
||||||
_node,
|
|
||||||
pathToNode
|
|
||||||
)
|
|
||||||
|
|
||||||
const toArrExp = createArrayExpression([
|
|
||||||
createLiteral(roundOff(to[0])),
|
|
||||||
createLiteral(roundOff(to[1])),
|
|
||||||
])
|
|
||||||
|
|
||||||
mutateArrExp(callExpression.arguments?.[0], toArrExp) ||
|
|
||||||
mutateObjExpProp(callExpression.arguments?.[0], toArrExp, 'to')
|
|
||||||
return {
|
|
||||||
modifiedAst: _node,
|
|
||||||
pathToNode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
|
export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
|
||||||
line,
|
line,
|
||||||
lineTo,
|
lineTo,
|
||||||
@ -1029,15 +1047,6 @@ export function changeSketchArguments(
|
|||||||
throw new Error(`not a sketch line helper: ${callExpression?.callee?.name}`)
|
throw new Error(`not a sketch line helper: ${callExpression?.callee?.name}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateLineFnCallArgs {
|
|
||||||
node: Program
|
|
||||||
programMemory: ProgramMemory
|
|
||||||
to: [number, number]
|
|
||||||
from: [number, number]
|
|
||||||
fnName: ToolTip
|
|
||||||
pathToNode: PathToNode
|
|
||||||
}
|
|
||||||
|
|
||||||
export function compareVec2Epsilon(
|
export function compareVec2Epsilon(
|
||||||
vec1: [number, number],
|
vec1: [number, number],
|
||||||
vec2: [number, number],
|
vec2: [number, number],
|
||||||
@ -1062,6 +1071,15 @@ export function compareVec2Epsilon2(
|
|||||||
return distance < compareEpsilon
|
return distance < compareEpsilon
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateLineFnCallArgs {
|
||||||
|
node: Program
|
||||||
|
programMemory: ProgramMemory
|
||||||
|
to: [number, number]
|
||||||
|
from: [number, number]
|
||||||
|
fnName: ToolTip
|
||||||
|
pathToNode: PathToNode
|
||||||
|
}
|
||||||
|
|
||||||
export function addNewSketchLn({
|
export function addNewSketchLn({
|
||||||
node: _node,
|
node: _node,
|
||||||
programMemory: previousProgramMemory,
|
programMemory: previousProgramMemory,
|
||||||
|
@ -88,6 +88,7 @@ describe('testing swapping out sketch calls with xLine/xLineTo', () => {
|
|||||||
` |> yLine(-1.07, %)`,
|
` |> yLine(-1.07, %)`,
|
||||||
` |> xLineTo(3.27, %)`,
|
` |> xLineTo(3.27, %)`,
|
||||||
` |> yLineTo(2.14, %)`,
|
` |> yLineTo(2.14, %)`,
|
||||||
|
`show(part001)`,
|
||||||
]
|
]
|
||||||
const bigExample = bigExampleArr.join('\n')
|
const bigExample = bigExampleArr.join('\n')
|
||||||
it('line with tag converts to xLine', async () => {
|
it('line with tag converts to xLine', async () => {
|
||||||
@ -289,6 +290,7 @@ describe('testing swapping out sketch calls with xLine/xLineTo while keeping var
|
|||||||
` |> angledLineToX([330, angledLineToXx], %)`,
|
` |> angledLineToX([330, angledLineToXx], %)`,
|
||||||
` |> angledLineToY([217, angledLineToYy], %)`,
|
` |> angledLineToY([217, angledLineToYy], %)`,
|
||||||
` |> line([0.89, -0.1], %)`,
|
` |> line([0.89, -0.1], %)`,
|
||||||
|
`show(part001)`,
|
||||||
]
|
]
|
||||||
const varExample = variablesExampleArr.join('\n')
|
const varExample = variablesExampleArr.join('\n')
|
||||||
it('line keeps variable when converted to xLine', async () => {
|
it('line keeps variable when converted to xLine', async () => {
|
||||||
@ -376,7 +378,8 @@ const part001 = startSketchOn('XY')
|
|||||||
|> line([0, 0.4], %)
|
|> line([0, 0.4], %)
|
||||||
|> xLine(3.48, %)
|
|> xLine(3.48, %)
|
||||||
|> line([2.14, 1.35], %) // normal-segment
|
|> line([2.14, 1.35], %) // normal-segment
|
||||||
|> xLine(3.54, %)`
|
|> xLine(3.54, %)
|
||||||
|
show(part001)`
|
||||||
it('normal case works', async () => {
|
it('normal case works', async () => {
|
||||||
const programMemory = await enginelessExecutor(parse(code))
|
const programMemory = await enginelessExecutor(parse(code))
|
||||||
const index = code.indexOf('// normal-segment') - 7
|
const index = code.indexOf('// normal-segment') - 7
|
||||||
|
@ -123,6 +123,7 @@ const part001 = startSketchOn('XY')
|
|||||||
|> yLine(1.04, %) // ln-yLine-free should sub in segLen
|
|> yLine(1.04, %) // ln-yLine-free should sub in segLen
|
||||||
|> xLineTo(30, %) // ln-xLineTo-free should convert to xLine
|
|> xLineTo(30, %) // ln-xLineTo-free should convert to xLine
|
||||||
|> yLineTo(20, %) // ln-yLineTo-free should convert to yLine
|
|> yLineTo(20, %) // ln-yLineTo-free should convert to yLine
|
||||||
|
show(part001)
|
||||||
`
|
`
|
||||||
const expectModifiedScript = `const myVar = 3
|
const expectModifiedScript = `const myVar = 3
|
||||||
const myVar2 = 5
|
const myVar2 = 5
|
||||||
@ -195,6 +196,7 @@ const part001 = startSketchOn('XY')
|
|||||||
|> yLine(segLen('seg01', %), %) // ln-yLine-free should sub in segLen
|
|> yLine(segLen('seg01', %), %) // ln-yLine-free should sub in segLen
|
||||||
|> xLine(segLen('seg01', %), %) // ln-xLineTo-free should convert to xLine
|
|> xLine(segLen('seg01', %), %) // ln-xLineTo-free should convert to xLine
|
||||||
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
|
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
|
||||||
|
show(part001)
|
||||||
`
|
`
|
||||||
it('should transform the ast', async () => {
|
it('should transform the ast', async () => {
|
||||||
const ast = parse(inputScript)
|
const ast = parse(inputScript)
|
||||||
@ -255,6 +257,7 @@ const part001 = startSketchOn('XY')
|
|||||||
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|
||||||
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
||||||
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
||||||
|
show(part001)
|
||||||
`
|
`
|
||||||
it('should transform horizontal lines the ast', async () => {
|
it('should transform horizontal lines the ast', async () => {
|
||||||
const expectModifiedScript = `const myVar = 2
|
const expectModifiedScript = `const myVar = 2
|
||||||
@ -283,6 +286,7 @@ const part001 = startSketchOn('XY')
|
|||||||
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|
||||||
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|
||||||
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
||||||
|
show(part001)
|
||||||
`
|
`
|
||||||
const ast = parse(inputScript)
|
const ast = parse(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
@ -341,6 +345,7 @@ const part001 = startSketchOn('XY')
|
|||||||
|> yLineTo(7.68, %) // select for vertical constraint 9
|
|> yLineTo(7.68, %) // select for vertical constraint 9
|
||||||
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|
||||||
|> yLineTo(myVar, %) // select for vertical constraint 10
|
|> yLineTo(myVar, %) // select for vertical constraint 10
|
||||||
|
show(part001)
|
||||||
`
|
`
|
||||||
const ast = parse(inputScript)
|
const ast = parse(inputScript)
|
||||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||||
@ -384,6 +389,7 @@ const part001 = startSketchOn('XY')
|
|||||||
|> line([0.45, 1.46], %) // free
|
|> line([0.45, 1.46], %) // free
|
||||||
|> line([myVar, 0.01], %) // xRelative
|
|> line([myVar, 0.01], %) // xRelative
|
||||||
|> line([0.7, myVar], %) // yRelative
|
|> line([0.7, myVar], %) // yRelative
|
||||||
|
show(part001)
|
||||||
`
|
`
|
||||||
it('testing for free to horizontal and vertical distance', async () => {
|
it('testing for free to horizontal and vertical distance', async () => {
|
||||||
const expectedHorizontalCode = await helperThing(
|
const expectedHorizontalCode = await helperThing(
|
||||||
@ -495,7 +501,8 @@ const part001 = startSketchOn('XY')
|
|||||||
|> xLine(3.36, %) // partial
|
|> xLine(3.36, %) // partial
|
||||||
|> line([-1.49, 1.06], %) // free
|
|> line([-1.49, 1.06], %) // free
|
||||||
|> xLine(-3.43 + 0, %) // full
|
|> xLine(-3.43 + 0, %) // full
|
||||||
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full`
|
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full
|
||||||
|
show(part001)`
|
||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
const constraintLevels: ReturnType<
|
const constraintLevels: ReturnType<
|
||||||
typeof getConstraintLevelFromSourceRange
|
typeof getConstraintLevelFromSourceRange
|
||||||
|
@ -15,7 +15,8 @@ describe('testing angledLineThatIntersects', () => {
|
|||||||
offset: ${offset},
|
offset: ${offset},
|
||||||
tag: "yo2"
|
tag: "yo2"
|
||||||
}, %)
|
}, %)
|
||||||
const intersect = segEndX('yo2', part001)`
|
const intersect = segEndX('yo2', part001)
|
||||||
|
show(part001)`
|
||||||
const { root } = await enginelessExecutor(parse(code('-1')))
|
const { root } = await enginelessExecutor(parse(code('-1')))
|
||||||
expect(root.intersect.value).toBe(1 + Math.sqrt(2))
|
expect(root.intersect.value).toBe(1 + Math.sqrt(2))
|
||||||
const { root: noOffset } = await enginelessExecutor(parse(code('0')))
|
const { root: noOffset } = await enginelessExecutor(parse(code('0')))
|
||||||
|
@ -40,9 +40,9 @@ export interface MouseGuard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const butName = (e: React.MouseEvent) => ({
|
const butName = (e: React.MouseEvent) => ({
|
||||||
middle: !!(e.buttons & 4) || e.button === 1,
|
middle: !!(e.buttons & 4),
|
||||||
right: !!(e.buttons & 2) || e.button === 2,
|
right: !!(e.buttons & 2),
|
||||||
left: !!(e.buttons & 1) || e.button === 0,
|
left: !!(e.buttons & 1),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||||
|
@ -28,8 +28,7 @@ export const homeCommandBarConfig: CommandSetConfig<
|
|||||||
name: {
|
name: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
required: true,
|
required: true,
|
||||||
options: [],
|
options: (context) =>
|
||||||
optionsFromContext: (context) =>
|
|
||||||
context.projects.map((p) => ({
|
context.projects.map((p) => ({
|
||||||
name: p.name!,
|
name: p.name!,
|
||||||
value: p.name!,
|
value: p.name!,
|
||||||
@ -44,7 +43,7 @@ export const homeCommandBarConfig: CommandSetConfig<
|
|||||||
name: {
|
name: {
|
||||||
inputType: 'string',
|
inputType: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
defaultValueFromContext: (context) => context.defaultProjectName,
|
defaultValue: (context) => context.defaultProjectName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -56,8 +55,7 @@ export const homeCommandBarConfig: CommandSetConfig<
|
|||||||
name: {
|
name: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
required: true,
|
required: true,
|
||||||
options: [],
|
options: (context) =>
|
||||||
optionsFromContext: (context) =>
|
|
||||||
context.projects.map((p) => ({
|
context.projects.map((p) => ({
|
||||||
name: p.name!,
|
name: p.name!,
|
||||||
value: p.name!,
|
value: p.name!,
|
||||||
@ -73,8 +71,7 @@ export const homeCommandBarConfig: CommandSetConfig<
|
|||||||
oldName: {
|
oldName: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
required: true,
|
required: true,
|
||||||
options: [],
|
options: (context) =>
|
||||||
optionsFromContext: (context) =>
|
|
||||||
context.projects.map((p) => ({
|
context.projects.map((p) => ({
|
||||||
name: p.name!,
|
name: p.name!,
|
||||||
value: p.name!,
|
value: p.name!,
|
||||||
@ -83,7 +80,7 @@ export const homeCommandBarConfig: CommandSetConfig<
|
|||||||
newName: {
|
newName: {
|
||||||
inputType: 'string',
|
inputType: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
defaultValueFromContext: (context) => context.defaultProjectName,
|
defaultValue: (context) => context.defaultProjectName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
import { Models } from '@kittycad/lib'
|
|
||||||
import { CommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
import { CommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { modelingMachine } from 'machines/modelingMachine'
|
import { modelingMachine } from 'machines/modelingMachine'
|
||||||
|
|
||||||
type OutputFormat = Models['OutputFormat_type']
|
|
||||||
type OutputTypeKey = OutputFormat['type']
|
|
||||||
type ExtractStorageTypes<T> = T extends { storage: infer U } ? U : never
|
|
||||||
type StorageUnion = ExtractStorageTypes<OutputFormat>
|
|
||||||
|
|
||||||
export const EXTRUSION_RESULTS = [
|
export const EXTRUSION_RESULTS = [
|
||||||
'new',
|
'new',
|
||||||
'add',
|
'add',
|
||||||
@ -17,10 +11,6 @@ export const EXTRUSION_RESULTS = [
|
|||||||
|
|
||||||
export type ModelingCommandSchema = {
|
export type ModelingCommandSchema = {
|
||||||
'Enter sketch': {}
|
'Enter sketch': {}
|
||||||
Export: {
|
|
||||||
type: OutputTypeKey
|
|
||||||
storage?: StorageUnion
|
|
||||||
}
|
|
||||||
Extrude: {
|
Extrude: {
|
||||||
selection: Selections // & { type: 'face' } would be cool to lock that down
|
selection: Selections // & { type: 'face' } would be cool to lock that down
|
||||||
// result: (typeof EXTRUSION_RESULTS)[number]
|
// result: (typeof EXTRUSION_RESULTS)[number]
|
||||||
@ -36,80 +26,6 @@ export const modelingMachineConfig: CommandSetConfig<
|
|||||||
description: 'Enter sketch mode.',
|
description: 'Enter sketch mode.',
|
||||||
icon: 'sketch',
|
icon: 'sketch',
|
||||||
},
|
},
|
||||||
Export: {
|
|
||||||
description: 'Export the current model.',
|
|
||||||
icon: 'exportFile',
|
|
||||||
needsReview: true,
|
|
||||||
args: {
|
|
||||||
type: {
|
|
||||||
inputType: 'options',
|
|
||||||
defaultValue: 'gltf',
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
{ name: 'gLTF', isCurrent: true, value: 'gltf' },
|
|
||||||
{ name: 'OBJ', isCurrent: false, value: 'obj' },
|
|
||||||
{ name: 'STL', isCurrent: false, value: 'stl' },
|
|
||||||
{ name: 'STEP', isCurrent: false, value: 'step' },
|
|
||||||
{ name: 'PLY', isCurrent: false, value: 'ply' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
storage: {
|
|
||||||
inputType: 'options',
|
|
||||||
defaultValue: (c) => {
|
|
||||||
switch (c.argumentsToSubmit.type) {
|
|
||||||
case 'gltf':
|
|
||||||
return 'embedded'
|
|
||||||
case 'stl':
|
|
||||||
return 'ascii'
|
|
||||||
case 'ply':
|
|
||||||
return 'ascii'
|
|
||||||
default:
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
skip: true,
|
|
||||||
required: (commandContext) =>
|
|
||||||
['gltf', 'stl', 'ply'].includes(
|
|
||||||
commandContext.argumentsToSubmit.type as string
|
|
||||||
),
|
|
||||||
options: (commandContext) => {
|
|
||||||
const type = commandContext.argumentsToSubmit.type as
|
|
||||||
| OutputTypeKey
|
|
||||||
| undefined
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'gltf':
|
|
||||||
return [
|
|
||||||
{ name: 'embedded', isCurrent: true, value: 'embedded' },
|
|
||||||
{ name: 'binary', isCurrent: false, value: 'binary' },
|
|
||||||
{ name: 'standard', isCurrent: false, value: 'standard' },
|
|
||||||
]
|
|
||||||
case 'stl':
|
|
||||||
return [
|
|
||||||
{ name: 'binary', isCurrent: false, value: 'binary' },
|
|
||||||
{ name: 'ascii', isCurrent: true, value: 'ascii' },
|
|
||||||
]
|
|
||||||
case 'ply':
|
|
||||||
return [
|
|
||||||
{ name: 'ascii', isCurrent: true, value: 'ascii' },
|
|
||||||
{
|
|
||||||
name: 'binary_big_endian',
|
|
||||||
isCurrent: false,
|
|
||||||
value: 'binary_big_endian',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'binary_little_endian',
|
|
||||||
isCurrent: false,
|
|
||||||
value: 'binary_little_endian',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
default:
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Extrude: {
|
Extrude: {
|
||||||
description: 'Pull a sketch into 3D along its normal or perpendicular.',
|
description: 'Pull a sketch into 3D along its normal or perpendicular.',
|
||||||
icon: 'extrude',
|
icon: 'extrude',
|
||||||
|
@ -41,9 +41,8 @@ export const settingsCommandBarConfig: CommandSetConfig<
|
|||||||
baseUnit: {
|
baseUnit: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
required: true,
|
required: true,
|
||||||
defaultValueFromContext: (context) => context.baseUnit,
|
defaultValue: (context) => context.baseUnit,
|
||||||
options: [],
|
options: (context) =>
|
||||||
optionsFromContext: (context) =>
|
|
||||||
Object.values(baseUnitsUnion).map((v) => ({
|
Object.values(baseUnitsUnion).map((v) => ({
|
||||||
name: v,
|
name: v,
|
||||||
value: v,
|
value: v,
|
||||||
@ -58,9 +57,8 @@ export const settingsCommandBarConfig: CommandSetConfig<
|
|||||||
cameraControls: {
|
cameraControls: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
required: true,
|
required: true,
|
||||||
defaultValueFromContext: (context) => context.cameraControls,
|
defaultValue: (context) => context.cameraControls,
|
||||||
options: [],
|
options: (context) =>
|
||||||
optionsFromContext: (context) =>
|
|
||||||
Object.values(cameraSystems).map((v) => ({
|
Object.values(cameraSystems).map((v) => ({
|
||||||
name: v,
|
name: v,
|
||||||
value: v,
|
value: v,
|
||||||
@ -76,7 +74,7 @@ export const settingsCommandBarConfig: CommandSetConfig<
|
|||||||
defaultProjectName: {
|
defaultProjectName: {
|
||||||
inputType: 'string',
|
inputType: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
defaultValueFromContext: (context) => context.defaultProjectName,
|
defaultValue: (context) => context.defaultProjectName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -86,9 +84,8 @@ export const settingsCommandBarConfig: CommandSetConfig<
|
|||||||
textWrapping: {
|
textWrapping: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
required: true,
|
required: true,
|
||||||
defaultValueFromContext: (context) => context.textWrapping,
|
defaultValue: (context) => context.textWrapping,
|
||||||
options: [],
|
options: (context) => [
|
||||||
optionsFromContext: (context) => [
|
|
||||||
{
|
{
|
||||||
name: 'On',
|
name: 'On',
|
||||||
value: 'On' as Toggle,
|
value: 'On' as Toggle,
|
||||||
@ -109,9 +106,8 @@ export const settingsCommandBarConfig: CommandSetConfig<
|
|||||||
theme: {
|
theme: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
required: true,
|
required: true,
|
||||||
defaultValueFromContext: (context) => context.theme,
|
defaultValue: (context) => context.theme,
|
||||||
options: [],
|
options: (context) =>
|
||||||
optionsFromContext: (context) =>
|
|
||||||
Object.values(Themes).map((v) => ({
|
Object.values(Themes).map((v) => ({
|
||||||
name: v,
|
name: v,
|
||||||
value: v,
|
value: v,
|
||||||
@ -126,9 +122,8 @@ export const settingsCommandBarConfig: CommandSetConfig<
|
|||||||
unitSystem: {
|
unitSystem: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
required: true,
|
required: true,
|
||||||
defaultValueFromContext: (context) => context.unitSystem,
|
defaultValue: (context) => context.unitSystem,
|
||||||
options: [],
|
options: (context) => [
|
||||||
optionsFromContext: (context) => [
|
|
||||||
{
|
{
|
||||||
name: 'Imperial',
|
name: 'Imperial',
|
||||||
value: 'imperial' as UnitSystem,
|
value: 'imperial' as UnitSystem,
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
} from 'xstate'
|
} from 'xstate'
|
||||||
import { Selection } from './selections'
|
import { Selection } from './selections'
|
||||||
import { Identifier, Value, VariableDeclaration } from 'lang/wasm'
|
import { Identifier, Value, VariableDeclaration } from 'lang/wasm'
|
||||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
|
||||||
|
|
||||||
type Icon = CustomIconName
|
type Icon = CustomIconName
|
||||||
const PLATFORMS = ['both', 'web', 'desktop'] as const
|
const PLATFORMS = ['both', 'web', 'desktop'] as const
|
||||||
@ -94,31 +93,15 @@ export type CommandArgumentConfig<
|
|||||||
> =
|
> =
|
||||||
| {
|
| {
|
||||||
description?: string
|
description?: string
|
||||||
required:
|
required: boolean
|
||||||
| boolean
|
skip?: true
|
||||||
| ((
|
|
||||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> } // Should be the commandbarMachine's context, but it creates a circular dependency
|
|
||||||
) => boolean)
|
|
||||||
skip?: boolean
|
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
inputType: Extract<CommandInputType, 'options'>
|
inputType: Extract<CommandInputType, 'options'>
|
||||||
options:
|
options:
|
||||||
| CommandArgumentOption<OutputType>[]
|
| CommandArgumentOption<OutputType>[]
|
||||||
| ((
|
| ((context: ContextFrom<T>) => CommandArgumentOption<OutputType>[])
|
||||||
commandBarContext: {
|
defaultValue?: OutputType | ((context: ContextFrom<T>) => OutputType)
|
||||||
argumentsToSubmit: Record<string, unknown>
|
|
||||||
} // Should be the commandbarMachine's context, but it creates a circular dependency
|
|
||||||
) => CommandArgumentOption<OutputType>[])
|
|
||||||
optionsFromContext?: (
|
|
||||||
context: ContextFrom<T>
|
|
||||||
) => CommandArgumentOption<OutputType>[]
|
|
||||||
defaultValue?:
|
|
||||||
| OutputType
|
|
||||||
| ((
|
|
||||||
commandBarContext: ContextFrom<typeof commandBarMachine>
|
|
||||||
) => OutputType)
|
|
||||||
defaultValueFromContext?: (context: ContextFrom<T>) => OutputType
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
inputType: Extract<CommandInputType, 'selection'>
|
inputType: Extract<CommandInputType, 'selection'>
|
||||||
@ -128,12 +111,7 @@ export type CommandArgumentConfig<
|
|||||||
| { inputType: Extract<CommandInputType, 'kcl'>; defaultValue?: string } // KCL expression inputs have simple strings as default values
|
| { inputType: Extract<CommandInputType, 'kcl'>; defaultValue?: string } // KCL expression inputs have simple strings as default values
|
||||||
| {
|
| {
|
||||||
inputType: Extract<CommandInputType, 'string'>
|
inputType: Extract<CommandInputType, 'string'>
|
||||||
defaultValue?:
|
defaultValue?: OutputType | ((context: ContextFrom<T>) => OutputType)
|
||||||
| OutputType
|
|
||||||
| ((
|
|
||||||
commandBarContext: ContextFrom<typeof commandBarMachine>
|
|
||||||
) => OutputType)
|
|
||||||
defaultValueFromContext?: (context: ContextFrom<T>) => OutputType
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -143,42 +121,24 @@ export type CommandArgument<
|
|||||||
> =
|
> =
|
||||||
| {
|
| {
|
||||||
description?: string
|
description?: string
|
||||||
required:
|
required: boolean
|
||||||
| boolean
|
skip?: true
|
||||||
| ((
|
|
||||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> } // Should be the commandbarMachine's context, but it creates a circular dependency
|
|
||||||
) => boolean)
|
|
||||||
skip?: boolean
|
|
||||||
machineActor: InterpreterFrom<T>
|
|
||||||
} & (
|
} & (
|
||||||
| {
|
| {
|
||||||
inputType: Extract<CommandInputType, 'options'>
|
inputType: Extract<CommandInputType, 'options'>
|
||||||
options:
|
options: CommandArgumentOption<OutputType>[]
|
||||||
| CommandArgumentOption<OutputType>[]
|
defaultValue?: OutputType
|
||||||
| ((
|
|
||||||
commandBarContext: {
|
|
||||||
argumentsToSubmit: Record<string, unknown>
|
|
||||||
} // Should be the commandbarMachine's context, but it creates a circular dependency
|
|
||||||
) => CommandArgumentOption<OutputType>[])
|
|
||||||
defaultValue?:
|
|
||||||
| OutputType
|
|
||||||
| ((
|
|
||||||
commandBarContext: ContextFrom<typeof commandBarMachine>
|
|
||||||
) => OutputType)
|
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
inputType: Extract<CommandInputType, 'selection'>
|
inputType: Extract<CommandInputType, 'selection'>
|
||||||
selectionTypes: Selection['type'][]
|
selectionTypes: Selection['type'][]
|
||||||
|
actor: InterpreterFrom<T>
|
||||||
multiple: boolean
|
multiple: boolean
|
||||||
}
|
}
|
||||||
| { inputType: Extract<CommandInputType, 'kcl'>; defaultValue?: string } // KCL expression inputs have simple strings as default values
|
| { inputType: Extract<CommandInputType, 'kcl'>; defaultValue?: string } // KCL expression inputs have simple strings as default values
|
||||||
| {
|
| {
|
||||||
inputType: Extract<CommandInputType, 'string'>
|
inputType: Extract<CommandInputType, 'string'>
|
||||||
defaultValue?:
|
defaultValue?: OutputType
|
||||||
| OutputType
|
|
||||||
| ((
|
|
||||||
commandBarContext: ContextFrom<typeof commandBarMachine>
|
|
||||||
) => OutputType)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ interface CreateMachineCommandProps<
|
|||||||
ownerMachine: T['id']
|
ownerMachine: T['id']
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
send: Function
|
send: Function
|
||||||
actor: InterpreterFrom<T>
|
actor?: InterpreterFrom<T>
|
||||||
commandBarConfig?: CommandSetConfig<T, S>
|
commandBarConfig?: CommandSetConfig<T, S>
|
||||||
onCancel?: () => void
|
onCancel?: () => void
|
||||||
}
|
}
|
||||||
@ -91,13 +91,13 @@ function buildCommandArguments<
|
|||||||
>(
|
>(
|
||||||
state: StateFrom<T>,
|
state: StateFrom<T>,
|
||||||
args: CommandConfig<T, CommandName, S>['args'],
|
args: CommandConfig<T, CommandName, S>['args'],
|
||||||
machineActor: InterpreterFrom<T>
|
actor?: InterpreterFrom<T>
|
||||||
): NonNullable<Command<T, CommandName, S>['args']> {
|
): NonNullable<Command<T, CommandName, S>['args']> {
|
||||||
const newArgs = {} as NonNullable<Command<T, CommandName, S>['args']>
|
const newArgs = {} as NonNullable<Command<T, CommandName, S>['args']>
|
||||||
|
|
||||||
for (const arg in args) {
|
for (const arg in args) {
|
||||||
const argConfig = args[arg] as CommandArgumentConfig<S[typeof arg], T>
|
const argConfig = args[arg] as CommandArgumentConfig<S[typeof arg], T>
|
||||||
const newArg = buildCommandArgument(argConfig, arg, state, machineActor)
|
const newArg = buildCommandArgument(argConfig, arg, state, actor)
|
||||||
newArgs[arg] = newArg
|
newArgs[arg] = newArg
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,36 +111,44 @@ function buildCommandArgument<
|
|||||||
arg: CommandArgumentConfig<O, T>,
|
arg: CommandArgumentConfig<O, T>,
|
||||||
argName: string,
|
argName: string,
|
||||||
state: StateFrom<T>,
|
state: StateFrom<T>,
|
||||||
machineActor: InterpreterFrom<T>
|
actor?: InterpreterFrom<T>
|
||||||
): CommandArgument<O, T> & { inputType: typeof arg.inputType } {
|
): CommandArgument<O, T> & { inputType: typeof arg.inputType } {
|
||||||
const baseCommandArgument = {
|
const baseCommandArgument = {
|
||||||
description: arg.description,
|
description: arg.description,
|
||||||
required: arg.required,
|
required: arg.required,
|
||||||
skip: arg.skip,
|
skip: arg.skip,
|
||||||
machineActor,
|
|
||||||
} satisfies Omit<CommandArgument<O, T>, 'inputType'>
|
} satisfies Omit<CommandArgument<O, T>, 'inputType'>
|
||||||
|
|
||||||
if (arg.inputType === 'options') {
|
if (arg.inputType === 'options') {
|
||||||
if (!arg.options) {
|
const options = arg.options
|
||||||
|
? arg.options instanceof Function
|
||||||
|
? arg.options(state.context)
|
||||||
|
: arg.options
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (!options) {
|
||||||
throw new Error('Options must be provided for options input type')
|
throw new Error('Options must be provided for options input type')
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputType: arg.inputType,
|
inputType: arg.inputType,
|
||||||
...baseCommandArgument,
|
...baseCommandArgument,
|
||||||
defaultValue: arg.defaultValueFromContext
|
defaultValue:
|
||||||
? arg.defaultValueFromContext(state.context)
|
arg.defaultValue instanceof Function
|
||||||
|
? arg.defaultValue(state.context)
|
||||||
: arg.defaultValue,
|
: arg.defaultValue,
|
||||||
options: arg.optionsFromContext
|
options,
|
||||||
? arg.optionsFromContext(state.context)
|
|
||||||
: arg.options,
|
|
||||||
} satisfies CommandArgument<O, T> & { inputType: 'options' }
|
} satisfies CommandArgument<O, T> & { inputType: 'options' }
|
||||||
} else if (arg.inputType === 'selection') {
|
} else if (arg.inputType === 'selection') {
|
||||||
|
if (!actor)
|
||||||
|
throw new Error('Actor must be provided for selection input type')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputType: arg.inputType,
|
inputType: arg.inputType,
|
||||||
...baseCommandArgument,
|
...baseCommandArgument,
|
||||||
multiple: arg.multiple,
|
multiple: arg.multiple,
|
||||||
selectionTypes: arg.selectionTypes,
|
selectionTypes: arg.selectionTypes,
|
||||||
|
actor,
|
||||||
} satisfies CommandArgument<O, T> & { inputType: 'selection' }
|
} satisfies CommandArgument<O, T> & { inputType: 'selection' }
|
||||||
} else if (arg.inputType === 'kcl') {
|
} else if (arg.inputType === 'kcl') {
|
||||||
return {
|
return {
|
||||||
@ -151,7 +159,10 @@ function buildCommandArgument<
|
|||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
inputType: arg.inputType,
|
inputType: arg.inputType,
|
||||||
defaultValue: arg.defaultValue,
|
defaultValue:
|
||||||
|
arg.defaultValue instanceof Function
|
||||||
|
? arg.defaultValue(state.context)
|
||||||
|
: arg.defaultValue,
|
||||||
...baseCommandArgument,
|
...baseCommandArgument,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { engineCommandManager } from 'lang/std/engineConnection'
|
|
||||||
import { type Models } from '@kittycad/lib'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
|
|
||||||
// Isolating a function to call the engine to export the current scene.
|
|
||||||
// Because it has given us trouble in automated testing environments.
|
|
||||||
export function exportFromEngine({
|
|
||||||
source_unit,
|
|
||||||
format,
|
|
||||||
}: {
|
|
||||||
source_unit: Models['UnitLength_type']
|
|
||||||
format: Models['OutputFormat_type']
|
|
||||||
}) {
|
|
||||||
return engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'export',
|
|
||||||
// By default let's leave this blank to export the whole scene.
|
|
||||||
// In the future we might want to let the user choose which entities
|
|
||||||
// in the scene to export. In that case, you'd pass the IDs thru here.
|
|
||||||
entity_ids: [],
|
|
||||||
format,
|
|
||||||
source_unit,
|
|
||||||
},
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
})
|
|
||||||
}
|
|
@ -20,7 +20,6 @@ import {
|
|||||||
TANGENTIAL_ARC_TO_SEGMENT,
|
TANGENTIAL_ARC_TO_SEGMENT,
|
||||||
sceneEntitiesManager,
|
sceneEntitiesManager,
|
||||||
getParentGroup,
|
getParentGroup,
|
||||||
PROFILE_START,
|
|
||||||
} from 'clientSideScene/sceneEntities'
|
} from 'clientSideScene/sceneEntities'
|
||||||
import { Mesh } from 'three'
|
import { Mesh } from 'three'
|
||||||
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
|
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
|
||||||
@ -189,11 +188,7 @@ export async function getEventForSelectWithPoint(
|
|||||||
export function getEventForSegmentSelection(
|
export function getEventForSegmentSelection(
|
||||||
obj: any
|
obj: any
|
||||||
): ModelingMachineEvent | null {
|
): ModelingMachineEvent | null {
|
||||||
const group = getParentGroup(obj, [
|
const group = getParentGroup(obj)
|
||||||
STRAIGHT_SEGMENT,
|
|
||||||
TANGENTIAL_ARC_TO_SEGMENT,
|
|
||||||
PROFILE_START,
|
|
||||||
])
|
|
||||||
const axisGroup = getParentGroup(obj, [AXIS_GROUP])
|
const axisGroup = getParentGroup(obj, [AXIS_GROUP])
|
||||||
if (!group && !axisGroup) return null
|
if (!group && !axisGroup) return null
|
||||||
if (axisGroup?.userData.type === AXIS_GROUP) {
|
if (axisGroup?.userData.type === AXIS_GROUP) {
|
||||||
@ -412,8 +407,8 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
|||||||
}
|
}
|
||||||
Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => {
|
Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => {
|
||||||
if (
|
if (
|
||||||
![STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, PROFILE_START].includes(
|
![STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT].includes(
|
||||||
segmentGroup?.name
|
segmentGroup?.userData?.type
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
@ -425,9 +420,7 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
|||||||
const groupHasCursor = codeBasedSelections.some((selection) => {
|
const groupHasCursor = codeBasedSelections.some((selection) => {
|
||||||
return isOverlap(selection.range, [node.start, node.end])
|
return isOverlap(selection.range, [node.start, node.end])
|
||||||
})
|
})
|
||||||
const color = groupHasCursor
|
const color = groupHasCursor ? 0x0000ff : 0xffffff
|
||||||
? 0x0000ff
|
|
||||||
: segmentGroup?.userData?.baseColor || 0xffffff
|
|
||||||
segmentGroup.traverse(
|
segmentGroup.traverse(
|
||||||
(child) => child instanceof Mesh && child.material.color.set(color)
|
(child) => child instanceof Mesh && child.material.color.set(color)
|
||||||
)
|
)
|
||||||
|
@ -15,16 +15,7 @@ export const FILE_EXT = '.kcl'
|
|||||||
export const PROJECT_ENTRYPOINT = 'main' + FILE_EXT
|
export const PROJECT_ENTRYPOINT = 'main' + FILE_EXT
|
||||||
const INDEX_IDENTIFIER = '$n' // $nn.. will pad the number with 0s
|
const INDEX_IDENTIFIER = '$n' // $nn.. will pad the number with 0s
|
||||||
export const MAX_PADDING = 7
|
export const MAX_PADDING = 7
|
||||||
const RELEVANT_FILE_TYPES = [
|
const RELEVANT_FILE_TYPES = ['kcl']
|
||||||
'kcl',
|
|
||||||
'fbx',
|
|
||||||
'gltf',
|
|
||||||
'glb',
|
|
||||||
'obj',
|
|
||||||
'ply',
|
|
||||||
'step',
|
|
||||||
'stl',
|
|
||||||
]
|
|
||||||
|
|
||||||
// Initializes the project directory and returns the path
|
// Initializes the project directory and returns the path
|
||||||
export async function initializeProjectDirectory(directory: string) {
|
export async function initializeProjectDirectory(directory: string) {
|
||||||
|
@ -20,8 +20,6 @@ const LOCAL_USER: Models['User_type'] = {
|
|||||||
phone: '555-555-5555',
|
phone: '555-555-5555',
|
||||||
first_name: 'Test',
|
first_name: 'Test',
|
||||||
last_name: 'User',
|
last_name: 'User',
|
||||||
can_train_on_data: false,
|
|
||||||
is_service_account: false,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserContext {
|
export interface UserContext {
|
||||||
|
@ -8,29 +8,21 @@ import {
|
|||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
|
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
|
||||||
|
|
||||||
export type CommandBarContext = {
|
|
||||||
commands: Command[]
|
|
||||||
selectedCommand?: Command
|
|
||||||
currentArgument?: CommandArgument<unknown> & { name: string }
|
|
||||||
selectionRanges: Selections
|
|
||||||
argumentsToSubmit: { [x: string]: unknown }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const commandBarMachine = createMachine(
|
export const commandBarMachine = createMachine(
|
||||||
{
|
{
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswZJbPltM0U3eXLZAsRFeQcrerUHRbTFvfkmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUilkskqdiUWjWCAcDC0kjUqnElkc6lkoK0JzOT0CnWo1zIAEEIARvn48LA-uwuIC4qAEqIHJjJPJcYtxFoYdJFFjVsZEG5pqCquJxJoGvJxOUCT82sSrj0AEpgdCoABuYC+yog9OYIyZsQmYmhWzMmTqLnU0kyikRGVRbj5lg2SmUam5StpFxIkgAymBXh8HlADQGyKHw58aecGZEzUDWYgchY7CisWoSlpQQ5EVDLJJxKkdIpyypUop-ed2kHCW0Xu9uD1kwDzcCEJCtlUNgWhZZ1OlnaKEBpZJItHVzDZ5xlpPWiZdDc8w22Ow5wozosyLUiVNMSg5ytVKmfrIipzO564z2otPVpI1vKd18SAOJYbgACzAEhI3wUgoAAV3QQZuFgCg-1wGAvjAkgSCgkCSHAyCcG4TtUxZYRED2TQZGSOw80qaQ7ARCc9mmZYSksextGkNJBRXFUOh-f9AOA0CIKgmCABE4E3D5oyTE1-lww8clKBjZF2Bh5SFZwXUUyRVDzJwNE2LQzzYwNJE4gCgJwKNeMw6DJHJAB3LAYlM-AHjYMDuFjMCACN0B4NCMKgnD927dMkWSUs8yY8RISfWQshvcsrDlapshULJPHfZsDKM7iHPM-jJAANSwShOAgX9IzICB+DASQHh1VAAGsqp1Qrit-MByXQvisP8sY8ISZwbCsDllBLSwz1qRFBQsRZ5WRIVkui-TG0M39jJ4jqLNgfLmpK3hTLIQCSFQIM2EoX8ADMjvQSQmqKna2vWvyJL3HrD1SEoyzSdJUkUm9dnUtQn10aK6hkxbiU1HVODAay3M87zE1+J6UwCtN8IQUauR0OZ0ksFwUUqRFPWmUdouPXR3TBjoIahmHKQIHKuqRrtUYSaVShhLF+TkeTzBFQosVKDRM2RVInEdSmg2p6GyE1bU9R8zrsKZqSexFqQHWlHNXHzCbFhnX1+UydFkVxiXJClmGAFEIG8hmld3ZGXp7TR5C5RSCxdjlQV1tR9bqaVdhsSFxDN5AALeOrgPa3ysJgiqcCqmr6sa7bWujxXjQd5nesQZYthsblIQYJx6hUic9AsdEFT2HQzByVLmgDJaw-eSOHPTjbysq6qcFqhrrtT9sO-4ugd1NFGc4QXEPo5eVLGsFxlnKREXGnZI9HcT1mOikO0s-S5w7bqNh9j-bkKOyQTvOy6B9utOHtj7qD1V8pSyrHJZ3STY4QmjQ0R2Ww0oSjuF3u+HAqAIBwEEOlRs48nZBVENkKQNprC42qBoJ0LothaXMJ9aElRXDLj3k3VUpJIBwOfgg4oMx8y41SPUOY8p5DFk2GiKoxsoRVmhGoM2cY2zAQRngChgU0a7HZtFH0ilRp1D5usVh6JXCKD2CUdI8lQ7rlbB8chkkJ6HkxFsBeulbQZAUCwrYCiqzIhyE+fqZtMomTMg-aCwiWYEV2NOBUCghQ6EFGzYsvpwQUT5MxawFECx2JWllRxMdLI2TsrtKMTkXIuMnmeCiZYwrePMFWCKv1XZKVGroWwmxNARK4g4hWG0tp3wSSk16lQpC41ULjV8uxUjSBvFCNEDTdCOkWCY44xCGzgzAJDaGdTnaZHBADYcqg5DpCovzeRno5izA0BeUOh8o5OPgDo+BaNvqV0xNFaE7gdYTnnvkmUChdKEMyF4LwQA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAJwA6AGwAmAKwBmBoukAWafIAcDcSoA0IAJ6JZDaZIDs8hgzV6AjA61a1DWQF8PhtJlwFiciowUkYWJBAOWB4+AQiRBFF5CwdpcVkHS1lpVyU5QxNEh1lFGTUsrUUtOQd5SwZLLx8MbDwiUkkqGkgyAHk2MBwwwSiY-kEEswdJNWcNbPFLNMr5AsRFWUtJcVSdRR2VVMUmkF9WgI6u2ggyADFONv98WkowAGNufDeW-2GI0d443iiHESkktUUG1klTsSi0awQDgYWhmqkWjnUslBWhOZyegU61GuZAAghACN8-HhYH92FxAXFQAlRA5FpJ5FjFPYtNDpIpLOkEW4GNs4eJxJoGvJxOVcT82gSrj0AEpgdCoABuYC+8ogNOYI3psQmYlkGUkU2slhc6mkmUUCIyKLc4i0lkU8iUyjUHLlVIuJEkAGUwK8Pg8oDr-WQQ2HPpTzrTIkagUzEDkLHZkQK1CUtKCHAiNlsdjkVAdFEc-ed2oG8W0Xu9uD0kwDjcCEJDplUPfn+Ut7CUhXJJFo6uYbBOMtJq-jLrrnqGmy2HOE6dEGSbESoRSUHOVqpV99Zh7JR+PXPu1G7zI1vKcFwSAOJYbgACzAJAj+FIUAAruggzcLAFBvrgMBfH+JAkEBP4kP+gE4NwrYpoywiIA4qjyDIyR2LmlTSHY8LGBhyjsrm-L2No0hpHys4Kh0L7vp+36-gBQEgQAInAS4fFGiYGv8qFbjkpSWLmswMNK-LOI6UmSKouZOBo8jOPu9EBpITEfl+OCRmxiHAZIJIAO5YDEen4A8bB-twMZ-gARugPBwQhQEoRu7ZpoiyRbLm1HiJC16bLIQo7FYUrVNkKhZJ4971pp2ksZZBkcZIABqWCUJwECvhGZAQPwYCSA8GqoAA1sVGpZTlr5gCS8HsUhHljGhCTODYVissoxaWPutQInyFhctKSL8jFmwabWWmvjprGNYZsAZTVuW8HpZCfiQqCBmwlCvgAZtt6CSNV2WrfVC3uYJ66tVuqQlNsaTpKkUlCrMClqNeuibHUolTQSqoapwYAmfZTkuQmvzXcmnmpuhCB9eyOieuk1o6C4DokQjZHqKjO66C6-0dIDwOg2SBCpc10NtnDCTiqU0ICjyciyG4+RYwKpQaBmSKpE4dpE4GJMg2QqrqlqrlNch1PCR2vNSLa4rZq4eaDVyo4+jymRqJWDQ4vFj7E2AQMiwAohALmU9La4w7dHaaNhNjaBKnqsqCatqBrdTirMNiQuIgudB+bzld+DVuUhIGFTgxWlRVVUrXV4dS-qNs021iDyO9TslMoTj1LJWN6BYOsyphOhmDkcXNP603IMHoeWcni0FUVJU4GVlUnYnzbNxxdCroasMZwgWKPay0qWNYLhZ+UCIuGeyR6O47o0ZsAcG7XioN2Hl2Rxt0HbZIu0HUd3dnUne-AS1m5y+UWz7DkY7pKpsKDRoMz1MK4olO4G-3jgVAEA4CCASrWIedtvKiAWBaBgmQ6g2g0PaR00xWYSjHFPHQNFCIzk3jWRURJIAQNvlA4oFo8zWlSPUT00pVhYw9NMHWrhKwbH2GaNQgdYxNm-JDPAxCvLw1mAzTY3opJ9TqKFehqlUTMMwiUdIrNA5gMbB8IhQlh5bkWFsVwyJshYmkNYQs9DNhI2vJhFQZp+SgkDklXS+kr7wHUZA+G-UzwygUPyLB+xApFh9BaCU6hpSLGqNKDheC5yBlsfNCORlTLmTWpGaytl+G0wwhoEU7ilDWnMN4zGhRPqO0Cn1XQthVKaBsbNZK9iYlLUyhfBJKSR7OH2FYAi1oDGzFSNIIUGwZiVD0BKTCSkFCB2FiZRpIlMjgk+v2VQch0jEUKIYz+HoOSaA0IeJRO8m4OImXLTQjDYRpAFIsfckillZAUjYGipceQ6zvF4IAA */
|
||||||
predictableActionArguments: true,
|
|
||||||
tsTypes: {} as import('./commandBarMachine.typegen').Typegen0,
|
|
||||||
context: {
|
context: {
|
||||||
commands: [],
|
commands: [] as Command[],
|
||||||
selectedCommand: undefined,
|
selectedCommand: undefined as Command | undefined,
|
||||||
currentArgument: undefined,
|
currentArgument: undefined as
|
||||||
|
| (CommandArgument<unknown> & { name: string })
|
||||||
|
| undefined,
|
||||||
selectionRanges: {
|
selectionRanges: {
|
||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
codeBasedSelections: [],
|
codeBasedSelections: [],
|
||||||
|
} as Selections,
|
||||||
|
argumentsToSubmit: {} as { [x: string]: unknown },
|
||||||
},
|
},
|
||||||
argumentsToSubmit: {},
|
|
||||||
} as CommandBarContext,
|
|
||||||
id: 'Command Bar',
|
id: 'Command Bar',
|
||||||
initial: 'Closed',
|
initial: 'Closed',
|
||||||
states: {
|
states: {
|
||||||
@ -275,6 +267,7 @@ export const commandBarMachine = createMachine(
|
|||||||
data: { [x: string]: CommandArgumentWithName<unknown> }
|
data: { [x: string]: CommandArgumentWithName<unknown> }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
predictableActionArguments: true,
|
||||||
preserveActionOrder: true,
|
preserveActionOrder: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -286,45 +279,28 @@ export const commandBarMachine = createMachine(
|
|||||||
(selectedCommand?.args && event.type === 'Submit command') ||
|
(selectedCommand?.args && event.type === 'Submit command') ||
|
||||||
event.type === 'done.invoke.validateArguments'
|
event.type === 'done.invoke.validateArguments'
|
||||||
) {
|
) {
|
||||||
const resolvedArgs = {} as { [x: string]: unknown }
|
selectedCommand?.onSubmit(getCommandArgumentKclValuesOnly(event.data))
|
||||||
for (const [argName, argValue] of Object.entries(
|
|
||||||
getCommandArgumentKclValuesOnly(event.data)
|
|
||||||
)) {
|
|
||||||
resolvedArgs[argName] =
|
|
||||||
typeof argValue === 'function' ? argValue(context) : argValue
|
|
||||||
}
|
|
||||||
selectedCommand?.onSubmit(resolvedArgs)
|
|
||||||
} else {
|
} else {
|
||||||
selectedCommand?.onSubmit()
|
selectedCommand?.onSubmit()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Set current argument to first non-skippable': assign({
|
'Set current argument to first non-skippable': assign({
|
||||||
currentArgument: (context, event) => {
|
currentArgument: (context) => {
|
||||||
const { selectedCommand } = context
|
const { selectedCommand } = context
|
||||||
if (!(selectedCommand && selectedCommand.args)) return undefined
|
if (!(selectedCommand && selectedCommand.args)) return undefined
|
||||||
const rejectedArg = 'data' in event && event.data.arg
|
|
||||||
|
|
||||||
// Find the first argument that is not to be skipped:
|
// Find the first argument that is not to be skipped:
|
||||||
// that is, the first argument that is not already in the argumentsToSubmit
|
// that is, the first argument that is not already in the argumentsToSubmit
|
||||||
// or that is not undefined, or that is not marked as "skippable".
|
// or that is not undefined, or that is not marked as "skippable".
|
||||||
// TODO validate the type of the existing arguments
|
// TODO validate the type of the existing arguments
|
||||||
let argIndex = 0
|
let argIndex = 0
|
||||||
|
|
||||||
while (argIndex < Object.keys(selectedCommand.args).length) {
|
while (argIndex < Object.keys(selectedCommand.args).length) {
|
||||||
const [argName, argConfig] = Object.entries(selectedCommand.args)[
|
const argName = Object.keys(selectedCommand.args)[argIndex]
|
||||||
argIndex
|
|
||||||
]
|
|
||||||
const argIsRequired =
|
|
||||||
typeof argConfig.required === 'function'
|
|
||||||
? argConfig.required(context)
|
|
||||||
: argConfig.required
|
|
||||||
const mustNotSkipArg =
|
const mustNotSkipArg =
|
||||||
argIsRequired &&
|
!context.argumentsToSubmit.hasOwnProperty(argName) ||
|
||||||
(!context.argumentsToSubmit.hasOwnProperty(argName) ||
|
|
||||||
context.argumentsToSubmit[argName] === undefined ||
|
context.argumentsToSubmit[argName] === undefined ||
|
||||||
(rejectedArg && rejectedArg.name === argName))
|
!selectedCommand.args[argName].skip
|
||||||
|
if (mustNotSkipArg) {
|
||||||
if (mustNotSkipArg === true) {
|
|
||||||
return {
|
return {
|
||||||
...selectedCommand.args[argName],
|
...selectedCommand.args[argName],
|
||||||
name: argName,
|
name: argName,
|
||||||
@ -332,10 +308,14 @@ export const commandBarMachine = createMachine(
|
|||||||
}
|
}
|
||||||
argIndex++
|
argIndex++
|
||||||
}
|
}
|
||||||
|
// Just show the last argument if all are skippable
|
||||||
// TODO: use an XState service to continue onto review step
|
// TODO: use an XState service to continue onto review step
|
||||||
// if all arguments are skippable and contain values.
|
// if all arguments are skippable and contain values.
|
||||||
return undefined
|
const argName = Object.keys(selectedCommand.args)[argIndex - 1]
|
||||||
|
return {
|
||||||
|
...selectedCommand.args[argName],
|
||||||
|
name: argName,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
'Clear current argument': assign({
|
'Clear current argument': assign({
|
||||||
@ -353,6 +333,8 @@ export const commandBarMachine = createMachine(
|
|||||||
'Set current argument': assign({
|
'Set current argument': assign({
|
||||||
currentArgument: (context, event) => {
|
currentArgument: (context, event) => {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
|
case 'error.platform.validateArguments':
|
||||||
|
return event.data.arg
|
||||||
case 'Edit argument':
|
case 'Edit argument':
|
||||||
return event.data.arg
|
return event.data.arg
|
||||||
default:
|
default:
|
||||||
@ -361,22 +343,27 @@ export const commandBarMachine = createMachine(
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
'Remove current argument and set a new one': assign({
|
'Remove current argument and set a new one': assign({
|
||||||
|
currentArgument: (context, event) => {
|
||||||
|
if (event.type !== 'Change current argument')
|
||||||
|
return context.currentArgument
|
||||||
|
return Object.values(event.data)[0]
|
||||||
|
},
|
||||||
argumentsToSubmit: (context, event) => {
|
argumentsToSubmit: (context, event) => {
|
||||||
if (
|
if (
|
||||||
event.type !== 'Change current argument' ||
|
event.type !== 'Change current argument' ||
|
||||||
!context.currentArgument
|
!context.currentArgument
|
||||||
)
|
)
|
||||||
return context.argumentsToSubmit
|
return context.argumentsToSubmit
|
||||||
const { name } = context.currentArgument
|
const { name, required } = context.currentArgument
|
||||||
|
if (required)
|
||||||
|
return {
|
||||||
|
[name]: undefined,
|
||||||
|
...context.argumentsToSubmit,
|
||||||
|
}
|
||||||
|
|
||||||
const { [name]: _, ...rest } = context.argumentsToSubmit
|
const { [name]: _, ...rest } = context.argumentsToSubmit
|
||||||
return rest
|
return rest
|
||||||
},
|
},
|
||||||
currentArgument: (context, event) => {
|
|
||||||
if (event.type !== 'Change current argument')
|
|
||||||
return context.currentArgument
|
|
||||||
return Object.values(event.data)[0]
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
'Clear argument data': assign({
|
'Clear argument data': assign({
|
||||||
selectedCommand: undefined,
|
selectedCommand: undefined,
|
||||||
@ -401,6 +388,11 @@ export const commandBarMachine = createMachine(
|
|||||||
}),
|
}),
|
||||||
'Initialize arguments to submit': assign({
|
'Initialize arguments to submit': assign({
|
||||||
argumentsToSubmit: (c, e) => {
|
argumentsToSubmit: (c, e) => {
|
||||||
|
if (
|
||||||
|
e.type !== 'Select command' &&
|
||||||
|
e.type !== 'Find and select command'
|
||||||
|
)
|
||||||
|
return c.argumentsToSubmit
|
||||||
const command =
|
const command =
|
||||||
'command' in e.data ? e.data.command : c.selectedCommand!
|
'command' in e.data ? e.data.command : c.selectedCommand!
|
||||||
if (!command.args) return {}
|
if (!command.args) return {}
|
||||||
@ -429,45 +421,20 @@ export const commandBarMachine = createMachine(
|
|||||||
},
|
},
|
||||||
'Validate all arguments': (context, _) => {
|
'Validate all arguments': (context, _) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
for (const [argName, argConfig] of Object.entries(
|
for (const [argName, arg] of Object.entries(
|
||||||
context.selectedCommand!.args!
|
context.argumentsToSubmit
|
||||||
)) {
|
)) {
|
||||||
let arg = context.argumentsToSubmit[argName]
|
let argConfig = context.selectedCommand!.args![argName]
|
||||||
let argValue = typeof arg === 'function' ? arg(context) : arg
|
|
||||||
|
|
||||||
try {
|
|
||||||
const isRequired =
|
|
||||||
typeof argConfig.required === 'function'
|
|
||||||
? argConfig.required(context)
|
|
||||||
: argConfig.required
|
|
||||||
|
|
||||||
const resolvedDefaultValue =
|
|
||||||
'defaultValue' in argConfig
|
|
||||||
? typeof argConfig.defaultValue === 'function'
|
|
||||||
? argConfig.defaultValue(context)
|
|
||||||
: argConfig.defaultValue
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
const hasMismatchedDefaultValueType =
|
|
||||||
isRequired &&
|
|
||||||
typeof argValue !== typeof resolvedDefaultValue &&
|
|
||||||
!(argConfig.inputType === 'kcl' || argConfig.skip)
|
|
||||||
const hasInvalidKclValue =
|
|
||||||
argConfig.inputType === 'kcl' &&
|
|
||||||
!(argValue as Partial<KclCommandValue> | undefined)?.valueAst
|
|
||||||
const hasInvalidOptionsValue =
|
|
||||||
isRequired &&
|
|
||||||
'options' in argConfig &&
|
|
||||||
!(
|
|
||||||
typeof argConfig.options === 'function'
|
|
||||||
? argConfig.options(context)
|
|
||||||
: argConfig.options
|
|
||||||
).some((o) => o.value === argValue)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hasMismatchedDefaultValueType ||
|
('defaultValue' in argConfig &&
|
||||||
hasInvalidKclValue ||
|
argConfig.defaultValue &&
|
||||||
hasInvalidOptionsValue
|
typeof arg !== typeof argConfig.defaultValue &&
|
||||||
|
argConfig.inputType !== 'kcl') ||
|
||||||
|
(argConfig.inputType === 'kcl' &&
|
||||||
|
!(arg as Partial<KclCommandValue>).valueAst) ||
|
||||||
|
('options' in argConfig &&
|
||||||
|
typeof arg !== typeof argConfig.options[0].value)
|
||||||
) {
|
) {
|
||||||
return reject({
|
return reject({
|
||||||
message: 'Argument payload is of the wrong type',
|
message: 'Argument payload is of the wrong type',
|
||||||
@ -478,7 +445,7 @@ export const commandBarMachine = createMachine(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!argValue && isRequired) {
|
if (!arg && argConfig.required) {
|
||||||
return reject({
|
return reject({
|
||||||
message: 'Argument payload is falsy but is required',
|
message: 'Argument payload is falsy but is required',
|
||||||
arg: {
|
arg: {
|
||||||
@ -487,10 +454,6 @@ export const commandBarMachine = createMachine(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.error('Error validating argument', context, e)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolve(context.argumentsToSubmit)
|
return resolve(context.argumentsToSubmit)
|
||||||
|
@ -7,7 +7,7 @@ export const DEFAULT_FILE_NAME = 'Untitled'
|
|||||||
|
|
||||||
export const fileMachine = createMachine(
|
export const fileMachine = createMachine(
|
||||||
{
|
{
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QDECWAbMACAtgQwGMALVAOzAGI9ZZUpSBtABgF1FQAHAe1oBdUupdiAAeiAKwBGcQDoALACYAHAE4AzGoUA2JXK1yANCACeiabOmSlGpkqsqAvg6NpMuQiXIyAEtSykuLAAzDDgKAGEAJzA8XmwQzGY2JBBuPgEhFLEESTVxWS1xBWVVcTU5PXEjUwRNFRkFWwB2FQVxJia5JnE5JxdQ92IyMB8-BLCAJTBSPBx40KThNNR+QWFslSVZJS1u3TUVSR1xJurEXSYZJslipismbTklPpBXbHwhr19YYNDYCOisXmiVYSx4Kwy6zMeQKRRKKjKFUKZwQPXqkjkmjUTCYWi0TQOCheb0GnhG31+mH+ABEwJg4pSwIsUstVplQNlcvkZIVikpSuVKijdFoZOItJJpEomtcYUTnK8Bh8yaMfuN-gB5DjTRnMzjgtlQnIwnlw-kIwXIkyIdSSGRKHF5XFqSwdYlKjzDVWM-4AZTAvCwsDpYAIcQgWAgqGiYa4kWMetSBshWTMNyaMkOuV0ChUBJUEpRnUuux6WmxGkkTF6CpJyq9URi-FIUEZFAgghGZAAblwANYjAiAuIAWnGidZKY50O5vPhiKF1oQcnF9q0myYCN2FSa4ndbnrXkbsTIrfGFDAkUicZkHHQsSCcZwMiHTbAY4WoJZybWqeNq82ddygUaQHiqJc7BkTRcwUOQjmKHR133d5PS8KYZhwU82w7Lwe37EZogw99xy-fV0l-adalzeQVForY1Ada4ni0FE5CaJRM0UAlmm6LRkNJL10NmLDz0va9Ilve9eEfSJn0I2ZiM-ZIyIhCjREQOoaLospGIxHYUQY0U8VaVoJUdB5+MPEZaXpETQnbTsZDwgcZAgENRxI5Sk3I9l1P-UVci6cUbg0JRlBRcRNkzHRdwUGVaPEOxLNQ6z3LszALyvG87wfJ9XPcxSQS8yc1M5PIMz0aU7gYuQCyOIsegaaUCTCwpnT42sPU+EYpjwKMWx9BzcNIXsXMBCAPypCcf187I7DUGQqweWDGgJNR8SLJ55AY9ibgLPJy2S7qZF6-qzz+IauxG-CZHGya4AYSRipmo15sWnFikUDoNA2pcXVkBQbFo7FuMkJojpVU70rCMTsqkmS5JiCb1WmnzXtyd7lq+tbfpqYoFEzSL9DqNofo6-oDxSigpiCaJYCIVHVNmxAtEaBpyxuZQ8iOaQUTBjNpWJxo2l3TpnheAI3PgFI6xSsE0b-EcWKXJWZBxHF2hAip0ySzrKeOikAh9eWmaNSVriappqy2CpWkkAzqLUJp8Q5h4DgRGsKZQg2xj+E3DT-c2OIlGVdwqFc4rUYUuntB0wesaQmhAvc9e9lVj2bc7MH9qc-OkNjMwFfkygLWqIpxe0cTsWCOiOE4IcE6ZhIG8Yc9KxBFFYlp5EkWirFB6Q1AbrwbIDaG2+ZnIegzTYLWLg59BUYUmAWwHKo3B51HlL2BLQpHoellSA8oooOOsSLGiRdowdY2r7RWu5OhdQLycVfWVS1aZx+-BXKPzmei70VLkvJcKhbBijKHicq3QQLiycEAA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QAkD2BbMACdBDAxgBYCWAdmAHTK6xampYAOATqgFZj4AusAxAMLMwuLthbtOXANoAGALqJQjVLGJdiqUopAAPRAHYAbPooAWABwBGUwE5zAJgeGArM-MAaEAE9EN0wGYKGX97GX1nGVNDS0MbfwBfeM80TBwCEnIqGiZWDm4+ACUwUlxU8TzpeW1lVXVNbT0EcJNg02d-fzt7fU77Tx8EQ0iKCPtnfUsjGRtLGXtE5IxsPCIySmpacsk+QWFRHIluWQUkEBq1DS1TxqN7ChjzOxtXf0t7a37EcwsRibH-ZzRezA8wLEApZbpNZZTa5ba8AAiYAANmB9lsjlVTuc6ldQDdDOYKP5bm0os5TDJDJ8mlEzPpzIZHA4bO9umCIWlVpkNgcKnwAPKMYp8yTHaoqC71a6IEmBUz6BkWZzWDq2Uw0qzOIJAwz+PXWfSmeZJcFLLkZSi7ERkKCi7i8CCaShkABuqAA1pR8EIRGAALQYyonJSS3ENRDA2wUeyvd6dPVhGw0-RhGOp8IA8xGFkc80rS0Ua3qUh2oO8MDMVjMCiMZEiABmqGY6AoPr2AaD4uxYcuEYQoQpQWNNjsMnMgLGKbT3TC7TcOfsNjzqQL0KKJXQtvtXEdzoobs9lCEm87cMxIbOvel+MQqtMQRmS5ks31sZpAUsZkcIX+cQZJIrpC3KUBupTbuWlbVrW9ZcE2LYUCepRnocwYSrUfYyggbzvBQ+jMq49imLYwTUt4iCft+5i-u0-7UfoQEWtCSKoiWZbnruTqZIeXoUBAKJoihFTdqGGE3rod7UdqsQTI8hiGAqrIauRA7RvYeoqhO1jtAqjFrpkLFohBHEVlWzYwY2zatvxrFCWKWKiVKeISdh4yBJE-jGs4fhhA4zg0kRNgxhplhaW0nn4XpUKZEUuAQMZqF8FxLqkO6vG+hAgYcbAIlXmJzmNERdy0RYNiKgpthxDSEU6q8MSTJYjWGFFIEULF8WljuSX7jxx7CJlQY5ZYl44pht4IP61gyPc8njt0lIuH51UKrVVITEyMy2C1hbtQl-KmdBdaWQhGVZYluWjeJjSTf402shMEyuEyljPAFL0UNmMiuN86lWHMiSmvQ-HwKcnL6WA6FOf2k3mESMRDA4RpUm4U4qf6gSEt0QIvvqfjOCaiyrtF6zZPQXWQ+GWFlUEsbmNMf1TV9NLeXDcqRIySnNaaYPEzC5M9vl-b+IyFCjupryPF9jKWP5Kks-cbMWLERHRNt0LFntkgU2NLk4dqsz43YsTK++Kk2C+MbTOOcxzOMrhqzFxTgZ1Qba1dd6BUE1jGsLMxxK9KlDNqm3tMLUQvqYlgO5QhlsTubsFXesTTUuPTfHExshDS0RftRftGgEnTZtHbX9Zr+QJ-2S4Y3qnmTC+4tMyp1EfeOnmeQqdOhyXQrFOXXCV1hCkmLDOnBJYvRRDSsyRzGjiKj0lKdAkANAA */
|
||||||
id: 'File machine',
|
id: 'File machine',
|
||||||
|
|
||||||
initial: 'Reading files',
|
initial: 'Reading files',
|
||||||
@ -24,8 +24,6 @@ export const fileMachine = createMachine(
|
|||||||
})),
|
})),
|
||||||
target: '.Reading files',
|
target: '.Reading files',
|
||||||
},
|
},
|
||||||
|
|
||||||
Refresh: '.Reading files',
|
|
||||||
},
|
},
|
||||||
states: {
|
states: {
|
||||||
'Has no files': {
|
'Has no files': {
|
||||||
@ -160,8 +158,7 @@ export const fileMachine = createMachine(
|
|||||||
type: 'done.invoke.read-files'
|
type: 'done.invoke.read-files'
|
||||||
data: ProjectWithEntryPointMetadata
|
data: ProjectWithEntryPointMetadata
|
||||||
}
|
}
|
||||||
| { type: 'assign'; data: { [key: string]: any } }
|
| { type: 'assign'; data: { [key: string]: any } },
|
||||||
| { type: 'Refresh' },
|
|
||||||
},
|
},
|
||||||
|
|
||||||
predictableActionArguments: true,
|
predictableActionArguments: true,
|
||||||
|
@ -104,7 +104,6 @@ export type ModelingMachineEvent =
|
|||||||
| { type: 'Constrain parallel' }
|
| { type: 'Constrain parallel' }
|
||||||
| { type: 'Constrain remove constraints' }
|
| { type: 'Constrain remove constraints' }
|
||||||
| { type: 'Re-execute' }
|
| { type: 'Re-execute' }
|
||||||
| { type: 'Export'; data: ModelingCommandSchema['Export'] }
|
|
||||||
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
|
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
|
||||||
| { type: 'Equip Line tool' }
|
| { type: 'Equip Line tool' }
|
||||||
| { type: 'Equip tangential arc to' }
|
| { type: 'Equip tangential arc to' }
|
||||||
@ -120,7 +119,7 @@ export type MoveDesc = { line: number; snippet: string }
|
|||||||
|
|
||||||
export const modelingMachine = createMachine(
|
export const modelingMachine = createMachine(
|
||||||
{
|
{
|
||||||
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBLWFJDWV5hVSZZTrlBUKjS1FZiUrDeWVDasaQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iaF07bCNLiRYlGiZUyZDbFYQzfIaLSQ5Sw0TGZQnM6eNodLoEW73J6wV7sD5UQyRZisMbcP4IQRrGiSdRmfIqUxrCrQlbKcoQ4RHDTWUx5DHNLGXXHXPjsB4AVwwX0psXGCSEFThkNEymFGlECkM0MM+0ZMgUa1MwuypgRhlFHlaEpufBYD3YiuiVN+qrpdg0klMwkFaU0ermwuhwlUkiBhxUyKUazt5za3mJH2IZEomFTb0+9BGnpVoESRrNkmMga0ahMplkhqOZVL+sM4ksCj1SfFOZJ70k3Y+AEkrj0AkEugNwvnvoWad7DIGyuqOe2rPNDRCLIKaKJa6JBXrRJ2Hf3eyeh7jkCRXn0oABbMB3fwAN0enHIJEw7p+Rf4RhkpkbapNEjTRUgKfQjA0FsUnBOQJHyJQNCPC4Tz7NN3nPbpL2vII7wfAJ3lQB5sAAL24dgPy-Gd4mLP9A0kVQzW3I18mRGRDXEI1yl1LJDBWYwKhkZCU3QtDc0w4huFgGUSDwfxCOIsi7g-fwIGwaTMzAKjlVnWiECsPdNzSGgqnSAD1GhNJ2QsCpRHBXUAJbYSxJ7FzB2HIgpJkuSX1dbB30wVT1IoigtKnJVqRo399OWBR-TyFdlCgtRBUs1lymsAVYRjPdnNQs8PK8h5ZNwfwAEEACFvH8AANbTItpKx21mcQxEMGxOP-NJLJ1eL41gndagaVxTjFY9RIK3FPNwaTirkyrqoATXqr09Ka7ZrDsZQ5H2ZQtXYiDijUOKgVsvi1ErPKJvQiTptmkr-DIKAuhWn8S3SKQQRBU15H1YRTEsuy4QPbKtX+gMrtzNyMMKmbvNKrp8HYPMKQ9HSove1rpD21QANkTjREsw4Tu1KD-oRBNhEh1zJu6O74f8JhHiZ3A1PIWVMBIJ41I00LXt06KrFNWYLU6jIjlUZRLP1P1+SqAMgQRDQxGpj5oduoqHoU0jyI-TA9EenAoCGcK0YaudlhkJlQ3bAEMhWQ1UR5f9Q1hfItRKVXTxu2H7p819-L1g2P2wY3+YxujzByU1-qdsxUUNbbtj1AMqjWf9HGGpp7RQ67xN9hnYFwEgmH8dhUFq8PGs4qRlhBNQA2qMxLOyK2ZDkeZ5m3Rx2699WC7m0qi5LsuK+W03vwF97oMyfjYVa4wNEs9qZj3Hud3nOYcj72nJLhwf-DAABHWUVMRqBkari3a3KFf-qyPIJHAop51bFqTHBbccdMHefamzW5JMC5nragE9qLV3yBYdu-EdzpE3oaWsMwTCcUOBaTIahDwjUxONKGu96YHweGAW8qAXz+HIAAu4sAr5rWsHFQ41h9qaH2DoQ6RxOIWD4rYbIYIzRCSwWNXOuC-7dAAEpgEEGAPgIRZT3GoYLOoUgWyGFrKkdIYgE6HXUFbdIqRkSdwTLafhOcRJCPzpKE+2BS4ABk8BgFHqgT8YD0aNQUNZCQKgbDCnSBZTRdReSK3bnuPaRpf5mJuBY0uIUYB3GwCpLm5BR5yMSFtOKIIPZ8V+go+slhoxrFUJkhESU+5lQAO6yQIkRHWylAo8xCpQfweAABmqACAQG4GAdouAnyoFeJIGA7BBDayUhRTAggmmoCSYgYyfpCkZADGYBW4hLJQXMO2U0aROEdypkY5M0NJClPKfJSpwyVK1M0g03AzSCCPAeERSQTBObsGaQ8W8fS-CDOObrUZ4zJkIGMtsI0NoWy6lNAGSy31Nw0FUCYPIkZDHZ12ahA5HBnwBwCkFXm9TxmtPaZ07pvT+mCF8m+D8YzLkTKcebPSGyrYtkUPYZEyj8hpSUEyMQcg7ClCBMUspKLiWBxqcFc52Kbl3IeSQJ5RFXmEv5QFMlzTfk0qZNuMQAExCaDSoKcsONVFaOZDyw5C1aoXKuW03AHS8D4o6YSkgAAjWAgg+DyopajSeEc-koNyTIWCcg6jrEOv+ba5YMhQSSrIbatQDUoqNTVE1LTRUPHuY8550r3l2odU6n5lLVrRQ2TMHIIIlA7m3FoHqfFpB6j2pxbIgYf47K7KJZFAQjWLTjTi81eKenWrTfawQehnWKs9XUeQFRbCIN1NCVsyIRauLsnqVsuV604Nck28qVV-CtpFQ8W5ibxWSpeW8gZ6a+0DuzW9KZnEkGsVassFYobJ18T9FC9k20do3qjQEJ6XQ21motV0rth7BBfvEVm114DvR5pSEaOocgLQaKKHBHkKi7JGgqGaWEH7Hr4G-VundSaJUpsA8B09YHnEQc4idbITUrAN3nIDec5YWzyFSHO5Y2yEUNqhqu8+yMf24stQBwlPH3gkYLGR6lBMWpWTJluZlAaOQWBWJelQ84rCYeE22hN+H92poGcJ0T05xO5s9cYGF+o1jKwOghpKVt5i0OtGIFE6Il2CJXbygITMHgszZhzLmGK6l2Oxb+ztBL3mee8-5XzDxBBnNCgZiKObkmW3ikoawr706EwDdUbYC85h7Tg76zD4WHw+c5tzIVoVNPbrFcmqVgHius0i2VmLFXKDxbNolqZyXhSpbmNA7LllsrSH5HlncwpCsuZMa5Gx5r7GYBHH0cccRFXeqjjRo4mQjh5CWQGgM5YesmVoYxPhHHl1qxm3Y8uDi8QRLLk9fCsTArxMSWeqeUzYR+n2GYcE6h5ZS0OgrFqrVHBZHZH3C7c3JADlwBwAgK2VBMjSRt60sGuT6kkMGWskJWr-TSOD2xkPoew7JKRqlub8hWzsqaR9NHtQRn+rMFYEhbBakhNufHs2ruYEkLgKVH4FtjhCMt177rrC1GkKafUvCMhan+0UJKMwFzKz1O1KCnEOeXdQA4yQAA5CuAAFVAeB2CwAIGVCAEBAgUVdIzI3dxFXzD9JqfLgpFhJS5NqFqqcsiKCUAoDXkO9f+EN8b03pBQqONJ51-SmhzAmGDKtrQep-VFF4iLVBpRTQrD7kT9gcOReNUsDyJPWQVDbkT4aYEjPIzLChboqCOeYd55J2JsnJZmJMkhJCOYshwTtUrwBaMts5irg6s507rm1YABV7sxLiQ8BJ5cBf9GF1H89fztrmG1EoLxh3-yV6gkyR93rLC6g7JNvZM-8APfn4vlp1xbsQ65w7hE79FA7jsttaE1oeRuzSMxqFOYdjUaYxPZWUVmCuDSV0E8AAeVwHbT-StX2W8Cn0EHALaUECgPYFgJNjXze30nxkZxMF1BMkyEWGfkQB3DihXDQzpU8UwQnymw+H8F538EaRIEoB6CW1UjAHYI5g805nNV+UEHanMDSRhSNEDAkGUS5DkH9BsEODmEsDgmAOwQuDIGwFvAlVaFHkZkEO6GCwE16Q0K0PuEEHLkEHYMoF+QqEAjIOyB9ShEOm2lSSsDBFBUDHUGchMO0PwF0PFXNWXyW0GEVSBEYw5R3EYkDGhDXnLFWDsHBBWBqG8Jh1MJ0PLkPj4GCh0KJFzAQJCw6R8LMIsNyJ7BsL2gYisFqAtHbjSFhGhCghywSkAN9BcKQhOF5wwHgCiDUKgFb2j0EEWDKFyEUDL3UC0B2yKEGJNC1EsFqCFGMDNGchxDAH6PX3pC0HKGRHmRDGAmhHUEXFbH1GkOMmUS9jWPwPZDihLxMHyQr0OiQQWGtEGlsHmAwwv3ymEQuPdRbAtAxySn1GB19UyxfiZTiNalSGqAAmhMwyGS+X82FXJW+JcTWFmDmUUH5BqFSHBWSABFbChW1HyHhRAMRUbXc1RT8nRViyxSRMMzbyMC0W1T7wUU3wNADTkEZFSCghlnmFsMwxjTjWRLnFcSkDUE6lrG2lMwBl23YV1HjFUHOj1BOxJM4zc0NXXU3VpIS3XyOGSAShyA21SkOinQ2jghHSqChMw2A0FLpOj11O2BWHshKCqDyFrEBjUCZGDAqFUBrzqHUwfAvneBtO1PwN1MZAyFWBVwlLXHkwR3+mUUcG2LlKzhVLO17FXQa1Kz82pMCy1I6x1PbF-3ZDslAgUJKEG3RzlnbAAnVDBw+NEify10wCFOpXsAsCGnbBsHUDWBiKjG5MEmQVtnH1TMn17EbO11zxbNzX2DKEyBKE7ItJ7OcOSD4jvR72721ADy5x5z52bNtPXyFHzXnn-GrFkCs0QDTwUOUUzzNFkC3KbN1wNztxNynOSV1FriUHan1HjxUEmIvKDWp2gyOEjC0GJN6L2UnP3NDO3C311EFAjJ3371YQbEYwOJbDbHP0YMv1n04Fv1HlfKmVDGjBjBUB+nBnXDyH+LNA5AfgMj7nQMgOt2wPQjgIIv0nNHKDqCTyZ1S2lKKCCUZyZTP3jC1C9hYIrisNWKgvdSmDkIkBWVUDqH+i-2cLkKgnsDnQmNNBSM0N8KgH8P0LYr4ikADG1CtHkFp1kJ5BMHUEjDXiONUIETaCKPSIrkkWyL8NKI+CMscAx1hCYlanZE5UnUjF5HsHIMswAnaKwpcr8IyICLsQAApyEmA9AABKNikQCQCXTJRiTQFhIoHhRTF9QJOlZU8C2K-S+K-Q-wZK1AVKtK2qlK9KzKsQT6OQUYvKiYydBESnHcawA0muYUFwFwIAA */
|
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBLWFJDWV5hVSZZTrlBUKjS1FZiUrDeWVDasaQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iaF07bCNLiRYlGiZUyZDbFYQzfIaLSQ5Sw0TGZQnM6eNodLoEW73J6wV7sD5UQyRZisMbcP4IQRrGiSdRmfIqUxrCrQlbKcoQ4RHDTWUx5DHNLGXXHXPjsB4AVwwX0psXGCSEohWO3Uh2UOSsmmhhn2jPyNGUolMhjWwvZoo8rUk3mJH2IZEomEdb0+9BGVN+qoQhoUPOMwgRi1BplkBqOZUDCkN4ksClEClt5zaHpJ7wdTveAEkrj0AkEugNwt7vr6VaBEoZQ2UKsIOcmrPMDRCLIKaOa6oKU6I0+LMx8c56C7jkCRXn0oABbMB3fwAN0enHIJEwiuiVZp-qsFskRxkmibmlSBX0Rg0hikifBcgk+SUGkH9uH2ff4+6k+nQTnC4Cd5UAebAAC9uHYDctx+at+CMeFJFUINu0NfJkRkA1xENcoNHkSwVmMCoZFfC531HLMv2IbhYBlEg8H8ICQPAu4N38CBsBo10wGgnd4hreDRA0Ts0hoKp0gtdRoTSdkLAqURwVwi0bxIjNc3Ij5KKIajaPolcHjXVj2M4ihuIrJVqT4uCA2WBRJGFY9UR1YxNAwy8EBkVlymsAVYSBM0X1cU4xTfNTP0LLTcBoh46NwfwAEEACFvH8AANHjlV3fjrOTWZxDEQwbCwmRbHEKThSE4VkUWVJzVqBpAsxELPXU-Nwu06L6MS5KAE10os2k9W2aw7GUOR9jNUboUTdRJCBOTLTUUMAqaO1SNC3NNPamL-DIKAuj6v0spvHJpCUapk0UOtTCk+S4X7Xz1WEUxTGEFSWpazbIp02KunwdgvQpbcMss2sShmFR1VEm80n2KTDlsptTGvJ6wxUV6GuCtbmrC3EIqi7amEeQncHY8hZUwEgniMyCTIO2Daw82ybEjKwMiOVRlCk+MhP5KoXqBBENDEN6yJx7o8e+hjgLAiCN0wPQdpwKAhjMoH+r3ZYZCZFMtAkXCMhWA1HMkYqddhfJ1RKEX1rHNqvo62K9IMzB5cV7BlbpzKrKsJ7cuPJ7HLMVEDVG7YUxeqo1mKxx6pW9N3rFqj7e22BcBIJh-HYVBUs9kGjCwqRlhBNQXuqMwpOyLWPPVeZVBTIFiIx1bVOxja7fx+jU-TzPs961WYK90GbwsJssNhPLjA0KSCpmQTHDkC1Qx1WOgubhO29xrb6LAABHWVWN+qB-tzgbiqkVEhaerI8gkC8ijrRNcpMcFuwm0xrdb23N+T+imEpuXqD914gNWQMwlgER7MjKeblDCRjAaUQ4z1MhqAHE3eOosN7iy3rFB4YBZyoBXP4cg2D2CwBPhrLQiFWbqmPNeFMpUYFKDKPhWw2QwRBkbnHIcNsKKFgAEpgEEGAPgIRZT3HIUdOoUgbywMTGkIWFRDDQnUFrdIqRkS1yUIcD+WYPqFmuHvbAGcAAyeAwA91QJuIBwMBoKBkhIFQzM9aSTckoMOmRhpyDmKiQwOiRyJwMbKIxmddoAWwKxSm5Ae4SKsiNWyIJLaWnkG4hh99jBlFPHXRQ6Q5jLVXugtScUADudFALS2YpBTAbEOI00oP4PAAAzVABAIDcDAO0XAS5UCvEkDAdgghGIyxYpgQQjTUAxMSCJISCIdSiVqM9ceUlrzmGTMeGGyhbCPT8dmYppSpZMVllU6mXF6m4CaQQR4DxgKSCYBTdgTSHizl6X4AZ5TDmjLOeM6x6ssoiW2IaBECZcLHhelJEEWtsimnBHUNCvi0HcOarsjgy5VzYHXEcmpJyxktLaR0rpPS+mCCdmijcHymkTMQDDLWN5LomGRLA-IUljxSEcDfOwpQgTbMkEigIxL0XVOMnU7Flzrm3JIPc4CTzCV8tJWMil7kipMm7GIReOsmWCkPBNVIIJzpKC5Ty+KSVUqnPOa03A7S8D4vaYSkgAAjWAgg+Bkq+YDAeecFWWlmqNe8cg6jrDcsVUah4MjXmXprWo+qSnIq6sa4VDwrkPBuXch5UqXl2odU6uV3zDqxLHrlHVdRIRzzKp6v1ZosLZFDO-eFTVdEGpjd1E1zSzUWs6d061ab7WCD0M6+V6zpCNgqLYOBuEpqmhmDYOx8l6HdjyY1LGdao0BAbU2i58bRXJslc8-p6bu29uzfTSlebzwrD8isUNU1LRCVNOyUaY08qcPyQixdezdpdFXS2vF7bt2CDfYIrNrrgH+hhuDSwN4b7PWDgGioPJIzdjAxUIMsJI2vvwO+uNCak3ipTT+v9+7AM2OA1hBG2Q9RWBLnWG6dZDzgayHIbsyx0ZcNrSOA1h9-oftxZa79hL2PvHwz6QjvysLbByNJKBMhzxwxyqaDlKg6xWBQ8ivjq6RWJrFRKx5P6+MCcrEJ3NnrjAmBTJaE8rkijLA1fMawdiFEonRDWhdrGl3+EJg8YmpNyaUwFbU8x2LP3cYJS8tzHm0VeYeIIY5JldPmRzZMzWdl6HWDvVHUQXM5DSH5HMM0kHfVKYCCFhcnmKZU0xSZVT671Obq04SwrJMwslci2VygMW1ZxcpQl4UShkseVS1JXymWCrzHNPZCQXLTHmosZgIsfRSxxD7ZJ8w15JNHEyEcPIqTKUvUPF10SNmkKPvnS3XRE3zFZ0sXiQxGcaYwDuOEqpkTokHsHpS2EQl9hmHBOoPmnM3L83zYoWEokHPMac9mU7U3JB5lwBwAgC2VBMgSatpGC8uTxhNiCSMkI8pPTSONsxkPoew7JARn5sT8ha3kseK95GNnQlhEtlYEhNkbPBKg0Hx2RwQ-O5gSQuBJUbhmyWEI83nvuusLUaQx54wcIyDXLkQtZrXgUdza8WF8eTZ55IAActnAACqgPApCCBxQgBAQIkF9KucN3cPt8wKqv3NIKRYOouQbNyhHLIigzoa7O6gSxOv9c2+N6QEyVjSftYDJocwJg0jVFErrFxRQshlBsIg0ox4VhcqJ+wOHYuBr4VmBdOlDGE8GmBLMDRywoWWbhRz96Oe4fkkE2T2sKEmSQkhHMWQ4ICrl4PIoPWhxLORhB0+lj2YAAqoS7sRIeFErOQv+ii4j4e9yo1zAbKUMKRMthirl+vEyK9knLC4RTFy6f+Awlz4X80wJwTuf+-Dy3yPlnpmJkH8KU05nEBIx5ObNIPCU0OYJjcfMHSQWUEmbOTifSd8AAeVwBxXNS-R6Tim8En0EEgNaUEBgPYHgJVlXxewDFkCkDmBMFwlEkyEWDvkQHNFshbEQxpScW2X8H538AaRIEoB6DmzYjAA4PJgKwpnNXlUEAKnMASWM0NFDAkFgS5AyyRnZCG0sAfFAKO0kDIGwFnHFVaB7lcyEO6ACzbR6Q0K0PuEECzkEA4MoHlUUUPGoMWAyCWChDclGniSsDBBBVDHUDehMO0PwF0LFXNSXzm0GD7SBBozkAXiQlDGhDnkPFWDsHBBWBqB8Jh1MJ0Kzn8GERqR0KJE9CQNbStXULSPFUEQsLyKzBsLNCoVZGeg8jSFhGhDoQHWPGALsDmFoRcECn5wwHgCiCOxfzX0EEWDKFyGySQk0B0DckEEEiVSgX2DyFGktDehxDAEGKIPpEoUehehBFwhPGUXRwkDUA+3cMsGrXr3fHWPdXZCZmLxUFLyRimi1g2UqEDmcnZzAM5w-EwSuJAWehNh1HjDymqF2GjGSBvDylSGqAtBhPy32SGUqR8yxU+V+L3CDCZgyFLn5BqFSDBWSABDvHmFsH5DhJlQxUFT8xRL01byMBUU1V7ykQ3yUQDXowsG8m5mG1RDhJjRSibVRKOjsSkDUCKlH0SNEjKiwh2CqlUAWhTEO0xi+O5RcxXTGX5O9jsUp1oWI0rjHWqOrkHSqGhLhL-T5OpMjyOGFEQjvByAcDyEjBujUCZDjxg0yFGjqDhJU1VLNLXwtMZAyFWBM1HzbADW5FmhhMcGRCRktjhLq2K28yiyFSpNix9OTH-3ZHkjPDTxKH63R15mTAtEbBtEc0VMf0sTVMmXsBHhKGTEnXRNiNUBNmVxWBl1wmRF90Jxh3YHLMpX2GYTqhrMNLWC5HBPmByRWC7w2XbK1350eQ3G7PchsAnXHmKjUFjx-wQBT1mEUMjCKiDFkCnKf0D38ANyNz6OTKIOsAkBNiYS0HsDSENjcibCrnjATCOCbFvOz07PnMIk331msBMG3z7xgRjBo3UATCTHP2LPekv1u04Bvx7nnLkE0Fmj8hUAciemuhgQYwBKDA5GvisA+LULIiwOgMtzwNzAQO-KtHKF7FTL9VkEwqKEEh5DmAZTPyqnVBYLYKsLWO9I2L7BouWVUDqCekmhcIy2vHsGnS0FkFTCgt8M4H8MyMCN4vPPdUtCkBelZxsHkFpzkJ5AAojLnmmlUIVOKM0L8KgF0OyJolyNzG-McBNlhGQjynZHZSmibF5HsGoKFmKgKi6KcCAA */
|
||||||
id: 'Modeling',
|
id: 'Modeling',
|
||||||
|
|
||||||
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
||||||
@ -171,13 +170,6 @@ export const modelingMachine = createMachine(
|
|||||||
actions: ['AST extrude'],
|
actions: ['AST extrude'],
|
||||||
internal: true,
|
internal: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
Export: {
|
|
||||||
target: 'idle',
|
|
||||||
internal: true,
|
|
||||||
cond: 'Has exportable geometry',
|
|
||||||
actions: 'Engine export',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
entry: 'reset client scene mouse handlers',
|
entry: 'reset client scene mouse handlers',
|
||||||
@ -489,7 +481,6 @@ export const modelingMachine = createMachine(
|
|||||||
'animate after sketch',
|
'animate after sketch',
|
||||||
'tear down client sketch',
|
'tear down client sketch',
|
||||||
'remove sketch grid',
|
'remove sketch grid',
|
||||||
'engineToClient cam sync direction',
|
|
||||||
],
|
],
|
||||||
|
|
||||||
entry: ['add axis n grid', 'conditionally equip line tool'],
|
entry: ['add axis n grid', 'conditionally equip line tool'],
|
||||||
@ -523,8 +514,6 @@ export const modelingMachine = createMachine(
|
|||||||
internal: true,
|
internal: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
entry: 'clientToEngine cam sync direction',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'animating to existing sketch': {
|
'animating to existing sketch': {
|
||||||
@ -535,12 +524,7 @@ export const modelingMachine = createMachine(
|
|||||||
onDone: 'Sketch',
|
onDone: 'Sketch',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
entry: 'clientToEngine cam sync direction',
|
|
||||||
},
|
},
|
||||||
|
|
||||||
'animating to plane (copy)': {},
|
|
||||||
'animating to plane (copy) (copy)': {},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
initial: 'idle',
|
initial: 'idle',
|
||||||
@ -840,13 +824,13 @@ export const modelingMachine = createMachine(
|
|||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onClick: async (args) => {
|
onClick: async (args) => {
|
||||||
if (!args) return
|
if (!args) return
|
||||||
if (args.mouseEvent.which !== 1) return
|
if (args.event.which !== 1) return
|
||||||
const { intersectionPoint } = args
|
const { intersection2d } = args
|
||||||
if (!intersectionPoint?.twoD || !sketchPathToNode) return
|
if (!intersection2d || !sketchPathToNode) return
|
||||||
const { modifiedAst } = addStartProfileAt(
|
const { modifiedAst } = addStartProfileAt(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
[intersectionPoint.twoD.x, intersectionPoint.twoD.y]
|
[intersection2d.x, intersection2d.y]
|
||||||
)
|
)
|
||||||
await kclManager.updateAst(modifiedAst, false)
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
sceneEntitiesManager.removeIntersectionPlane()
|
sceneEntitiesManager.removeIntersectionPlane()
|
||||||
@ -861,12 +845,6 @@ export const modelingMachine = createMachine(
|
|||||||
// (note the orbit controls are always active though)
|
// (note the orbit controls are always active though)
|
||||||
sceneInfra.resetMouseListeners()
|
sceneInfra.resetMouseListeners()
|
||||||
},
|
},
|
||||||
'clientToEngine cam sync direction': () => {
|
|
||||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
|
||||||
},
|
|
||||||
'engineToClient cam sync direction': () => {
|
|
||||||
sceneInfra.camControls.syncDirection = 'engineToClient'
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// end actions
|
// end actions
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,7 @@ const Home = () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const [state, send, actor] = useMachine(homeMachine, {
|
const [state, send] = useMachine(homeMachine, {
|
||||||
context: {
|
context: {
|
||||||
projects: loadedProjects,
|
projects: loadedProjects,
|
||||||
defaultProjectName,
|
defaultProjectName,
|
||||||
@ -176,7 +176,6 @@ const Home = () => {
|
|||||||
send,
|
send,
|
||||||
state,
|
state,
|
||||||
commandBarConfig: homeCommandBarConfig,
|
commandBarConfig: homeCommandBarConfig,
|
||||||
actor,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -21,7 +21,7 @@ export default function Export() {
|
|||||||
<section className="flex-1">
|
<section className="flex-1">
|
||||||
<h2 className="text-2xl font-bold">Export</h2>
|
<h2 className="text-2xl font-bold">Export</h2>
|
||||||
<p className="my-4">
|
<p className="my-4">
|
||||||
Try opening the project menu and clicking "Export Part".
|
Try opening the project menu and clicking "Export Model".
|
||||||
</p>
|
</p>
|
||||||
<p className="my-4">
|
<p className="my-4">
|
||||||
{APP_NAME} uses{' '}
|
{APP_NAME} uses{' '}
|
||||||
|
182
src/wasm-lib/Cargo.lock
generated
@ -246,7 +246,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -257,7 +257,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -574,9 +574,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.1"
|
version = "4.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
|
checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_derive",
|
"clap_derive",
|
||||||
@ -584,9 +584,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_builder"
|
name = "clap_builder"
|
||||||
version = "4.5.1"
|
version = "4.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
|
checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anstream",
|
"anstream",
|
||||||
"anstyle",
|
"anstyle",
|
||||||
@ -606,7 +606,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -856,7 +856,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -897,7 +897,7 @@ checksum = "377af281d8f23663862a7c84623bc5dcf7f8c44b13c7496a590bdc157f941a43"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
"synstructure 0.13.0",
|
"synstructure 0.13.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -949,7 +949,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_tokenstream",
|
"serde_tokenstream",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -965,7 +965,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_tokenstream",
|
"serde_tokenstream",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -977,7 +977,7 @@ dependencies = [
|
|||||||
"diesel_table_macro_syntax",
|
"diesel_table_macro_syntax",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -986,7 +986,7 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
|
checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1036,7 +1036,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1118,7 +1118,7 @@ checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1335,7 +1335,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1404,9 +1404,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gif"
|
name = "gif"
|
||||||
version = "0.13.1"
|
version = "0.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
|
checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"color_quant",
|
"color_quant",
|
||||||
"weezl",
|
"weezl",
|
||||||
@ -1440,7 +1440,7 @@ dependencies = [
|
|||||||
"inflections",
|
"inflections",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1460,17 +1460,13 @@ name = "grackle"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"kcl-lib",
|
"kcl-lib",
|
||||||
"kittycad",
|
|
||||||
"kittycad-execution-plan",
|
"kittycad-execution-plan",
|
||||||
"kittycad-execution-plan-macros",
|
|
||||||
"kittycad-execution-plan-traits",
|
"kittycad-execution-plan-traits",
|
||||||
"kittycad-modeling-cmds",
|
|
||||||
"kittycad-modeling-session",
|
"kittycad-modeling-session",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"uuid",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1715,9 +1711,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "image"
|
name = "image"
|
||||||
version = "0.24.9"
|
version = "0.24.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
|
checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
@ -1877,9 +1873,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.69"
|
version = "0.3.68"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
@ -1916,7 +1912,7 @@ dependencies = [
|
|||||||
"itertools 0.12.1",
|
"itertools 0.12.1",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"kittycad",
|
"kittycad",
|
||||||
"kittycad-execution-plan-macros",
|
"kittycad-execution-plan-macros 0.1.4 (git+https://github.com/KittyCAD/modeling-api?branch=main)",
|
||||||
"kittycad-execution-plan-traits",
|
"kittycad-execution-plan-traits",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"parse-display 0.9.0",
|
"parse-display 0.9.0",
|
||||||
@ -1947,14 +1943,14 @@ dependencies = [
|
|||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad"
|
name = "kittycad"
|
||||||
version = "0.2.59"
|
version = "0.2.54"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4080db4364c103601db486e4a8aa889ea56c011991e4c454373d8050a165d3da"
|
checksum = "13958174d876353f429ea8230dc92fe86f164819cea2e51bbf22e01a4c2a496e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1990,12 +1986,11 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad-execution-plan"
|
name = "kittycad-execution-plan"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#03eb9c3763de56d7284c09dba678ddd6120bb523"
|
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#08f05d91062380fe3a69f4baa1f1301532d31977"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"insta",
|
"insta",
|
||||||
"kittycad",
|
"kittycad",
|
||||||
"kittycad-execution-plan-macros",
|
|
||||||
"kittycad-execution-plan-traits",
|
"kittycad-execution-plan-traits",
|
||||||
"kittycad-modeling-cmds",
|
"kittycad-modeling-cmds",
|
||||||
"kittycad-modeling-session",
|
"kittycad-modeling-session",
|
||||||
@ -2009,18 +2004,30 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad-execution-plan-macros"
|
name = "kittycad-execution-plan-macros"
|
||||||
version = "0.1.8"
|
version = "0.1.4"
|
||||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#03eb9c3763de56d7284c09dba678ddd6120bb523"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "71d31b689c944d00aadda2ef83d8422a6efff97e1be5654a61f9d95496f0c19e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "kittycad-execution-plan-macros"
|
||||||
|
version = "0.1.4"
|
||||||
|
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#632b75a0242400fa34373d7973b9149b0e08aa3f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad-execution-plan-traits"
|
name = "kittycad-execution-plan-traits"
|
||||||
version = "0.1.12"
|
version = "0.1.10"
|
||||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#03eb9c3763de56d7284c09dba678ddd6120bb523"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3ec8efd57b59697eb140b63c0ffe7db44fdfe5a55f14e45513411eba2280ba5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@ -2029,8 +2036,8 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad-modeling-cmds"
|
name = "kittycad-modeling-cmds"
|
||||||
version = "0.1.28"
|
version = "0.1.18"
|
||||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#03eb9c3763de56d7284c09dba678ddd6120bb523"
|
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#08f05d91062380fe3a69f4baa1f1301532d31977"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -2040,9 +2047,8 @@ dependencies = [
|
|||||||
"enum-iterator-derive",
|
"enum-iterator-derive",
|
||||||
"euler",
|
"euler",
|
||||||
"http 0.2.9",
|
"http 0.2.9",
|
||||||
"kittycad-execution-plan-macros",
|
"kittycad-execution-plan-macros 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"kittycad-execution-plan-traits",
|
"kittycad-execution-plan-traits",
|
||||||
"kittycad-modeling-cmds-macros",
|
|
||||||
"kittycad-unit-conversion-derive",
|
"kittycad-unit-conversion-derive",
|
||||||
"measurements",
|
"measurements",
|
||||||
"parse-display 0.8.2",
|
"parse-display 0.8.2",
|
||||||
@ -2055,20 +2061,10 @@ dependencies = [
|
|||||||
"webrtc",
|
"webrtc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "kittycad-modeling-cmds-macros"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#03eb9c3763de56d7284c09dba678ddd6120bb523"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.52",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad-modeling-session"
|
name = "kittycad-modeling-session"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#03eb9c3763de56d7284c09dba678ddd6120bb523"
|
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#08f05d91062380fe3a69f4baa1f1301532d31977"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures",
|
"futures",
|
||||||
"kittycad",
|
"kittycad",
|
||||||
@ -2259,9 +2255,9 @@ checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.8.11"
|
version = "0.8.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
@ -2427,7 +2423,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "openapitor"
|
name = "openapitor"
|
||||||
version = "0.0.9"
|
version = "0.0.9"
|
||||||
source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#6f38abe149c74aa9675e9f0d370aa2f78980dc2d"
|
source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#8db292eaa7be0292512a2cdbef09f2d37af7c79c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -2500,7 +2496,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2657,7 +2653,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.7.5",
|
"regex-syntax 0.7.5",
|
||||||
"structmeta 0.2.0",
|
"structmeta 0.2.0",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2671,7 +2667,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.8.2",
|
"regex-syntax 0.8.2",
|
||||||
"structmeta 0.3.0",
|
"structmeta 0.3.0",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2737,7 +2733,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3605,7 +3601,7 @@ checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3621,9 +3617,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.114"
|
version = "1.0.113"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
|
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.2.2",
|
"indexmap 2.2.2",
|
||||||
"itoa",
|
"itoa",
|
||||||
@ -3639,7 +3635,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3660,7 +3656,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3917,7 +3913,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"structmeta-derive 0.2.0",
|
"structmeta-derive 0.2.0",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3929,7 +3925,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"structmeta-derive 0.3.0",
|
"structmeta-derive 0.3.0",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3940,7 +3936,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3951,7 +3947,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4023,9 +4019,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.52"
|
version = "2.0.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
|
checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -4058,7 +4054,7 @@ checksum = "285ba80e733fac80aa4270fbcdf83772a79b80aa35c97075320abfee4a915b06"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -4188,7 +4184,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4295,7 +4291,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4458,7 +4454,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4486,7 +4482,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4570,7 +4566,7 @@ dependencies = [
|
|||||||
"Inflector",
|
"Inflector",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -4798,9 +4794,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.92"
|
version = "0.2.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
@ -4808,16 +4804,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.92"
|
version = "0.2.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -4836,9 +4832,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.92"
|
version = "0.2.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@ -4846,22 +4842,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.92"
|
version = "0.2.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.92"
|
version = "0.2.91"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-lib"
|
name = "wasm-lib"
|
||||||
@ -5138,9 +5134,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "weezl"
|
name = "weezl"
|
||||||
version = "0.1.8"
|
version = "0.1.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
|
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
@ -5470,7 +5466,7 @@ checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -5490,7 +5486,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.52",
|
"syn 2.0.49",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -14,14 +14,14 @@ bson = { version = "2.9.0", features = ["uuid-1", "chrono"] }
|
|||||||
gloo-utils = "0.2.0"
|
gloo-utils = "0.2.0"
|
||||||
kcl-lib = { path = "kcl" }
|
kcl-lib = { path = "kcl" }
|
||||||
kittycad = { workspace = true }
|
kittycad = { workspace = true }
|
||||||
serde_json = "1.0.114"
|
serde_json = "1.0.108"
|
||||||
uuid = { version = "1.7.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.7.0", features = ["v4", "js", "serde"] }
|
||||||
wasm-bindgen = "0.2.91"
|
wasm-bindgen = "0.2.91"
|
||||||
wasm-bindgen-futures = "0.4.41"
|
wasm-bindgen-futures = "0.4.41"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
image = "0.24.9"
|
image = "0.24.8"
|
||||||
kittycad = { workspace = true, default-features = true }
|
kittycad = { workspace = true, default-features = true }
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
reqwest = { version = "0.11.24", default-features = false }
|
reqwest = { version = "0.11.24", default-features = false }
|
||||||
@ -31,7 +31,7 @@ uuid = { version = "1.7.0", features = ["v4", "js", "serde"] }
|
|||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
js-sys = "0.3.69"
|
js-sys = "0.3.68"
|
||||||
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
|
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
|
||||||
wasm-bindgen-futures = { version = "0.4.41", features = ["futures-core-03-stream"] }
|
wasm-bindgen-futures = { version = "0.4.41", features = ["futures-core-03-stream"] }
|
||||||
wasm-streams = "0.4.0"
|
wasm-streams = "0.4.0"
|
||||||
@ -58,12 +58,11 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
kittycad = { version = "0.2.59", default-features = false, features = ["js", "requests"] }
|
kittycad = { version = "0.2.54", default-features = false, features = ["js", "requests"] }
|
||||||
kittycad-execution-plan = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
|
kittycad-execution-plan = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
|
||||||
kittycad-execution-plan-macros = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
|
kittycad-execution-plan-traits = "0.1.10"
|
||||||
kittycad-execution-plan-traits = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
|
|
||||||
kittycad-modeling-cmds = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
|
|
||||||
kittycad-modeling-session = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
|
kittycad-modeling-session = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
|
||||||
|
kittycad-execution-plan-macros = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
|
||||||
|
|
||||||
[[test]]
|
[[test]]
|
||||||
name = "executor"
|
name = "executor"
|
||||||
@ -76,7 +75,4 @@ path = "tests/modify/main.rs"
|
|||||||
# Example: how to point modeling-api at a different repo (e.g. a branch or a local clone)
|
# 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."https://github.com/KittyCAD/modeling-api"]
|
||||||
# kittycad-execution-plan = { path = "../../../modeling-api/execution-plan" }
|
# kittycad-execution-plan = { path = "../../../modeling-api/execution-plan" }
|
||||||
#kittycad-execution-plan-macros = { path = "../../../modeling-api/execution-plan-macros" }
|
|
||||||
#kittycad-execution-plan-traits = { path = "../../../modeling-api/execution-plan-traits" }
|
|
||||||
#kittycad-modeling-cmds = { path = "../../../modeling-api/modeling-cmds" }
|
|
||||||
# kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }
|
# kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }
|
||||||
|
@ -19,7 +19,7 @@ quote = "1"
|
|||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
serde_tokenstream = "0.2"
|
serde_tokenstream = "0.2"
|
||||||
syn = { version = "2.0.52", features = ["full"] }
|
syn = { version = "2.0.49", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
expectorate = "1.1.0"
|
expectorate = "1.1.0"
|
||||||
|
@ -7,16 +7,12 @@ description = "A new executor for KCL which compiles to Execution Plans"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
kcl-lib = { path = "../kcl" }
|
kcl-lib = { path = "../kcl" }
|
||||||
kittycad = { workspace = true }
|
|
||||||
kittycad-execution-plan = { workspace = true }
|
kittycad-execution-plan = { workspace = true }
|
||||||
kittycad-execution-plan-traits = { workspace = true }
|
kittycad-execution-plan-traits = { workspace = true }
|
||||||
kittycad-execution-plan-macros = { workspace = true }
|
|
||||||
kittycad-modeling-cmds = { workspace = true }
|
|
||||||
kittycad-modeling-session = { workspace = true }
|
kittycad-modeling-session = { workspace = true }
|
||||||
thiserror = "1.0.57"
|
thiserror = "1.0.57"
|
||||||
tokio = { version = "1.36.0", features = ["macros", "rt"] }
|
tokio = { version = "1.36.0", features = ["macros", "rt"] }
|
||||||
uuid = "1.7"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1"
|
pretty_assertions = "1"
|
||||||
serde_json = "1.0.114"
|
serde_json = "1.0.113"
|
||||||
|
@ -103,11 +103,7 @@ impl BindingScope {
|
|||||||
("add".into(), EpBinding::from(KclFunction::Add(native_functions::Add))),
|
("add".into(), EpBinding::from(KclFunction::Add(native_functions::Add))),
|
||||||
(
|
(
|
||||||
"startSketchAt".into(),
|
"startSketchAt".into(),
|
||||||
EpBinding::from(KclFunction::StartSketchAt(native_functions::sketch::StartSketchAt)),
|
EpBinding::from(KclFunction::StartSketchAt(native_functions::StartSketchAt)),
|
||||||
),
|
|
||||||
(
|
|
||||||
"lineTo".into(),
|
|
||||||
EpBinding::from(KclFunction::LineTo(native_functions::sketch::LineTo)),
|
|
||||||
),
|
),
|
||||||
]),
|
]),
|
||||||
parent: None,
|
parent: None,
|
||||||
|
@ -45,13 +45,6 @@ pub enum CompileError {
|
|||||||
NoReturnStmt,
|
NoReturnStmt,
|
||||||
#[error("You used the %, which means \"substitute this argument for the value to the left in this |> pipeline\". But there is no such value, because you're not calling a pipeline.")]
|
#[error("You used the %, which means \"substitute this argument for the value to the left in this |> pipeline\". But there is no such value, because you're not calling a pipeline.")]
|
||||||
NotInPipeline,
|
NotInPipeline,
|
||||||
#[error("The function '{fn_name}' expects a parameter of type {expected} as argument number {arg_number} but you supplied {actual}")]
|
|
||||||
ArgWrongType {
|
|
||||||
fn_name: &'static str,
|
|
||||||
expected: &'static str,
|
|
||||||
actual: String,
|
|
||||||
arg_number: usize,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
@ -252,7 +252,6 @@ impl Planner {
|
|||||||
} = match callee {
|
} = match callee {
|
||||||
KclFunction::Id(f) => f.call(&mut self.next_addr, args)?,
|
KclFunction::Id(f) => f.call(&mut self.next_addr, args)?,
|
||||||
KclFunction::StartSketchAt(f) => f.call(&mut self.next_addr, args)?,
|
KclFunction::StartSketchAt(f) => f.call(&mut self.next_addr, args)?,
|
||||||
KclFunction::LineTo(f) => f.call(&mut self.next_addr, args)?,
|
|
||||||
KclFunction::Add(f) => f.call(&mut self.next_addr, args)?,
|
KclFunction::Add(f) => f.call(&mut self.next_addr, args)?,
|
||||||
KclFunction::UserDefined(f) => {
|
KclFunction::UserDefined(f) => {
|
||||||
let UserDefinedFunction {
|
let UserDefinedFunction {
|
||||||
@ -619,8 +618,7 @@ impl Eq for UserDefinedFunction {}
|
|||||||
#[cfg_attr(test, derive(Eq, PartialEq))]
|
#[cfg_attr(test, derive(Eq, PartialEq))]
|
||||||
enum KclFunction {
|
enum KclFunction {
|
||||||
Id(native_functions::Id),
|
Id(native_functions::Id),
|
||||||
StartSketchAt(native_functions::sketch::StartSketchAt),
|
StartSketchAt(native_functions::StartSketchAt),
|
||||||
LineTo(native_functions::sketch::LineTo),
|
|
||||||
Add(native_functions::Add),
|
Add(native_functions::Add),
|
||||||
UserDefined(UserDefinedFunction),
|
UserDefined(UserDefinedFunction),
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,12 @@
|
|||||||
//! This includes some of the stdlib, e.g. `startSketchAt`.
|
//! This includes some of the stdlib, e.g. `startSketchAt`.
|
||||||
//! But some other stdlib functions will be written in KCL.
|
//! But some other stdlib functions will be written in KCL.
|
||||||
|
|
||||||
|
use kcl_lib::std::sketch::PlaneData;
|
||||||
use kittycad_execution_plan::{BinaryArithmetic, Destination, Instruction};
|
use kittycad_execution_plan::{BinaryArithmetic, Destination, Instruction};
|
||||||
use kittycad_execution_plan_traits::Address;
|
use kittycad_execution_plan_traits::{Address, Value};
|
||||||
|
|
||||||
use crate::{CompileError, EpBinding, EvalPlan};
|
use crate::{CompileError, EpBinding, EvalPlan};
|
||||||
|
|
||||||
pub mod sketch;
|
|
||||||
|
|
||||||
/// The identity function. Always returns its first input.
|
/// The identity function. Always returns its first input.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(test, derive(Eq, PartialEq))]
|
#[cfg_attr(test, derive(Eq, PartialEq))]
|
||||||
@ -42,6 +41,34 @@ impl Callable for Id {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg_attr(test, derive(Eq, PartialEq))]
|
||||||
|
pub struct StartSketchAt;
|
||||||
|
|
||||||
|
impl Callable for StartSketchAt {
|
||||||
|
fn call(&self, next_addr: &mut Address, _args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
|
||||||
|
let mut instructions = Vec::new();
|
||||||
|
// Store the plane.
|
||||||
|
let plane = PlaneData::XY.into_parts();
|
||||||
|
instructions.push(Instruction::SetValue {
|
||||||
|
address: next_addr.offset_by(plane.len()),
|
||||||
|
value_parts: plane,
|
||||||
|
});
|
||||||
|
// TODO: Get the plane ID from global context.
|
||||||
|
// TODO: Send this command:
|
||||||
|
// ModelingCmd::SketchModeEnable {
|
||||||
|
// animated: false,
|
||||||
|
// ortho: false,
|
||||||
|
// plane_id: plane.id,
|
||||||
|
// // We pass in the normal for the plane here.
|
||||||
|
// disable_camera_with_plane: Some(plane.z_axis.clone().into()),
|
||||||
|
// },
|
||||||
|
// TODO: Send ModelingCmd::StartPath at the given point.
|
||||||
|
// TODO (maybe): Store the SketchGroup in KCEP memory.
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A test function that adds two numbers.
|
/// A test function that adds two numbers.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(test, derive(Eq, PartialEq))]
|
#[cfg_attr(test, derive(Eq, PartialEq))]
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
//! Native functions for sketching on the plane.
|
|
||||||
|
|
||||||
pub mod helpers;
|
|
||||||
pub mod stdlib_functions;
|
|
||||||
|
|
||||||
pub use stdlib_functions::{LineTo, StartSketchAt};
|
|
@ -1,137 +0,0 @@
|
|||||||
use kittycad_execution_plan::{api_request::ApiRequest, Destination, Instruction};
|
|
||||||
use kittycad_execution_plan_traits::{Address, InMemory};
|
|
||||||
use kittycad_modeling_cmds::{id::ModelingCmdId, ModelingCmdEndpoint};
|
|
||||||
|
|
||||||
use crate::{binding_scope::EpBinding, error::CompileError};
|
|
||||||
|
|
||||||
/// Emit instructions for an API call with no parameters.
|
|
||||||
pub fn no_arg_api_call(instrs: &mut Vec<Instruction>, endpoint: ModelingCmdEndpoint, cmd_id: ModelingCmdId) {
|
|
||||||
instrs.push(Instruction::ApiRequest(ApiRequest {
|
|
||||||
endpoint,
|
|
||||||
store_response: None,
|
|
||||||
arguments: vec![],
|
|
||||||
cmd_id,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Emit instructions for an API call with the given parameters.
|
|
||||||
/// The API parameters are stored in the EP memory stack.
|
|
||||||
/// So, they have to be pushed onto the stack in the right order,
|
|
||||||
/// i.e. the reverse order in which the API call's Rust struct defines the fields.
|
|
||||||
pub fn stack_api_call<const N: usize>(
|
|
||||||
instrs: &mut Vec<Instruction>,
|
|
||||||
endpoint: ModelingCmdEndpoint,
|
|
||||||
store_response: Option<Address>,
|
|
||||||
cmd_id: ModelingCmdId,
|
|
||||||
data: [Vec<kittycad_execution_plan_traits::Primitive>; N],
|
|
||||||
) {
|
|
||||||
let arguments = vec![InMemory::StackPop; data.len()];
|
|
||||||
instrs.extend(data.map(|data| Instruction::StackPush { data }));
|
|
||||||
instrs.push(Instruction::ApiRequest(ApiRequest {
|
|
||||||
endpoint,
|
|
||||||
store_response,
|
|
||||||
arguments,
|
|
||||||
cmd_id,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn single_binding(
|
|
||||||
b: EpBinding,
|
|
||||||
fn_name: &'static str,
|
|
||||||
expected: &'static str,
|
|
||||||
arg_number: usize,
|
|
||||||
) -> Result<Address, CompileError> {
|
|
||||||
match b {
|
|
||||||
EpBinding::Single(a) => Ok(a),
|
|
||||||
EpBinding::Sequence { .. } => Err(CompileError::ArgWrongType {
|
|
||||||
fn_name,
|
|
||||||
expected,
|
|
||||||
actual: "array".to_owned(),
|
|
||||||
arg_number,
|
|
||||||
}),
|
|
||||||
EpBinding::Map { .. } => Err(CompileError::ArgWrongType {
|
|
||||||
fn_name,
|
|
||||||
expected,
|
|
||||||
actual: "object".to_owned(),
|
|
||||||
arg_number,
|
|
||||||
}),
|
|
||||||
EpBinding::Function(_) => Err(CompileError::ArgWrongType {
|
|
||||||
fn_name,
|
|
||||||
expected,
|
|
||||||
actual: "function".to_owned(),
|
|
||||||
arg_number,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sequence_binding(
|
|
||||||
b: EpBinding,
|
|
||||||
fn_name: &'static str,
|
|
||||||
expected: &'static str,
|
|
||||||
arg_number: usize,
|
|
||||||
) -> Result<Vec<EpBinding>, CompileError> {
|
|
||||||
match b {
|
|
||||||
EpBinding::Sequence { elements, .. } => Ok(elements),
|
|
||||||
EpBinding::Single(_) => Err(CompileError::ArgWrongType {
|
|
||||||
fn_name,
|
|
||||||
expected,
|
|
||||||
actual: "single".to_owned(),
|
|
||||||
arg_number,
|
|
||||||
}),
|
|
||||||
EpBinding::Map { .. } => Err(CompileError::ArgWrongType {
|
|
||||||
fn_name,
|
|
||||||
expected,
|
|
||||||
actual: "object".to_owned(),
|
|
||||||
arg_number,
|
|
||||||
}),
|
|
||||||
EpBinding::Function(_) => Err(CompileError::ArgWrongType {
|
|
||||||
fn_name,
|
|
||||||
expected,
|
|
||||||
actual: "function".to_owned(),
|
|
||||||
arg_number,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Extract a 2D point from an argument to a Cabble.
|
|
||||||
pub fn arg_point2d(
|
|
||||||
arg: EpBinding,
|
|
||||||
fn_name: &'static str,
|
|
||||||
instructions: &mut Vec<Instruction>,
|
|
||||||
next_addr: &mut Address,
|
|
||||||
arg_number: usize,
|
|
||||||
) -> Result<Address, CompileError> {
|
|
||||||
let expected = "2D point (array with length 2)";
|
|
||||||
let elements = sequence_binding(arg, "startSketchAt", "an array of length 2", arg_number)?;
|
|
||||||
if elements.len() != 2 {
|
|
||||||
return Err(CompileError::ArgWrongType {
|
|
||||||
fn_name,
|
|
||||||
expected,
|
|
||||||
actual: format!("array of length {}", elements.len()),
|
|
||||||
arg_number: 0,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// KCL stores points as an array.
|
|
||||||
// KC API stores them as Rust objects laid flat out in memory.
|
|
||||||
let start = next_addr.offset_by(2);
|
|
||||||
let start_x = start;
|
|
||||||
let start_y = start + 1;
|
|
||||||
let start_z = start + 2;
|
|
||||||
instructions.extend([
|
|
||||||
Instruction::Copy {
|
|
||||||
source: single_binding(elements[0].clone(), "startSketchAt", "number", arg_number)?,
|
|
||||||
destination: Destination::Address(start_x),
|
|
||||||
length: 1,
|
|
||||||
},
|
|
||||||
Instruction::Copy {
|
|
||||||
source: single_binding(elements[1].clone(), "startSketchAt", "number", arg_number)?,
|
|
||||||
destination: Destination::Address(start_y),
|
|
||||||
length: 1,
|
|
||||||
},
|
|
||||||
Instruction::SetPrimitive {
|
|
||||||
address: start_z,
|
|
||||||
value: 0.0.into(),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
Ok(start)
|
|
||||||
}
|
|
@ -1,211 +0,0 @@
|
|||||||
use kittycad_execution_plan::{
|
|
||||||
api_request::ApiRequest,
|
|
||||||
sketch_types::{self, Axes, BasePath, Plane, SketchGroup},
|
|
||||||
Destination, Instruction,
|
|
||||||
};
|
|
||||||
use kittycad_execution_plan_traits::{Address, InMemory, Value};
|
|
||||||
use kittycad_modeling_cmds::{
|
|
||||||
shared::{Point3d, Point4d},
|
|
||||||
ModelingCmdEndpoint,
|
|
||||||
};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use super::helpers::{arg_point2d, no_arg_api_call, single_binding, stack_api_call};
|
|
||||||
use crate::{binding_scope::EpBinding, error::CompileError, native_functions::Callable, EvalPlan};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(Eq, PartialEq))]
|
|
||||||
pub struct LineTo;
|
|
||||||
|
|
||||||
impl Callable for LineTo {
|
|
||||||
fn call(&self, next_addr: &mut Address, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
|
|
||||||
let mut instructions = Vec::new();
|
|
||||||
let fn_name = "lineTo";
|
|
||||||
// Get both required params.
|
|
||||||
let mut args_iter = args.into_iter();
|
|
||||||
let Some(to) = args_iter.next() else {
|
|
||||||
return Err(CompileError::NotEnoughArgs {
|
|
||||||
fn_name: fn_name.into(),
|
|
||||||
required: 2,
|
|
||||||
actual: 0,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let Some(sketch_group) = args_iter.next() else {
|
|
||||||
return Err(CompileError::NotEnoughArgs {
|
|
||||||
fn_name: fn_name.into(),
|
|
||||||
required: 2,
|
|
||||||
actual: 1,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// Check the type of both required params.
|
|
||||||
let to = arg_point2d(to, fn_name, &mut instructions, next_addr, 0)?;
|
|
||||||
let sg = single_binding(sketch_group, fn_name, "sketch group", 1)?;
|
|
||||||
let id = Uuid::new_v4();
|
|
||||||
let start_of_line = next_addr.offset(1);
|
|
||||||
let length_of_3d_point = Point3d::<f64>::default().into_parts().len();
|
|
||||||
instructions.extend([
|
|
||||||
// Push the `to` 2D point onto the stack.
|
|
||||||
Instruction::Copy {
|
|
||||||
source: to,
|
|
||||||
length: 2,
|
|
||||||
destination: Destination::StackPush,
|
|
||||||
},
|
|
||||||
// Make it a 3D point.
|
|
||||||
Instruction::StackExtend { data: vec![0.0.into()] },
|
|
||||||
// Append the new path segment to memory.
|
|
||||||
// First comes its tag.
|
|
||||||
Instruction::SetPrimitive {
|
|
||||||
address: start_of_line,
|
|
||||||
value: "Line".to_owned().into(),
|
|
||||||
},
|
|
||||||
// Then its end
|
|
||||||
Instruction::StackPop {
|
|
||||||
destination: Some(start_of_line + 1),
|
|
||||||
},
|
|
||||||
// Then its `relative` field.
|
|
||||||
Instruction::SetPrimitive {
|
|
||||||
address: start_of_line + 1 + length_of_3d_point,
|
|
||||||
value: false.into(),
|
|
||||||
},
|
|
||||||
// Send the ExtendPath request
|
|
||||||
Instruction::ApiRequest(ApiRequest {
|
|
||||||
endpoint: ModelingCmdEndpoint::ExtendPath,
|
|
||||||
store_response: None,
|
|
||||||
arguments: vec![
|
|
||||||
// Path ID
|
|
||||||
InMemory::Address(sg + SketchGroup::path_id_offset()),
|
|
||||||
// Segment
|
|
||||||
InMemory::Address(start_of_line),
|
|
||||||
],
|
|
||||||
cmd_id: id.into(),
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// TODO: Create a new SketchGroup from the old one + add the new path, then store it.
|
|
||||||
Ok(EvalPlan {
|
|
||||||
instructions,
|
|
||||||
binding: EpBinding::Single(Address::ZERO + 9999),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
#[cfg_attr(test, derive(Eq, PartialEq))]
|
|
||||||
pub struct StartSketchAt;
|
|
||||||
|
|
||||||
impl Callable for StartSketchAt {
|
|
||||||
fn call(&self, next_addr: &mut Address, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
|
|
||||||
let mut instructions = Vec::new();
|
|
||||||
// First, before we send any API calls, let's validate the arguments to this function.
|
|
||||||
let mut args_iter = args.into_iter();
|
|
||||||
let Some(start) = args_iter.next() else {
|
|
||||||
return Err(CompileError::NotEnoughArgs {
|
|
||||||
fn_name: "startSketchAt".into(),
|
|
||||||
required: 1,
|
|
||||||
actual: 0,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let start_point = arg_point2d(start, "startSketchAt", &mut instructions, next_addr, 0)?;
|
|
||||||
let tag = match args_iter.next() {
|
|
||||||
None => None,
|
|
||||||
Some(b) => Some(single_binding(b, "startSketchAt", "a single string", 1)?),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Define some constants:
|
|
||||||
let axes = Axes {
|
|
||||||
x: Point3d { x: 1.0, y: 0.0, z: 0.0 },
|
|
||||||
y: Point3d { x: 0.0, y: 1.0, z: 0.0 },
|
|
||||||
z: Point3d { x: 0.0, y: 0.0, z: 1.0 },
|
|
||||||
};
|
|
||||||
let origin = Point3d::default();
|
|
||||||
|
|
||||||
// Now the function can start.
|
|
||||||
// First API call: make the plane.
|
|
||||||
let plane_id = Uuid::new_v4();
|
|
||||||
stack_api_call(
|
|
||||||
&mut instructions,
|
|
||||||
ModelingCmdEndpoint::MakePlane,
|
|
||||||
None,
|
|
||||||
plane_id.into(),
|
|
||||||
[
|
|
||||||
Some(true).into_parts(), // hide
|
|
||||||
vec![false.into()], // clobber
|
|
||||||
vec![60.0.into()], // size
|
|
||||||
axes.y.into_parts(),
|
|
||||||
axes.x.into_parts(),
|
|
||||||
origin.into_parts(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Next, enter sketch mode.
|
|
||||||
stack_api_call(
|
|
||||||
&mut instructions,
|
|
||||||
ModelingCmdEndpoint::SketchModeEnable,
|
|
||||||
None,
|
|
||||||
Uuid::new_v4().into(),
|
|
||||||
[
|
|
||||||
Some(axes.z).into_parts(),
|
|
||||||
vec![false.into()], // animated
|
|
||||||
vec![false.into()], // ortho mode
|
|
||||||
vec![plane_id.into()],
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Then start a path
|
|
||||||
let path_id = Uuid::new_v4();
|
|
||||||
no_arg_api_call(&mut instructions, ModelingCmdEndpoint::StartPath, path_id.into());
|
|
||||||
|
|
||||||
// Move the path pen to the given point.
|
|
||||||
instructions.push(Instruction::StackPush {
|
|
||||||
data: vec![path_id.into()],
|
|
||||||
});
|
|
||||||
instructions.push(Instruction::ApiRequest(ApiRequest {
|
|
||||||
endpoint: ModelingCmdEndpoint::MovePathPen,
|
|
||||||
store_response: None,
|
|
||||||
arguments: vec![InMemory::StackPop, InMemory::Address(start_point)],
|
|
||||||
cmd_id: Uuid::new_v4().into(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Starting a sketch creates a sketch group.
|
|
||||||
// Updating the sketch will update this sketch group later.
|
|
||||||
let sketch_group = SketchGroup {
|
|
||||||
id: path_id,
|
|
||||||
position: origin,
|
|
||||||
rotation: Point4d {
|
|
||||||
x: 0.0,
|
|
||||||
y: 0.0,
|
|
||||||
z: 0.0,
|
|
||||||
w: 1.0,
|
|
||||||
},
|
|
||||||
// TODO: Must copy the existing data (from the arguments to this KCL function)
|
|
||||||
// over these values after writing to memory.
|
|
||||||
path_first: BasePath {
|
|
||||||
from: Default::default(),
|
|
||||||
to: Default::default(),
|
|
||||||
name: Default::default(),
|
|
||||||
},
|
|
||||||
path_rest: Vec::new(),
|
|
||||||
on: sketch_types::SketchSurface::Plane(Plane {
|
|
||||||
id: plane_id,
|
|
||||||
value: sketch_types::PlaneType::XY,
|
|
||||||
origin,
|
|
||||||
axes,
|
|
||||||
}),
|
|
||||||
axes,
|
|
||||||
entity_id: Some(plane_id),
|
|
||||||
};
|
|
||||||
let sketch_group_primitives = sketch_group.clone().into_parts();
|
|
||||||
|
|
||||||
let sketch_group_addr = next_addr.offset_by(sketch_group_primitives.len());
|
|
||||||
instructions.push(Instruction::SetValue {
|
|
||||||
address: sketch_group_addr,
|
|
||||||
value_parts: sketch_group_primitives,
|
|
||||||
});
|
|
||||||
instructions.extend(sketch_group.set_base_path(sketch_group_addr, start_point, tag));
|
|
||||||
|
|
||||||
Ok(EvalPlan {
|
|
||||||
instructions,
|
|
||||||
binding: EpBinding::Single(sketch_group_addr),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,7 @@
|
|||||||
use std::{collections::HashMap, env};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use ep::{Destination, UnaryArithmetic};
|
use ep::{Destination, UnaryArithmetic};
|
||||||
use ept::{ListHeader, ObjectHeader};
|
use ept::{ListHeader, ObjectHeader};
|
||||||
use kittycad_modeling_session::SessionBuilder;
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -1045,86 +1044,6 @@ fn store_object_with_array_property() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn stdlib_cube_partial() {
|
|
||||||
let program = r#"
|
|
||||||
let cube = startSketchAt([0.0, 0.0])
|
|
||||||
|> lineTo([4.0, 0.0], %)
|
|
||||||
"#;
|
|
||||||
let (_plan, _scope) = must_plan(program);
|
|
||||||
let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
|
|
||||||
.ast()
|
|
||||||
.unwrap();
|
|
||||||
let client = test_client().await;
|
|
||||||
let _mem = crate::execute(ast, Some(client)).await.unwrap();
|
|
||||||
// use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat, ModelingCmd};
|
|
||||||
// let out = client
|
|
||||||
// .run_command(
|
|
||||||
// uuid::Uuid::new_v4().into(),
|
|
||||||
// each_cmd::TakeSnapshot {
|
|
||||||
// format: ImageFormat::Png,
|
|
||||||
// },
|
|
||||||
// )
|
|
||||||
// .await
|
|
||||||
// .unwrap();
|
|
||||||
// let out = match out {
|
|
||||||
// OkModelingCmdResponse::TakeSnapshot(b) => b,
|
|
||||||
// other => panic!("wrong output: {other:?}"),
|
|
||||||
// };
|
|
||||||
// let out: Vec<u8> = out.contents.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn test_client() -> Session {
|
|
||||||
let kittycad_api_token = env::var("KITTYCAD_API_TOKEN").expect("You must set $KITTYCAD_API_TOKEN");
|
|
||||||
let kittycad_api_client = kittycad::Client::new(kittycad_api_token);
|
|
||||||
let session_builder = SessionBuilder {
|
|
||||||
client: kittycad_api_client,
|
|
||||||
fps: Some(10),
|
|
||||||
unlocked_framerate: Some(false),
|
|
||||||
video_res_height: Some(720),
|
|
||||||
video_res_width: Some(1280),
|
|
||||||
buffer_reqs: None,
|
|
||||||
await_response_timeout: None,
|
|
||||||
};
|
|
||||||
match Session::start(session_builder).await {
|
|
||||||
Err(e) => match e {
|
|
||||||
kittycad::types::error::Error::InvalidRequest(s) => panic!("Request did not meet requirements {s}"),
|
|
||||||
kittycad::types::error::Error::CommunicationError(e) => {
|
|
||||||
panic!(" A server error either due to the data, or with the connection: {e}")
|
|
||||||
}
|
|
||||||
kittycad::types::error::Error::RequestError(e) => panic!("Could not build request: {e}"),
|
|
||||||
kittycad::types::error::Error::SerdeError { error, status } => {
|
|
||||||
panic!("Serde error (HTTP {status}): {error}")
|
|
||||||
}
|
|
||||||
kittycad::types::error::Error::InvalidResponsePayload { error, response } => {
|
|
||||||
panic!("Invalid response payload. Error {error}, response {response:?}")
|
|
||||||
}
|
|
||||||
kittycad::types::error::Error::Server { body, status } => panic!("Server error (HTTP {status}): {body}"),
|
|
||||||
kittycad::types::error::Error::UnexpectedResponse(resp) => {
|
|
||||||
let status = resp.status();
|
|
||||||
let url = resp.url().to_owned();
|
|
||||||
match resp.text().await {
|
|
||||||
Ok(body) => panic!(
|
|
||||||
"Unexpected response from KittyCAD API.
|
|
||||||
URL:{url}
|
|
||||||
HTTP {status}
|
|
||||||
---Body----
|
|
||||||
{body}"
|
|
||||||
),
|
|
||||||
Err(e) => panic!(
|
|
||||||
"Unexpected response from KittyCAD API.
|
|
||||||
URL:{url}
|
|
||||||
HTTP {status}
|
|
||||||
---Body could not be read, the error is----
|
|
||||||
{e}"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Ok(x) => x,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[ignore = "haven't done API calls or stdlib yet"]
|
#[ignore = "haven't done API calls or stdlib yet"]
|
||||||
#[test]
|
#[test]
|
||||||
fn stdlib_api_calls() {
|
fn stdlib_api_calls() {
|
||||||
|