Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
8eee3e1c58 | |||
b02529cae0 | |||
cf03021366 | |||
f52d2d55f1 | |||
59b1319e50 | |||
b07bbda20b | |||
3c01924184 | |||
bd16902f02 | |||
8c3af1a72a | |||
33f5d7740d | |||
b388f60648 | |||
8f4380be74 | |||
9ae8042a57 | |||
4b676d47da | |||
e6641e68f3 | |||
450afb1605 | |||
04433fecad | |||
6567e2ff92 | |||
91c32a7fe2 | |||
f735cdc22e |
19
.github/workflows/ci.yml
vendored
19
.github/workflows/ci.yml
vendored
@ -147,6 +147,14 @@ jobs:
|
|||||||
cp artifact/src-tauri/tauri.conf.json src-tauri/tauri.conf.json
|
cp artifact/src-tauri/tauri.conf.json src-tauri/tauri.conf.json
|
||||||
cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json
|
cp artifact/src-tauri/tauri.release.conf.json src-tauri/tauri.release.conf.json
|
||||||
|
|
||||||
|
- name: Update WebView2 on Windows
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
# Workaround needed to build the tauri windows app with matching edge version.
|
||||||
|
# From https://github.com/actions/runner-images/issues/9538
|
||||||
|
run: |
|
||||||
|
Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'setup.exe'
|
||||||
|
Start-Process -FilePath setup.exe -Verb RunAs -Wait
|
||||||
|
|
||||||
- name: Install ubuntu system dependencies
|
- name: Install ubuntu system dependencies
|
||||||
if: matrix.os == 'ubuntu-latest'
|
if: matrix.os == 'ubuntu-latest'
|
||||||
run: |
|
run: |
|
||||||
@ -364,6 +372,17 @@ jobs:
|
|||||||
E2E_APPLICATION: "./src-tauri/target/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/zoo-modeling-app"
|
E2E_APPLICATION: "./src-tauri/target/${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}/zoo-modeling-app"
|
||||||
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
|
|
||||||
|
- name: Run e2e tests (windows only)
|
||||||
|
if: ${{ matrix.os == 'windows-latest' && github.event_name != 'release' && github.event_name != 'schedule' }}
|
||||||
|
run: |
|
||||||
|
cargo install tauri-driver --force
|
||||||
|
yarn wdio run wdio.conf.ts
|
||||||
|
env:
|
||||||
|
E2E_APPLICATION: ".\\src-tauri\\target\\${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}\\Zoo Modeling App.exe"
|
||||||
|
KITTYCAD_API_TOKEN: ${{ env.BUILD_RELEASE == 'true' && secrets.KITTYCAD_API_TOKEN || secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||||
|
VITE_KC_API_BASE_URL: ${{ env.BUILD_RELEASE == 'true' && 'https://api.zoo.dev' || 'https://api.dev.zoo.dev' }}
|
||||||
|
E2E_TAURI_ENABLED: true
|
||||||
|
TS_NODE_COMPILER_OPTIONS: '{"module": "commonjs"}'
|
||||||
|
|
||||||
publish-apps-release:
|
publish-apps-release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
19
README.md
19
README.md
@ -309,6 +309,25 @@ PS: for the debug panel, the following JSON is useful for snapping the camera
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
### Tauri e2e tests
|
||||||
|
|
||||||
|
#### Windows (local only until the CI edge version mismatch is fixed)
|
||||||
|
|
||||||
|
```
|
||||||
|
yarn install
|
||||||
|
yarn build:wasm
|
||||||
|
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
|
||||||
|
yarn vite build --mode development
|
||||||
|
yarn tauri build --debug -b
|
||||||
|
$env:KITTYCAD_API_TOKEN="<YOUR_KITTYCAD_API_TOKEN>"
|
||||||
|
$env:VITE_KC_API_BASE_URL="https://api.dev.zoo.dev"
|
||||||
|
$env:E2E_TAURI_ENABLED="true"
|
||||||
|
$env:TS_NODE_COMPILER_OPTIONS='{"module": "commonjs"}'
|
||||||
|
$env:E2E_APPLICATION=".\src-tauri\target\debug\Zoo Modeling App.exe"
|
||||||
|
Stop-Process -Name msedgedriver
|
||||||
|
yarn wdio run wdio.conf.ts
|
||||||
|
```
|
||||||
|
|
||||||
## KCL
|
## KCL
|
||||||
|
|
||||||
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,10 @@
|
|||||||
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 { Paths, doExport, getUtils } from './test-utils'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import { spawn } from 'child_process'
|
import { spawn } from 'child_process'
|
||||||
import { APP_NAME, KCL_DEFAULT_LENGTH } from 'lib/constants'
|
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||||
import JSZip from 'jszip'
|
import JSZip from 'jszip'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates'
|
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates'
|
||||||
@ -99,78 +99,6 @@ const part001 = startSketchOn('-XZ')
|
|||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
await u.clearAndCloseDebugPanel()
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
interface Paths {
|
|
||||||
modelPath: string
|
|
||||||
imagePath: string
|
|
||||||
outputType: string
|
|
||||||
}
|
|
||||||
const doExport = async (
|
|
||||||
output: Models['OutputFormat_type']
|
|
||||||
): Promise<Paths> => {
|
|
||||||
await page.getByRole('button', { name: APP_NAME }).click()
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Export Part' })
|
|
||||||
).toBeVisible()
|
|
||||||
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) {
|
|
||||||
await page.getByTestId('arg-name-storage').waitFor({ timeout: 1000 })
|
|
||||||
await page.getByRole('button', { name: 'storage', exact: false }).click()
|
|
||||||
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()
|
|
||||||
let downloadCnt = 0
|
|
||||||
|
|
||||||
page.on('download', async (download) => {
|
|
||||||
if (downloadCnt === 0) {
|
|
||||||
downloadResolve1(download)
|
|
||||||
}
|
|
||||||
downloadCnt++
|
|
||||||
})
|
|
||||||
await page.getByRole('button', { name: 'Submit command' }).click()
|
|
||||||
|
|
||||||
// Handle download
|
|
||||||
const download = await downloadPromise1
|
|
||||||
const downloadLocationer = (extra = '', isImage = false) =>
|
|
||||||
`./e2e/playwright/export-snapshots/${output.type}-${
|
|
||||||
'storage' in output ? output.storage : ''
|
|
||||||
}${extra}.${isImage ? 'png' : output.type}`
|
|
||||||
const downloadLocation = downloadLocationer()
|
|
||||||
|
|
||||||
await download.saveAs(downloadLocation)
|
|
||||||
|
|
||||||
if (output.type === 'step') {
|
|
||||||
// stable timestamps for step files
|
|
||||||
const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
|
|
||||||
const newFileContents = fileContents.replace(
|
|
||||||
/[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+[0-9]+[0-9]\+[0-9]{2}:[0-9]{2}/g,
|
|
||||||
'1970-01-01T00:00:00.0+00:00'
|
|
||||||
)
|
|
||||||
await fsp.writeFile(downloadLocation, newFileContents)
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
modelPath: downloadLocation,
|
|
||||||
imagePath: downloadLocationer('', true),
|
|
||||||
outputType: output.type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const axisDirectionPair: Models['AxisDirectionPair_type'] = {
|
const axisDirectionPair: Models['AxisDirectionPair_type'] = {
|
||||||
axis: 'z',
|
axis: 'z',
|
||||||
direction: 'positive',
|
direction: 'positive',
|
||||||
@ -186,84 +114,114 @@ const part001 = startSketchOn('-XZ')
|
|||||||
// just note that only `type` and `storage` are used for selecting the drop downs is the app
|
// just note that only `type` and `storage` are used for selecting the drop downs is the app
|
||||||
// the rest are only there to make typescript happy
|
// the rest are only there to make typescript happy
|
||||||
exportLocations.push(
|
exportLocations.push(
|
||||||
await doExport({
|
await doExport(
|
||||||
type: 'step',
|
{
|
||||||
coords: sysType,
|
type: 'step',
|
||||||
})
|
coords: sysType,
|
||||||
|
},
|
||||||
|
page
|
||||||
|
)
|
||||||
)
|
)
|
||||||
exportLocations.push(
|
exportLocations.push(
|
||||||
await doExport({
|
await doExport(
|
||||||
type: 'ply',
|
{
|
||||||
coords: sysType,
|
type: 'ply',
|
||||||
selection: { type: 'default_scene' },
|
coords: sysType,
|
||||||
storage: 'ascii',
|
selection: { type: 'default_scene' },
|
||||||
units: 'in',
|
storage: 'ascii',
|
||||||
})
|
units: 'in',
|
||||||
|
},
|
||||||
|
page
|
||||||
|
)
|
||||||
)
|
)
|
||||||
exportLocations.push(
|
exportLocations.push(
|
||||||
await doExport({
|
await doExport(
|
||||||
type: 'ply',
|
{
|
||||||
storage: 'binary_little_endian',
|
type: 'ply',
|
||||||
coords: sysType,
|
storage: 'binary_little_endian',
|
||||||
selection: { type: 'default_scene' },
|
coords: sysType,
|
||||||
units: 'in',
|
selection: { type: 'default_scene' },
|
||||||
})
|
units: 'in',
|
||||||
|
},
|
||||||
|
page
|
||||||
|
)
|
||||||
)
|
)
|
||||||
exportLocations.push(
|
exportLocations.push(
|
||||||
await doExport({
|
await doExport(
|
||||||
type: 'ply',
|
{
|
||||||
storage: 'binary_big_endian',
|
type: 'ply',
|
||||||
coords: sysType,
|
storage: 'binary_big_endian',
|
||||||
selection: { type: 'default_scene' },
|
coords: sysType,
|
||||||
units: 'in',
|
selection: { type: 'default_scene' },
|
||||||
})
|
units: 'in',
|
||||||
|
},
|
||||||
|
page
|
||||||
|
)
|
||||||
)
|
)
|
||||||
exportLocations.push(
|
exportLocations.push(
|
||||||
await doExport({
|
await doExport(
|
||||||
type: 'stl',
|
{
|
||||||
storage: 'ascii',
|
type: 'stl',
|
||||||
coords: sysType,
|
storage: 'ascii',
|
||||||
units: 'in',
|
coords: sysType,
|
||||||
selection: { type: 'default_scene' },
|
units: 'in',
|
||||||
})
|
selection: { type: 'default_scene' },
|
||||||
|
},
|
||||||
|
page
|
||||||
|
)
|
||||||
)
|
)
|
||||||
exportLocations.push(
|
exportLocations.push(
|
||||||
await doExport({
|
await doExport(
|
||||||
type: 'stl',
|
{
|
||||||
storage: 'binary',
|
type: 'stl',
|
||||||
coords: sysType,
|
storage: 'binary',
|
||||||
units: 'in',
|
coords: sysType,
|
||||||
selection: { type: 'default_scene' },
|
units: 'in',
|
||||||
})
|
selection: { type: 'default_scene' },
|
||||||
|
},
|
||||||
|
page
|
||||||
|
)
|
||||||
)
|
)
|
||||||
exportLocations.push(
|
exportLocations.push(
|
||||||
await doExport({
|
await doExport(
|
||||||
// obj seems to be a little flaky, times out tests sometimes
|
{
|
||||||
type: 'obj',
|
// obj seems to be a little flaky, times out tests sometimes
|
||||||
coords: sysType,
|
type: 'obj',
|
||||||
units: 'in',
|
coords: sysType,
|
||||||
})
|
units: 'in',
|
||||||
|
},
|
||||||
|
page
|
||||||
|
)
|
||||||
)
|
)
|
||||||
exportLocations.push(
|
exportLocations.push(
|
||||||
await doExport({
|
await doExport(
|
||||||
type: 'gltf',
|
{
|
||||||
storage: 'embedded',
|
type: 'gltf',
|
||||||
presentation: 'pretty',
|
storage: 'embedded',
|
||||||
})
|
presentation: 'pretty',
|
||||||
|
},
|
||||||
|
page
|
||||||
|
)
|
||||||
)
|
)
|
||||||
exportLocations.push(
|
exportLocations.push(
|
||||||
await doExport({
|
await doExport(
|
||||||
type: 'gltf',
|
{
|
||||||
storage: 'binary',
|
type: 'gltf',
|
||||||
presentation: 'pretty',
|
storage: 'binary',
|
||||||
})
|
presentation: 'pretty',
|
||||||
|
},
|
||||||
|
page
|
||||||
|
)
|
||||||
)
|
)
|
||||||
exportLocations.push(
|
exportLocations.push(
|
||||||
await doExport({
|
await doExport(
|
||||||
type: 'gltf',
|
{
|
||||||
storage: 'standard',
|
type: 'gltf',
|
||||||
presentation: 'pretty',
|
storage: 'standard',
|
||||||
})
|
presentation: 'pretty',
|
||||||
|
},
|
||||||
|
page
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// close page to disconnect websocket since we can only have one open atm
|
// close page to disconnect websocket since we can only have one open atm
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB |
Binary file not shown.
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Binary file not shown.
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
@ -1,9 +1,11 @@
|
|||||||
import { test, expect, Page } from '@playwright/test'
|
import { test, expect, Page, Download } from '@playwright/test'
|
||||||
import { EngineCommand } from '../../src/lang/std/engineConnection'
|
import { EngineCommand } from '../../src/lang/std/engineConnection'
|
||||||
import fsp from 'fs/promises'
|
import fsp from 'fs/promises'
|
||||||
import pixelMatch from 'pixelmatch'
|
import pixelMatch from 'pixelmatch'
|
||||||
import { PNG } from 'pngjs'
|
import { PNG } from 'pngjs'
|
||||||
import { Protocol } from 'playwright-core/types/protocol'
|
import { Protocol } from 'playwright-core/types/protocol'
|
||||||
|
import type { Models } from '@kittycad/lib'
|
||||||
|
import { APP_NAME } from 'lib/constants'
|
||||||
|
|
||||||
async function waitForPageLoad(page: Page) {
|
async function waitForPageLoad(page: Page) {
|
||||||
// wait for 'Loading stream...' spinner
|
// wait for 'Loading stream...' spinner
|
||||||
@ -95,10 +97,10 @@ async function waitForCmdReceive(page: Page, commandType: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function getUtils(page: Page) {
|
export async function getUtils(page: Page) {
|
||||||
|
// Chrome devtools protocol session only works in Chromium
|
||||||
|
const browserType = page.context().browser()?.browserType().name()
|
||||||
const cdpSession =
|
const cdpSession =
|
||||||
process.platform === 'darwin'
|
browserType !== 'chromium' ? null : await page.context().newCDPSession(page)
|
||||||
? null
|
|
||||||
: await page.context().newCDPSession(page)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
waitForAuthSkipAppStart: () => waitForPageLoad(page),
|
waitForAuthSkipAppStart: () => waitForPageLoad(page),
|
||||||
@ -130,6 +132,19 @@ export async function getUtils(page: Page) {
|
|||||||
},
|
},
|
||||||
waitForCmdReceive: (commandType: string) =>
|
waitForCmdReceive: (commandType: string) =>
|
||||||
waitForCmdReceive(page, commandType),
|
waitForCmdReceive(page, commandType),
|
||||||
|
getSegmentBodyCoords: async (locator: string, px = 30) => {
|
||||||
|
const overlay = page.locator(locator)
|
||||||
|
const bbox = await overlay
|
||||||
|
.boundingBox()
|
||||||
|
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 }))
|
||||||
|
const angle = Number(await overlay.getAttribute('data-overlay-angle'))
|
||||||
|
const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px
|
||||||
|
const angleYOffset = Math.sin(((angle - 180) * Math.PI) / 180) * px
|
||||||
|
return {
|
||||||
|
x: bbox.x + angleXOffset,
|
||||||
|
y: bbox.y - angleYOffset,
|
||||||
|
}
|
||||||
|
},
|
||||||
getBoundingBox: async (locator: string) =>
|
getBoundingBox: async (locator: string) =>
|
||||||
page
|
page
|
||||||
.locator(locator)
|
.locator(locator)
|
||||||
@ -277,3 +292,77 @@ export const makeTemplate: (
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Paths {
|
||||||
|
modelPath: string
|
||||||
|
imagePath: string
|
||||||
|
outputType: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const doExport = async (
|
||||||
|
output: Models['OutputFormat_type'],
|
||||||
|
page: Page
|
||||||
|
): Promise<Paths> => {
|
||||||
|
await page.getByRole('button', { name: APP_NAME }).click()
|
||||||
|
await expect(page.getByRole('button', { name: 'Export Part' })).toBeVisible()
|
||||||
|
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) {
|
||||||
|
await page.getByTestId('arg-name-storage').waitFor({ timeout: 1000 })
|
||||||
|
await page.getByRole('button', { name: 'storage', exact: false }).click()
|
||||||
|
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()
|
||||||
|
let downloadCnt = 0
|
||||||
|
|
||||||
|
page.on('download', async (download) => {
|
||||||
|
if (downloadCnt === 0) {
|
||||||
|
downloadResolve1(download)
|
||||||
|
}
|
||||||
|
downloadCnt++
|
||||||
|
})
|
||||||
|
await page.getByRole('button', { name: 'Submit command' }).click()
|
||||||
|
|
||||||
|
// Handle download
|
||||||
|
const download = await downloadPromise1
|
||||||
|
const downloadLocationer = (extra = '', isImage = false) =>
|
||||||
|
`./e2e/playwright/export-snapshots/${output.type}-${
|
||||||
|
'storage' in output ? output.storage : ''
|
||||||
|
}${extra}.${isImage ? 'png' : output.type}`
|
||||||
|
const downloadLocation = downloadLocationer()
|
||||||
|
|
||||||
|
await download.saveAs(downloadLocation)
|
||||||
|
|
||||||
|
if (output.type === 'step') {
|
||||||
|
// stable timestamps for step files
|
||||||
|
const fileContents = await fsp.readFile(downloadLocation, 'utf-8')
|
||||||
|
const newFileContents = fileContents.replace(
|
||||||
|
/[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]+[0-9]+[0-9]\+[0-9]{2}:[0-9]{2}/g,
|
||||||
|
'1970-01-01T00:00:00.0+00:00'
|
||||||
|
)
|
||||||
|
await fsp.writeFile(downloadLocation, newFileContents)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
modelPath: downloadLocation,
|
||||||
|
imagePath: downloadLocationer('', true),
|
||||||
|
outputType: output.type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
import { browser, $, expect } from '@wdio/globals'
|
import { browser, $, expect } from '@wdio/globals'
|
||||||
import fs from 'fs/promises'
|
import fs from 'fs/promises'
|
||||||
|
import path from 'path'
|
||||||
|
import os from 'os'
|
||||||
|
|
||||||
const documentsDir = `${process.env.HOME}/Documents`
|
const isWin32 = os.platform() === 'win32'
|
||||||
const userSettingsDir = `${process.env.HOME}/.config/dev.zoo.modeling-app`
|
const documentsDir = path.join(os.homedir(), 'Documents')
|
||||||
const defaultProjectDir = `${documentsDir}/zoo-modeling-app-projects`
|
const userSettingsDir = path.join(
|
||||||
const newProjectDir = `${documentsDir}/a-different-directory`
|
os.homedir(),
|
||||||
const userCodeDir = '/tmp/kittycad_user_code'
|
'.config',
|
||||||
|
'dev.zoo.modeling-app'
|
||||||
|
)
|
||||||
|
const defaultProjectDir = path.join(documentsDir, 'zoo-modeling-app-projects')
|
||||||
|
const newProjectDir = path.join(documentsDir, 'a-different-directory')
|
||||||
|
const tmp = process.env.TEMP || '/tmp'
|
||||||
|
const userCodeDir = path.join(tmp, 'kittycad_user_code')
|
||||||
|
|
||||||
async function click(element: WebdriverIO.Element): Promise<void> {
|
async function click(element: WebdriverIO.Element): Promise<void> {
|
||||||
// Workaround for .click(), see https://github.com/tauri-apps/tauri/issues/6541
|
// Workaround for .click(), see https://github.com/tauri-apps/tauri/issues/6541
|
||||||
@ -24,7 +32,7 @@ async function setDatasetValue(
|
|||||||
await browser.execute(`arguments[0].dataset.${property} = "${value}"`, field)
|
await browser.execute(`arguments[0].dataset.${property} = "${value}"`, field)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ZMA (Tauri, Linux)', () => {
|
describe('ZMA (Tauri)', () => {
|
||||||
it('opens the auth page and signs in', async () => {
|
it('opens the auth page and signs in', async () => {
|
||||||
// Clean up filesystem from previous tests
|
// Clean up filesystem from previous tests
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
@ -42,9 +50,7 @@ describe('ZMA (Tauri, Linux)', () => {
|
|||||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||||
|
|
||||||
// Get from main.rs
|
// Get from main.rs
|
||||||
const userCode = await (
|
const userCode = await (await fs.readFile(userCodeDir)).toString()
|
||||||
await fs.readFile('/tmp/kittycad_user_code')
|
|
||||||
).toString()
|
|
||||||
console.log(`Found user code ${userCode}`)
|
console.log(`Found user code ${userCode}`)
|
||||||
|
|
||||||
// Device flow: verify
|
// Device flow: verify
|
||||||
@ -92,7 +98,12 @@ describe('ZMA (Tauri, Linux)', () => {
|
|||||||
* to be able to skip the folder selection dialog if data-testValue
|
* to be able to skip the folder selection dialog if data-testValue
|
||||||
* has a value, allowing us to test the input otherwise works.
|
* has a value, allowing us to test the input otherwise works.
|
||||||
*/
|
*/
|
||||||
await setDatasetValue(projectDirInput, 'testValue', newProjectDir)
|
// TODO: understand why we need to force double \ on Windows
|
||||||
|
await setDatasetValue(
|
||||||
|
projectDirInput,
|
||||||
|
'testValue',
|
||||||
|
isWin32 ? newProjectDir.replaceAll('\\', '\\\\') : newProjectDir
|
||||||
|
)
|
||||||
const projectDirButton = await $('[data-testid="project-directory-button"]')
|
const projectDirButton = await $('[data-testid="project-directory-button"]')
|
||||||
await click(projectDirButton)
|
await click(projectDirButton)
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500))
|
await new Promise((resolve) => setTimeout(resolve, 500))
|
||||||
@ -102,6 +113,15 @@ describe('ZMA (Tauri, Linux)', () => {
|
|||||||
const nameInput = await $('[data-testid="projects-defaultProjectName"]')
|
const nameInput = await $('[data-testid="projects-defaultProjectName"]')
|
||||||
expect(await nameInput.getValue()).toEqual('project-$nnn')
|
expect(await nameInput.getValue()).toEqual('project-$nnn')
|
||||||
|
|
||||||
|
// Setting it back (for back to back local tests)
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 5000))
|
||||||
|
await setDatasetValue(
|
||||||
|
projectDirInput,
|
||||||
|
'testValue',
|
||||||
|
isWin32 ? defaultProjectDir.replaceAll('\\', '\\\\') : newProjectDir
|
||||||
|
)
|
||||||
|
await click(projectDirButton)
|
||||||
|
|
||||||
const closeButton = await $('[data-testid="settings-close-button"]')
|
const closeButton = await $('[data-testid="settings-close-button"]')
|
||||||
await click(closeButton)
|
await click(closeButton)
|
||||||
})
|
})
|
||||||
@ -120,9 +140,15 @@ describe('ZMA (Tauri, Linux)', () => {
|
|||||||
it('opens the new file and expects a loading stream', async () => {
|
it('opens the new file and expects a loading stream', async () => {
|
||||||
const projectLink = await $('[data-testid="project-link"]')
|
const projectLink = await $('[data-testid="project-link"]')
|
||||||
await click(projectLink)
|
await click(projectLink)
|
||||||
const errorText = await $('[data-testid="unexpected-error"]')
|
if (isWin32) {
|
||||||
expect(await errorText.getText()).toContain('unexpected error')
|
// TODO: actually do something to check that the stream is up
|
||||||
await browser.execute('window.location.href = "tauri://localhost/home"')
|
await new Promise((resolve) => setTimeout(resolve, 5000))
|
||||||
|
} else {
|
||||||
|
const errorText = await $('[data-testid="unexpected-error"]')
|
||||||
|
expect(await errorText.getText()).toContain('unexpected error')
|
||||||
|
}
|
||||||
|
const base = isWin32 ? 'http://tauri.localhost' : 'tauri://localhost'
|
||||||
|
await browser.execute(`window.location.href = "${base}/home"`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('signs out', async () => {
|
it('signs out', async () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.21.9",
|
"version": "0.22.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.16.0",
|
"@codemirror/autocomplete": "^6.16.0",
|
||||||
|
15
src-tauri/Cargo.lock
generated
15
src-tauri/Cargo.lock
generated
@ -2602,7 +2602,7 @@ dependencies = [
|
|||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"winnow 0.5.40",
|
"winnow 0.5.40",
|
||||||
"zip 1.3.0",
|
"zip 2.1.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4500,9 +4500,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.202"
|
version = "1.0.203"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
|
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@ -4518,9 +4518,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.202"
|
version = "1.0.203"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
|
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -7106,15 +7106,16 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "1.3.0"
|
version = "2.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1f4a27345eb6f7aa7bd015ba7eb4175fa4e1b462a29874b779e0bbcf96c6ac7"
|
checksum = "1dd56a4d5921bc2f99947ac5b3abe5f510b1be7376fdc5e9fce4a23c6a93e87c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arbitrary",
|
"arbitrary",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"indexmap 2.2.6",
|
"indexmap 2.2.6",
|
||||||
|
"memchr",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -267,7 +267,15 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
|
|||||||
let e2e_tauri_enabled = env::var("E2E_TAURI_ENABLED").is_ok();
|
let e2e_tauri_enabled = env::var("E2E_TAURI_ENABLED").is_ok();
|
||||||
if e2e_tauri_enabled {
|
if e2e_tauri_enabled {
|
||||||
log::warn!("E2E_TAURI_ENABLED is set, won't open {} externally", auth_uri.secret());
|
log::warn!("E2E_TAURI_ENABLED is set, won't open {} externally", auth_uri.secret());
|
||||||
tokio::fs::write("/tmp/kittycad_user_code", details.user_code().secret())
|
let mut temp = String::from("/tmp");
|
||||||
|
// Overwrite with Windows variable
|
||||||
|
match env::var("TEMP") {
|
||||||
|
Ok(val) => temp = val,
|
||||||
|
Err(_e) => println!("Fallback to default /tmp"),
|
||||||
|
}
|
||||||
|
let path = Path::new(&temp).join("kittycad_user_code");
|
||||||
|
println!("Writing to {}", path.to_string_lossy());
|
||||||
|
tokio::fs::write(path, details.user_code().secret())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
|
||||||
} else {
|
} else {
|
||||||
|
@ -74,5 +74,5 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"version": "0.21.9"
|
"version": "0.22.0"
|
||||||
}
|
}
|
||||||
|
@ -531,8 +531,7 @@ const ConstraintSymbol = ({
|
|||||||
varNameMap[_type as LineInputsType]?.implicitConstraintDesc
|
varNameMap[_type as LineInputsType]?.implicitConstraintDesc
|
||||||
|
|
||||||
const node = useMemo(
|
const node = useMemo(
|
||||||
() =>
|
() => getNodeFromPath<Value>(kclManager.ast, pathToNode).node,
|
||||||
getNodeFromPath<Value>(parse(recast(kclManager.ast)), pathToNode).node,
|
|
||||||
[kclManager.ast, pathToNode]
|
[kclManager.ast, pathToNode]
|
||||||
)
|
)
|
||||||
const range: SourceRange = node ? [node.start, node.end] : [0, 0]
|
const range: SourceRange = node ? [node.start, node.end] : [0, 0]
|
||||||
|
@ -39,6 +39,7 @@ export function ActionButtonDropdown({
|
|||||||
onClick={item.onClick}
|
onClick={item.onClick}
|
||||||
className="block px-3 py-1 hover:bg-primary/10 dark:hover:bg-chalkboard-80 border-0 m-0 text-sm w-full rounded-none text-left disabled:!bg-transparent dark:disabled:text-chalkboard-60"
|
className="block px-3 py-1 hover:bg-primary/10 dark:hover:bg-chalkboard-80 border-0 m-0 text-sm w-full rounded-none text-left disabled:!bg-transparent dark:disabled:text-chalkboard-60"
|
||||||
disabled={item.disabled}
|
disabled={item.disabled}
|
||||||
|
data-testid={item.label}
|
||||||
>
|
>
|
||||||
<span className="capitalize">{item.label}</span>
|
<span className="capitalize">{item.label}</span>
|
||||||
{item.shortcut && (
|
{item.shortcut && (
|
||||||
|
@ -214,13 +214,17 @@ export const CreateNewVariable = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<label htmlFor="create-new-variable" className="block mt-3 font-mono">
|
<label
|
||||||
|
htmlFor="create-new-variable"
|
||||||
|
className="block mt-3 font-mono text-chalkboard-90"
|
||||||
|
>
|
||||||
Create new variable
|
Create new variable
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-1 flex gap-2 items-center">
|
<div className="mt-1 flex gap-2 items-center">
|
||||||
{showCheckbox && (
|
{showCheckbox && (
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
data-testid="create-new-variable-checkbox"
|
||||||
checked={shouldCreateVariable}
|
checked={shouldCreateVariable}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setShouldCreateVariable(e.target.checked)
|
setShouldCreateVariable(e.target.checked)
|
||||||
|
@ -11,7 +11,10 @@ import {
|
|||||||
import { SetSelections, modelingMachine } from 'machines/modelingMachine'
|
import { SetSelections, modelingMachine } from 'machines/modelingMachine'
|
||||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
import {
|
||||||
|
isCursorInSketchCommandRange,
|
||||||
|
updatePathToNodeFromMap,
|
||||||
|
} from 'lang/util'
|
||||||
import {
|
import {
|
||||||
kclManager,
|
kclManager,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
@ -150,7 +153,7 @@ export const ModelingMachineProvider = ({
|
|||||||
])
|
])
|
||||||
const pathToNode = parent?.userData?.pathToNode
|
const pathToNode = parent?.userData?.pathToNode
|
||||||
const pathToNodeString = JSON.stringify(pathToNode)
|
const pathToNodeString = JSON.stringify(pathToNode)
|
||||||
if (!parent || !pathToNode) return {}
|
if (!parent || !pathToNode) return segmentHoverMap
|
||||||
if (segmentHoverMap[pathToNodeString] !== undefined)
|
if (segmentHoverMap[pathToNodeString] !== undefined)
|
||||||
clearTimeout(segmentHoverMap[JSON.stringify(pathToNode)])
|
clearTimeout(segmentHoverMap[JSON.stringify(pathToNode)])
|
||||||
return {
|
return {
|
||||||
@ -218,9 +221,8 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
),
|
),
|
||||||
'Set selection': assign(({ selectionRanges }, event) => {
|
'Set selection': assign(({ selectionRanges, sketchDetails }, event) => {
|
||||||
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
|
const setSelections = event.data as SetSelections // this was needed for ts after adding 'Set selection' action to on done modal events
|
||||||
const setSelections = event.data
|
|
||||||
if (!editorManager.editorView) return {}
|
if (!editorManager.editorView) return {}
|
||||||
const dispatchSelection = (selection?: EditorSelection) => {
|
const dispatchSelection = (selection?: EditorSelection) => {
|
||||||
if (!selection) return // TODO less of hack for the below please
|
if (!selection) return // TODO less of hack for the below please
|
||||||
@ -307,11 +309,29 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges: selections,
|
selectionRanges: selections,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (setSelections.selectionType === 'completeSelection') {
|
||||||
|
editorManager.selectRange(setSelections.selection)
|
||||||
|
if (!sketchDetails)
|
||||||
|
return {
|
||||||
|
selectionRanges: setSelections.selection,
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
selectionRanges: setSelections.selection,
|
||||||
|
sketchDetails: {
|
||||||
|
...sketchDetails,
|
||||||
|
sketchPathToNode:
|
||||||
|
setSelections.updatedPathToNode ||
|
||||||
|
sketchDetails?.sketchPathToNode ||
|
||||||
|
[],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {}
|
return {}
|
||||||
}),
|
}),
|
||||||
'Engine export': (_, event) => {
|
'Engine export': async (_, event) => {
|
||||||
if (event.type !== 'Export' || TEST) return
|
if (event.type !== 'Export' || TEST) return
|
||||||
|
console.log('exporting', event.data)
|
||||||
const format = {
|
const format = {
|
||||||
...event.data,
|
...event.data,
|
||||||
} as Partial<Models['OutputFormat_type']>
|
} as Partial<Models['OutputFormat_type']>
|
||||||
@ -355,9 +375,16 @@ export const ModelingMachineProvider = ({
|
|||||||
format.selection = { type: 'default_scene' }
|
format.selection = { type: 'default_scene' }
|
||||||
}
|
}
|
||||||
|
|
||||||
exportFromEngine({
|
toast.promise(
|
||||||
format: format as Models['OutputFormat_type'],
|
exportFromEngine({
|
||||||
}).catch((e) => toast.error('Error while exporting', e)) // TODO I think we need to throw the error from engineCommandManager
|
format: format as Models['OutputFormat_type'],
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
loading: 'Exporting...',
|
||||||
|
success: 'Exported successfully',
|
||||||
|
error: 'Error while exporting',
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
guards: {
|
guards: {
|
||||||
@ -481,13 +508,26 @@ export const ModelingMachineProvider = ({
|
|||||||
},
|
},
|
||||||
'Get horizontal info': async ({
|
'Get horizontal info': async ({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
sketchDetails,
|
||||||
}): Promise<SetSelections> => {
|
}): Promise<SetSelections> => {
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap } =
|
||||||
await applyConstraintHorzVertDistance({
|
await applyConstraintHorzVertDistance({
|
||||||
constraint: 'setHorzDistance',
|
constraint: 'setHorzDistance',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
await kclManager.updateAst(modifiedAst, true)
|
const _modifiedAst = parse(recast(modifiedAst))
|
||||||
|
if (!sketchDetails) throw new Error('No sketch details')
|
||||||
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
|
sketchDetails.sketchPathToNode,
|
||||||
|
pathToNodeMap
|
||||||
|
)
|
||||||
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
|
updatedPathToNode,
|
||||||
|
_modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection: pathMapToSelections(
|
selection: pathMapToSelections(
|
||||||
@ -495,17 +535,31 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
pathToNodeMap
|
pathToNodeMap
|
||||||
),
|
),
|
||||||
|
updatedPathToNode,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Get vertical info': async ({
|
'Get vertical info': async ({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
sketchDetails,
|
||||||
}): Promise<SetSelections> => {
|
}): Promise<SetSelections> => {
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap } =
|
||||||
await applyConstraintHorzVertDistance({
|
await applyConstraintHorzVertDistance({
|
||||||
constraint: 'setVertDistance',
|
constraint: 'setVertDistance',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
await kclManager.updateAst(modifiedAst, true)
|
const _modifiedAst = parse(recast(modifiedAst))
|
||||||
|
if (!sketchDetails) throw new Error('No sketch details')
|
||||||
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
|
sketchDetails.sketchPathToNode,
|
||||||
|
pathToNodeMap
|
||||||
|
)
|
||||||
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
|
updatedPathToNode,
|
||||||
|
_modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection: pathMapToSelections(
|
selection: pathMapToSelections(
|
||||||
@ -513,10 +567,12 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
pathToNodeMap
|
pathToNodeMap
|
||||||
),
|
),
|
||||||
|
updatedPathToNode,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Get angle info': async ({
|
'Get angle info': async ({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
sketchDetails,
|
||||||
}): Promise<SetSelections> => {
|
}): Promise<SetSelections> => {
|
||||||
const { modifiedAst, pathToNodeMap } = await (angleBetweenInfo({
|
const { modifiedAst, pathToNodeMap } = await (angleBetweenInfo({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -528,22 +584,48 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
angleOrLength: 'setAngle',
|
angleOrLength: 'setAngle',
|
||||||
}))
|
}))
|
||||||
await kclManager.updateAst(modifiedAst, true)
|
const _modifiedAst = parse(recast(modifiedAst))
|
||||||
|
if (!sketchDetails) throw new Error('No sketch details')
|
||||||
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
|
sketchDetails.sketchPathToNode,
|
||||||
|
pathToNodeMap
|
||||||
|
)
|
||||||
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
|
updatedPathToNode,
|
||||||
|
_modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection: pathMapToSelections(
|
selection: pathMapToSelections(
|
||||||
kclManager.ast,
|
_modifiedAst,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
pathToNodeMap
|
pathToNodeMap
|
||||||
),
|
),
|
||||||
|
updatedPathToNode,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Get length info': async ({
|
'Get length info': async ({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
sketchDetails,
|
||||||
}): Promise<SetSelections> => {
|
}): Promise<SetSelections> => {
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap } =
|
||||||
await applyConstraintAngleLength({ selectionRanges })
|
await applyConstraintAngleLength({ selectionRanges })
|
||||||
await kclManager.updateAst(modifiedAst, true)
|
const _modifiedAst = parse(recast(modifiedAst))
|
||||||
|
if (!sketchDetails) throw new Error('No sketch details')
|
||||||
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
|
sketchDetails.sketchPathToNode,
|
||||||
|
pathToNodeMap
|
||||||
|
)
|
||||||
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
|
updatedPathToNode,
|
||||||
|
_modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection: pathMapToSelections(
|
selection: pathMapToSelections(
|
||||||
@ -551,17 +633,31 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
pathToNodeMap
|
pathToNodeMap
|
||||||
),
|
),
|
||||||
|
updatedPathToNode,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Get perpendicular distance info': async ({
|
'Get perpendicular distance info': async ({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
sketchDetails,
|
||||||
}): Promise<SetSelections> => {
|
}): Promise<SetSelections> => {
|
||||||
const { modifiedAst, pathToNodeMap } = await applyConstraintIntersect(
|
const { modifiedAst, pathToNodeMap } = await applyConstraintIntersect(
|
||||||
{
|
{
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
await kclManager.updateAst(modifiedAst, true)
|
const _modifiedAst = parse(recast(modifiedAst))
|
||||||
|
if (!sketchDetails) throw new Error('No sketch details')
|
||||||
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
|
sketchDetails.sketchPathToNode,
|
||||||
|
pathToNodeMap
|
||||||
|
)
|
||||||
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
|
updatedPathToNode,
|
||||||
|
_modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection: pathMapToSelections(
|
selection: pathMapToSelections(
|
||||||
@ -569,17 +665,31 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
pathToNodeMap
|
pathToNodeMap
|
||||||
),
|
),
|
||||||
|
updatedPathToNode,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Get ABS X info': async ({
|
'Get ABS X info': async ({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
sketchDetails,
|
||||||
}): Promise<SetSelections> => {
|
}): Promise<SetSelections> => {
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap } =
|
||||||
await applyConstraintAbsDistance({
|
await applyConstraintAbsDistance({
|
||||||
constraint: 'xAbs',
|
constraint: 'xAbs',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
await kclManager.updateAst(modifiedAst, true)
|
const _modifiedAst = parse(recast(modifiedAst))
|
||||||
|
if (!sketchDetails) throw new Error('No sketch details')
|
||||||
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
|
sketchDetails.sketchPathToNode,
|
||||||
|
pathToNodeMap
|
||||||
|
)
|
||||||
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
|
updatedPathToNode,
|
||||||
|
_modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection: pathMapToSelections(
|
selection: pathMapToSelections(
|
||||||
@ -587,17 +697,31 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
pathToNodeMap
|
pathToNodeMap
|
||||||
),
|
),
|
||||||
|
updatedPathToNode,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Get ABS Y info': async ({
|
'Get ABS Y info': async ({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
sketchDetails,
|
||||||
}): Promise<SetSelections> => {
|
}): Promise<SetSelections> => {
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap } =
|
||||||
await applyConstraintAbsDistance({
|
await applyConstraintAbsDistance({
|
||||||
constraint: 'yAbs',
|
constraint: 'yAbs',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
await kclManager.updateAst(modifiedAst, true)
|
const _modifiedAst = parse(recast(modifiedAst))
|
||||||
|
if (!sketchDetails) throw new Error('No sketch details')
|
||||||
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
|
sketchDetails.sketchPathToNode,
|
||||||
|
pathToNodeMap
|
||||||
|
)
|
||||||
|
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||||
|
updatedPathToNode,
|
||||||
|
_modifiedAst,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection: pathMapToSelections(
|
selection: pathMapToSelections(
|
||||||
@ -605,6 +729,7 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
pathToNodeMap
|
pathToNodeMap
|
||||||
),
|
),
|
||||||
|
updatedPathToNode,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Get convert to variable info': async ({ sketchDetails }, { data }) => {
|
'Get convert to variable info': async ({ sketchDetails }, { data }) => {
|
||||||
@ -658,6 +783,19 @@ export const ModelingMachineProvider = ({
|
|||||||
editorManager.selectionRanges = modelingState.context.selectionRanges
|
editorManager.selectionRanges = modelingState.context.selectionRanges
|
||||||
}, [modelingState.context.selectionRanges])
|
}, [modelingState.context.selectionRanges])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const offlineCallback = () => {
|
||||||
|
// If we are in sketch mode we need to exit it.
|
||||||
|
// TODO: how do i check if we are in a sketch mode, I only want to call
|
||||||
|
// this then.
|
||||||
|
modelingSend({ type: 'Cancel' })
|
||||||
|
}
|
||||||
|
window.addEventListener('offline', offlineCallback)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('offline', offlineCallback)
|
||||||
|
}
|
||||||
|
}, [modelingSend])
|
||||||
|
|
||||||
useStateMachineCommands({
|
useStateMachineCommands({
|
||||||
machineId: 'modeling',
|
machineId: 'modeling',
|
||||||
state: modelingState,
|
state: modelingState,
|
||||||
|
@ -31,43 +31,6 @@ const projectWellFormed = {
|
|||||||
} satisfies Project
|
} satisfies Project
|
||||||
|
|
||||||
describe('ProjectSidebarMenu tests', () => {
|
describe('ProjectSidebarMenu tests', () => {
|
||||||
test('Renders the project name', () => {
|
|
||||||
render(
|
|
||||||
<BrowserRouter>
|
|
||||||
<CommandBarProvider>
|
|
||||||
<SettingsAuthProviderJest>
|
|
||||||
<ProjectSidebarMenu project={projectWellFormed} enableMenu={true} />
|
|
||||||
</SettingsAuthProviderJest>
|
|
||||||
</CommandBarProvider>
|
|
||||||
</BrowserRouter>
|
|
||||||
)
|
|
||||||
|
|
||||||
fireEvent.click(screen.getByTestId('project-sidebar-toggle'))
|
|
||||||
|
|
||||||
expect(screen.getByTestId('projectName')).toHaveTextContent(
|
|
||||||
projectWellFormed.name
|
|
||||||
)
|
|
||||||
expect(screen.getByTestId('createdAt')).toHaveTextContent(
|
|
||||||
`Created ${now.toLocaleDateString()}`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Renders app name if given no project', () => {
|
|
||||||
render(
|
|
||||||
<BrowserRouter>
|
|
||||||
<CommandBarProvider>
|
|
||||||
<SettingsAuthProviderJest>
|
|
||||||
<ProjectSidebarMenu enableMenu={true} />
|
|
||||||
</SettingsAuthProviderJest>
|
|
||||||
</CommandBarProvider>
|
|
||||||
</BrowserRouter>
|
|
||||||
)
|
|
||||||
|
|
||||||
fireEvent.click(screen.getByTestId('project-sidebar-toggle'))
|
|
||||||
|
|
||||||
expect(screen.getByTestId('projectName')).toHaveTextContent(APP_NAME)
|
|
||||||
})
|
|
||||||
|
|
||||||
test('Disables popover menu by default', () => {
|
test('Disables popover menu by default', () => {
|
||||||
render(
|
render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
|
@ -138,41 +138,7 @@ function ProjectMenuPopover({
|
|||||||
>
|
>
|
||||||
{({ close }) => (
|
{({ close }) => (
|
||||||
<>
|
<>
|
||||||
<div className="flex items-center gap-4 px-4 py-3">
|
<div className="flex flex-col gap-2 p-4">
|
||||||
<div>
|
|
||||||
<p className="m-0 text-mono" data-testid="projectName">
|
|
||||||
{project?.name ? project.name : APP_NAME}
|
|
||||||
</p>
|
|
||||||
{project?.metadata && project.metadata.created && (
|
|
||||||
<p
|
|
||||||
className="m-0 text-xs text-chalkboard-80 dark:text-chalkboard-40"
|
|
||||||
data-testid="createdAt"
|
|
||||||
>
|
|
||||||
Created{' '}
|
|
||||||
{new Date(project.metadata.created).toLocaleDateString()}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isTauri() ? (
|
|
||||||
<FileTree
|
|
||||||
file={file}
|
|
||||||
className="overflow-hidden border-0 border-y border-chalkboard-30 dark:border-chalkboard-80"
|
|
||||||
onNavigateToFile={close}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className="flex-1 p-4 text-sm overflow-hidden">
|
|
||||||
<p>
|
|
||||||
In the browser version of Modeling App you can only have one
|
|
||||||
part, and the code is stored in your browser's storage.
|
|
||||||
</p>
|
|
||||||
<p className="my-6">
|
|
||||||
Please save any code you want to keep more permanently, as
|
|
||||||
your browser's storage is not guaranteed to be permanent.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className="flex flex-col gap-2 p-4 dark:bg-chalkboard-90">
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
iconStart={{ icon: 'exportFile', className: 'p-1' }}
|
iconStart={{ icon: 'exportFile', className: 'p-1' }}
|
||||||
|
@ -140,7 +140,11 @@ export async function applyConstraintIntersect({
|
|||||||
value: valueUsedInTransform,
|
value: valueUsedInTransform,
|
||||||
initialVariableName: 'offset',
|
initialVariableName: 'offset',
|
||||||
})
|
})
|
||||||
if (segName === tagInfo?.tag && Number(value) === valueUsedInTransform) {
|
if (
|
||||||
|
!variableName &&
|
||||||
|
segName === tagInfo?.tag &&
|
||||||
|
Number(value) === valueUsedInTransform
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
@ -169,6 +173,10 @@ export async function applyConstraintIntersect({
|
|||||||
createVariableDeclaration(variableName, valueNode)
|
createVariableDeclaration(variableName, valueNode)
|
||||||
)
|
)
|
||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
|
Object.values(_pathToNodeMap).forEach((pathToNode) => {
|
||||||
|
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||||
|
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _modifiedAst,
|
modifiedAst: _modifiedAst,
|
||||||
|
@ -120,6 +120,10 @@ export async function applyConstraintAbsDistance({
|
|||||||
createVariableDeclaration(variableName, valueNode)
|
createVariableDeclaration(variableName, valueNode)
|
||||||
)
|
)
|
||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
|
Object.values(pathToNodeMap).forEach((pathToNode) => {
|
||||||
|
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||||
|
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return { modifiedAst: _modifiedAst, pathToNodeMap }
|
return { modifiedAst: _modifiedAst, pathToNodeMap }
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,11 @@ export async function applyConstraintAngleBetween({
|
|||||||
value: valueUsedInTransform,
|
value: valueUsedInTransform,
|
||||||
initialVariableName: 'angle',
|
initialVariableName: 'angle',
|
||||||
} as any)
|
} as any)
|
||||||
if (segName === tagInfo?.tag && Number(value) === valueUsedInTransform) {
|
if (
|
||||||
|
segName === tagInfo?.tag &&
|
||||||
|
Number(value) === valueUsedInTransform &&
|
||||||
|
!variableName
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
@ -128,6 +132,10 @@ export async function applyConstraintAngleBetween({
|
|||||||
createVariableDeclaration(variableName, valueNode)
|
createVariableDeclaration(variableName, valueNode)
|
||||||
)
|
)
|
||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
|
Object.values(_pathToNodeMap).forEach((pathToNode) => {
|
||||||
|
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||||
|
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _modifiedAst,
|
modifiedAst: _modifiedAst,
|
||||||
|
@ -106,7 +106,11 @@ export async function applyConstraintHorzVertDistance({
|
|||||||
value: valueUsedInTransform,
|
value: valueUsedInTransform,
|
||||||
initialVariableName: constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
|
initialVariableName: constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
|
||||||
} as any)
|
} as any)
|
||||||
if (segName === tagInfo?.tag && Number(value) === valueUsedInTransform) {
|
if (
|
||||||
|
!variableName &&
|
||||||
|
segName === tagInfo?.tag &&
|
||||||
|
Number(value) === valueUsedInTransform
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
@ -133,6 +137,10 @@ export async function applyConstraintHorzVertDistance({
|
|||||||
createVariableDeclaration(variableName, valueNode)
|
createVariableDeclaration(variableName, valueNode)
|
||||||
)
|
)
|
||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
|
Object.values(pathToNodeMap).forEach((pathToNode) => {
|
||||||
|
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||||
|
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _modifiedAst,
|
modifiedAst: _modifiedAst,
|
||||||
|
@ -138,13 +138,17 @@ export async function applyConstraintAngleLength({
|
|||||||
createVariableDeclaration(variableName, valueNode)
|
createVariableDeclaration(variableName, valueNode)
|
||||||
)
|
)
|
||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
|
Object.values(pathToNodeMap).forEach((pathToNode) => {
|
||||||
|
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
|
||||||
|
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
modifiedAst: _modifiedAst,
|
modifiedAst: _modifiedAst,
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('erorr', e)
|
console.log('error', e)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -994,6 +994,10 @@ export class EngineCommandManager {
|
|||||||
engineConnection?: EngineConnection
|
engineConnection?: EngineConnection
|
||||||
defaultPlanes: DefaultPlanes | null = null
|
defaultPlanes: DefaultPlanes | null = null
|
||||||
commandLogs: CommandLog[] = []
|
commandLogs: CommandLog[] = []
|
||||||
|
pendingExport?: {
|
||||||
|
resolve: (filename?: string) => void
|
||||||
|
reject: (reason: any) => void
|
||||||
|
}
|
||||||
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
_commandLogCallBack: (command: CommandLog[]) => void = () => {}
|
||||||
private resolveReady = () => {}
|
private resolveReady = () => {}
|
||||||
/** Folks should realize that wait for ready does not get called _everytime_
|
/** Folks should realize that wait for ready does not get called _everytime_
|
||||||
@ -1150,7 +1154,9 @@ export class EngineCommandManager {
|
|||||||
// because in all other cases we send JSON strings. But in the case of
|
// because in all other cases we send JSON strings. But in the case of
|
||||||
// export we send a binary blob.
|
// export we send a binary blob.
|
||||||
// Pass this to our export function.
|
// Pass this to our export function.
|
||||||
void exportSave(event.data)
|
exportSave(event.data).then(() => {
|
||||||
|
this.pendingExport?.resolve()
|
||||||
|
}, this.pendingExport?.reject)
|
||||||
} else {
|
} else {
|
||||||
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
||||||
event.data
|
event.data
|
||||||
@ -1548,6 +1554,12 @@ export class EngineCommandManager {
|
|||||||
this.outSequence++
|
this.outSequence++
|
||||||
this.engineConnection?.unreliableSend(command)
|
this.engineConnection?.unreliableSend(command)
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
|
} else if (cmd.type === 'export') {
|
||||||
|
const promise = new Promise((resolve, reject) => {
|
||||||
|
this.pendingExport = { resolve, reject }
|
||||||
|
})
|
||||||
|
this.engineConnection?.send(command)
|
||||||
|
return promise
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
command.cmd.type === 'default_camera_look_at' ||
|
command.cmd.type === 'default_camera_look_at' ||
|
||||||
|
@ -341,36 +341,29 @@ const setAbsDistanceCreateNode =
|
|||||||
isXOrYLine = false,
|
isXOrYLine = false,
|
||||||
index = xOrY === 'x' ? 0 : 1
|
index = xOrY === 'x' ? 0 : 1
|
||||||
): TransformInfo['createNode'] =>
|
): TransformInfo['createNode'] =>
|
||||||
({ tag, forceValueUsedInTransform }) => {
|
({ tag, forceValueUsedInTransform }) =>
|
||||||
return (args, _, referencedSegment) => {
|
(args) => {
|
||||||
const valueUsedInTransform = roundOff(
|
const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[index]), 2)
|
||||||
getArgLiteralVal(args?.[index]) - (referencedSegment?.to?.[index] || 0),
|
const val =
|
||||||
2
|
(forceValueUsedInTransform as BinaryPart) ||
|
||||||
)
|
createLiteral(valueUsedInTransform)
|
||||||
const val =
|
if (isXOrYLine) {
|
||||||
(forceValueUsedInTransform as BinaryPart) ||
|
|
||||||
createLiteral(valueUsedInTransform)
|
|
||||||
if (isXOrYLine) {
|
|
||||||
return createCallWrapper(
|
|
||||||
xOrY === 'x' ? 'xLineTo' : 'yLineTo',
|
|
||||||
val,
|
|
||||||
tag,
|
|
||||||
valueUsedInTransform
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return createCallWrapper(
|
return createCallWrapper(
|
||||||
'lineTo',
|
xOrY === 'x' ? 'xLineTo' : 'yLineTo',
|
||||||
!index ? [val, args[1]] : [args[0], val],
|
val,
|
||||||
tag,
|
tag,
|
||||||
valueUsedInTransform
|
valueUsedInTransform
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
return createCallWrapper(
|
||||||
|
'lineTo',
|
||||||
|
!index ? [val, args[1]] : [args[0], val],
|
||||||
|
tag,
|
||||||
|
valueUsedInTransform
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const setAbsDistanceForAngleLineCreateNode =
|
const setAbsDistanceForAngleLineCreateNode =
|
||||||
(
|
(xOrY: 'x' | 'y'): TransformInfo['createNode'] =>
|
||||||
xOrY: 'x' | 'y',
|
|
||||||
index = xOrY === 'x' ? 0 : 1
|
|
||||||
): TransformInfo['createNode'] =>
|
|
||||||
({ tag, forceValueUsedInTransform, varValA }) => {
|
({ tag, forceValueUsedInTransform, varValA }) => {
|
||||||
return (args) => {
|
return (args) => {
|
||||||
const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[1]), 2)
|
const valueUsedInTransform = roundOff(getArgLiteralVal(args?.[1]), 2)
|
||||||
|
@ -26,6 +26,22 @@ export function pathMapToSelections(
|
|||||||
return newSelections
|
return newSelections
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updatePathToNodeFromMap(
|
||||||
|
oldPath: PathToNode,
|
||||||
|
pathToNodeMap: { [key: number]: PathToNode }
|
||||||
|
): PathToNode {
|
||||||
|
const updatedPathToNode = JSON.parse(JSON.stringify(oldPath))
|
||||||
|
let max = 0
|
||||||
|
Object.values(pathToNodeMap).forEach((path) => {
|
||||||
|
const index = Number(path[1][0])
|
||||||
|
if (index > max) {
|
||||||
|
max = index
|
||||||
|
}
|
||||||
|
})
|
||||||
|
updatedPathToNode[1][0] = max
|
||||||
|
return updatedPathToNode
|
||||||
|
}
|
||||||
|
|
||||||
export function isCursorInSketchCommandRange(
|
export function isCursorInSketchCommandRange(
|
||||||
artifactMap: ArtifactMap,
|
artifactMap: ArtifactMap,
|
||||||
selectionRanges: Selections
|
selectionRanges: Selections
|
||||||
|
@ -69,6 +69,13 @@ export const getRectangleCallExpressions = (
|
|||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
createLiteral(tags[2]),
|
createLiteral(tags[2]),
|
||||||
]),
|
]),
|
||||||
|
createCallExpressionStdLib('lineTo', [
|
||||||
|
createArrayExpression([
|
||||||
|
createCallExpressionStdLib('profileStartX', [createPipeSubstitution()]),
|
||||||
|
createCallExpressionStdLib('profileStartY', [createPipeSubstitution()]),
|
||||||
|
]),
|
||||||
|
createPipeSubstitution(),
|
||||||
|
]), // close the rectangle
|
||||||
createCallExpressionStdLib('close', [createPipeSubstitution()]),
|
createCallExpressionStdLib('close', [createPipeSubstitution()]),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
kclManager,
|
kclManager,
|
||||||
sceneEntitiesManager,
|
sceneEntitiesManager,
|
||||||
} from 'lib/singletons'
|
} from 'lib/singletons'
|
||||||
import { CallExpression, SourceRange, parse, recast } from 'lang/wasm'
|
import { CallExpression, SourceRange, Value, parse, recast } from 'lang/wasm'
|
||||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { EditorSelection } from '@codemirror/state'
|
import { EditorSelection } from '@codemirror/state'
|
||||||
@ -27,6 +27,7 @@ import {
|
|||||||
} from 'clientSideScene/sceneEntities'
|
} from 'clientSideScene/sceneEntities'
|
||||||
import { Mesh, Object3D, Object3DEventMap } from 'three'
|
import { Mesh, Object3D, Object3DEventMap } from 'three'
|
||||||
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
|
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
|
||||||
|
import { PathToNodeMap } from 'lang/std/sketchcombos'
|
||||||
|
|
||||||
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
||||||
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
||||||
@ -564,3 +565,22 @@ export function sendSelectEventToEngine(
|
|||||||
.then((res) => res.data.data)
|
.then((res) => res.data.data)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateSelections(
|
||||||
|
pathToNodeMap: PathToNodeMap,
|
||||||
|
prevSelectionRanges: Selections,
|
||||||
|
ast: Program
|
||||||
|
): Selections {
|
||||||
|
return {
|
||||||
|
...prevSelectionRanges,
|
||||||
|
codeBasedSelections: Object.entries(pathToNodeMap).map(
|
||||||
|
([index, pathToNode]): Selection => {
|
||||||
|
const node = getNodeFromPath<Value>(ast, pathToNode).node
|
||||||
|
return {
|
||||||
|
range: [node.start, node.end],
|
||||||
|
type: prevSelectionRanges.codeBasedSelections[Number(index)]?.type,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -127,3 +127,7 @@ export function isReducedMotion(): boolean {
|
|||||||
window.matchMedia('(prefers-reduced-motion)').matches
|
window.matchMedia('(prefers-reduced-motion)').matches
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function XOR(bool1: boolean, bool2: boolean): boolean {
|
||||||
|
return (bool1 || bool2) && !(bool1 && bool2)
|
||||||
|
}
|
||||||
|
File diff suppressed because one or more lines are too long
16
src/wasm-lib/Cargo.lock
generated
16
src/wasm-lib/Cargo.lock
generated
@ -2861,9 +2861,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.83"
|
version = "1.0.84"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b33eb56c327dec362a9e55b3ad14f9d2f0904fb5a5b03b513ab5465399e9f43"
|
checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@ -3539,9 +3539,9 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.202"
|
version = "1.0.203"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
|
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
@ -3557,9 +3557,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.202"
|
version = "1.0.203"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
|
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -5311,9 +5311,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zip"
|
name = "zip"
|
||||||
version = "1.3.0"
|
version = "2.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f1f4a27345eb6f7aa7bd015ba7eb4175fa4e1b462a29874b779e0bbcf96c6ac7"
|
checksum = "fccb210625924ecbbe92f9bb497d04b167b64fe5540cec75f10b16e0c51ee92b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arbitrary",
|
"arbitrary",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
|
@ -18,7 +18,7 @@ once_cell = "1.19.0"
|
|||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
serde = { version = "1.0.202", features = ["derive"] }
|
serde = { version = "1.0.203", features = ["derive"] }
|
||||||
serde_tokenstream = "0.2"
|
serde_tokenstream = "0.2"
|
||||||
syn = { version = "2.0.66", features = ["full"] }
|
syn = { version = "2.0.66", features = ["full"] }
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ parse-display = "0.9.0"
|
|||||||
reqwest = { version = "0.11.26", default-features = false, features = ["stream", "rustls-tls"] }
|
reqwest = { version = "0.11.26", default-features = false, features = ["stream", "rustls-tls"] }
|
||||||
ropey = "1.6.1"
|
ropey = "1.6.1"
|
||||||
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"] }
|
schemars = { version = "0.8.17", features = ["impl_json_schema", "url", "uuid1"] }
|
||||||
serde = { version = "1.0.202", features = ["derive"] }
|
serde = { version = "1.0.203", features = ["derive"] }
|
||||||
serde_json = "1.0.116"
|
serde_json = "1.0.116"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
thiserror = "1.0.61"
|
thiserror = "1.0.61"
|
||||||
@ -44,7 +44,7 @@ url = { version = "2.5.0", features = ["serde"] }
|
|||||||
uuid = { version = "1.8.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.8.0", features = ["v4", "js", "serde"] }
|
||||||
validator = { version = "0.18.1", features = ["derive"] }
|
validator = { version = "0.18.1", features = ["derive"] }
|
||||||
winnow = "0.5.40"
|
winnow = "0.5.40"
|
||||||
zip = { version = "1.3.0", default-features = false }
|
zip = { version = "2.0.0", default-features = false }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
js-sys = { version = "0.3.69" }
|
js-sys = { version = "0.3.69" }
|
||||||
|
@ -504,51 +504,45 @@ impl SketchGroup {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_coords_from_paths(&self) -> Result<Point2d, KclError> {
|
/// Get the path most recently sketched.
|
||||||
if self.value.is_empty() {
|
pub fn latest_path(&self) -> Option<&Path> {
|
||||||
return Ok(self.start.to.into());
|
self.value.last()
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = self.value.len() - 1;
|
/// The "pen" is an imaginary pen drawing the path.
|
||||||
if let Some(path) = self.value.get(index) {
|
/// This gets the current point the pen is hovering over, i.e. the point
|
||||||
let base = path.get_base();
|
/// where the last path segment ends, and the next path segment will begin.
|
||||||
Ok(base.to.into())
|
pub fn current_pen_position(&self) -> Result<Point2d, KclError> {
|
||||||
} else {
|
let Some(path) = self.latest_path() else {
|
||||||
Ok(self.start.to.into())
|
return Ok(self.start.to.into());
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let base = path.get_base();
|
||||||
|
Ok(base.to.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
|
pub fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
|
||||||
if self.value.is_empty() {
|
let Some(path) = self.latest_path() else {
|
||||||
return GetTangentialInfoFromPathsResult {
|
return GetTangentialInfoFromPathsResult {
|
||||||
center_or_tangent_point: self.start.to,
|
center_or_tangent_point: self.start.to,
|
||||||
is_center: false,
|
is_center: false,
|
||||||
ccw: false,
|
ccw: false,
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
let index = self.value.len() - 1;
|
match path {
|
||||||
if let Some(path) = self.value.get(index) {
|
Path::TangentialArcTo { center, ccw, .. } => GetTangentialInfoFromPathsResult {
|
||||||
match path {
|
center_or_tangent_point: *center,
|
||||||
Path::TangentialArcTo { center, ccw, .. } => GetTangentialInfoFromPathsResult {
|
is_center: true,
|
||||||
center_or_tangent_point: *center,
|
ccw: *ccw,
|
||||||
is_center: true,
|
},
|
||||||
ccw: *ccw,
|
_ => {
|
||||||
},
|
let base = path.get_base();
|
||||||
_ => {
|
GetTangentialInfoFromPathsResult {
|
||||||
let base = path.get_base();
|
center_or_tangent_point: base.from,
|
||||||
GetTangentialInfoFromPathsResult {
|
is_center: false,
|
||||||
center_or_tangent_point: base.from,
|
ccw: false,
|
||||||
is_center: false,
|
|
||||||
ccw: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
GetTangentialInfoFromPathsResult {
|
|
||||||
center_or_tangent_point: self.start.to,
|
|
||||||
is_center: false,
|
|
||||||
ccw: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ async fn inner_line_to(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.current_pen_position()?;
|
||||||
let id = uuid::Uuid::new_v4();
|
let id = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
args.send_modeling_cmd(
|
args.send_modeling_cmd(
|
||||||
@ -128,7 +128,7 @@ async fn inner_x_line_to(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.current_pen_position()?;
|
||||||
|
|
||||||
let new_sketch_group = inner_line_to([to, from.y], sketch_group, tag, args).await?;
|
let new_sketch_group = inner_line_to([to, from.y], sketch_group, tag, args).await?;
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ async fn inner_y_line_to(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.current_pen_position()?;
|
||||||
|
|
||||||
let new_sketch_group = inner_line_to([from.x, to], sketch_group, tag, args).await?;
|
let new_sketch_group = inner_line_to([from.x, to], sketch_group, tag, args).await?;
|
||||||
Ok(new_sketch_group)
|
Ok(new_sketch_group)
|
||||||
@ -213,7 +213,7 @@ async fn inner_line(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.current_pen_position()?;
|
||||||
let to = [from.x + delta[0], from.y + delta[1]];
|
let to = [from.x + delta[0], from.y + delta[1]];
|
||||||
|
|
||||||
let id = uuid::Uuid::new_v4();
|
let id = uuid::Uuid::new_v4();
|
||||||
@ -381,7 +381,7 @@ async fn inner_angled_line(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.current_pen_position()?;
|
||||||
let (angle, length) = match data {
|
let (angle, length) = match data {
|
||||||
AngledLineData::AngleAndLengthNamed { angle, length } => (angle, length),
|
AngledLineData::AngleAndLengthNamed { angle, length } => (angle, length),
|
||||||
AngledLineData::AngleAndLengthPair(pair) => (pair[0], pair[1]),
|
AngledLineData::AngleAndLengthPair(pair) => (pair[0], pair[1]),
|
||||||
@ -514,7 +514,7 @@ async fn inner_angled_line_to_x(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.current_pen_position()?;
|
||||||
let AngledLineToData { angle, to: x_to } = data;
|
let AngledLineToData { angle, to: x_to } = data;
|
||||||
|
|
||||||
let x_component = x_to - from.x;
|
let x_component = x_to - from.x;
|
||||||
@ -600,7 +600,7 @@ async fn inner_angled_line_to_y(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.current_pen_position()?;
|
||||||
let AngledLineToData { angle, to: y_to } = data;
|
let AngledLineToData { angle, to: y_to } = data;
|
||||||
|
|
||||||
let y_component = y_to - from.y;
|
let y_component = y_to - from.y;
|
||||||
@ -672,7 +672,7 @@ async fn inner_angled_line_that_intersects(
|
|||||||
})?
|
})?
|
||||||
.get_base();
|
.get_base();
|
||||||
|
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.current_pen_position()?;
|
||||||
let to = intersection_with_parallel_line(
|
let to = intersection_with_parallel_line(
|
||||||
&[intersect_path.from.into(), intersect_path.to.into()],
|
&[intersect_path.from.into(), intersect_path.to.into()],
|
||||||
data.offset.unwrap_or_default(),
|
data.offset.unwrap_or_default(),
|
||||||
@ -1355,7 +1355,7 @@ pub(crate) async fn inner_close(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.current_pen_position()?;
|
||||||
let to: Point2d = sketch_group.start.from.into();
|
let to: Point2d = sketch_group.start.from.into();
|
||||||
|
|
||||||
let id = uuid::Uuid::new_v4();
|
let id = uuid::Uuid::new_v4();
|
||||||
@ -1449,7 +1449,7 @@ pub(crate) async fn inner_arc(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from: Point2d = sketch_group.get_coords_from_paths()?;
|
let from: Point2d = sketch_group.current_pen_position()?;
|
||||||
|
|
||||||
let (center, angle_start, angle_end, radius, end) = match &data {
|
let (center, angle_start, angle_end, radius, end) = match &data {
|
||||||
ArcData::AnglesAndRadius {
|
ArcData::AnglesAndRadius {
|
||||||
@ -1555,7 +1555,7 @@ async fn inner_tangential_arc(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from: Point2d = sketch_group.get_coords_from_paths()?;
|
let from: Point2d = sketch_group.current_pen_position()?;
|
||||||
|
|
||||||
let id = uuid::Uuid::new_v4();
|
let id = uuid::Uuid::new_v4();
|
||||||
|
|
||||||
@ -1676,7 +1676,7 @@ async fn inner_tangential_arc_to(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from: Point2d = sketch_group.get_coords_from_paths()?;
|
let from: Point2d = sketch_group.current_pen_position()?;
|
||||||
let tangent_info = sketch_group.get_tangential_info_from_paths();
|
let tangent_info = sketch_group.get_tangential_info_from_paths();
|
||||||
let tan_previous_point = if tangent_info.is_center {
|
let tan_previous_point = if tangent_info.is_center {
|
||||||
get_tangent_point_from_previous_arc(tangent_info.center_or_tangent_point, tangent_info.ccw, from.into())
|
get_tangent_point_from_previous_arc(tangent_info.center_or_tangent_point, tangent_info.ccw, from.into())
|
||||||
@ -1762,7 +1762,7 @@ async fn inner_bezier_curve(
|
|||||||
tag: Option<String>,
|
tag: Option<String>,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<SketchGroup>, KclError> {
|
) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let from = sketch_group.get_coords_from_paths()?;
|
let from = sketch_group.current_pen_position()?;
|
||||||
|
|
||||||
let relative = true;
|
let relative = true;
|
||||||
let delta = data.to;
|
let delta = data.to;
|
||||||
|
@ -8,6 +8,7 @@ const application =
|
|||||||
process.env.E2E_APPLICATION || `./src-tauri/target/release/zoo-modeling-app`
|
process.env.E2E_APPLICATION || `./src-tauri/target/release/zoo-modeling-app`
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
|
hostname: '127.0.0.1',
|
||||||
port: 4444,
|
port: 4444,
|
||||||
specs: ['./e2e/tauri/specs/**/*.ts'],
|
specs: ['./e2e/tauri/specs/**/*.ts'],
|
||||||
maxInstances: 1,
|
maxInstances: 1,
|
||||||
@ -16,6 +17,7 @@ export const config = {
|
|||||||
maxInstances: 1,
|
maxInstances: 1,
|
||||||
'tauri:options': {
|
'tauri:options': {
|
||||||
application,
|
application,
|
||||||
|
webviewOptions: {}, // Windows only
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
Reference in New Issue
Block a user