Merge branch 'main' into pierremtb/issue5301-Expose-the-sectional-argument-in-the-Sweep-command-flow
@ -22,6 +22,13 @@
|
||||
"rules": {
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"@typescript-eslint/no-unused-vars": ["error", {
|
||||
"varsIgnorePattern": "^_",
|
||||
"argsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true,
|
||||
"vars": "all",
|
||||
"args": "none"
|
||||
}],
|
||||
"jsx-a11y/click-events-have-key-events": "off",
|
||||
"jsx-a11y/no-autofocus": "off",
|
||||
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
||||
|
||||
60
.github/workflows/e2e-tests.yml
vendored
@ -75,7 +75,7 @@ jobs:
|
||||
|
||||
prepare-wasm:
|
||||
# seperate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
|
||||
runs-on: namespace-profile-ubuntu-8-cores
|
||||
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
|
||||
needs: conditions
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@ -163,7 +163,7 @@ jobs:
|
||||
|
||||
snapshots:
|
||||
name: playwright:snapshots:ubuntu
|
||||
runs-on: namespace-profile-ubuntu-8-cores
|
||||
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
|
||||
needs: [conditions, prepare-wasm]
|
||||
steps:
|
||||
- uses: actions/create-github-app-token@v1
|
||||
@ -237,19 +237,14 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
|
||||
with:
|
||||
name: playwright-report-snapshots-${{ matrix.os }}-snapshot-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: playwright-report-ubuntu-snapshot-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
- name: Clean up test-results
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
run: rm -r test-results
|
||||
|
||||
- name: check for changes
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 && github.ref != 'refs/heads/main' }}
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && github.ref != 'refs/heads/main' }}
|
||||
shell: bash
|
||||
id: git-check
|
||||
run: |
|
||||
@ -270,31 +265,36 @@ jobs:
|
||||
git fetch origin
|
||||
echo ${{ github.head_ref }}
|
||||
git checkout ${{ github.head_ref }}
|
||||
git commit -m "A snapshot a day keeps the bugs away! 📷🐛 (OS: ${{matrix.os}})" || true
|
||||
git commit -m "A snapshot a day keeps the bugs away! 📷🐛" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
|
||||
# only upload artifacts if there's actually changes
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
|
||||
with:
|
||||
name: playwright-report-ubuntu-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
|
||||
|
||||
electron:
|
||||
needs: [conditions, prepare-wasm]
|
||||
timeout-minutes: 60
|
||||
name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
||||
env:
|
||||
OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
|
||||
name: playwright:electron:${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}:${{ matrix.shardIndex }}:${{ matrix.shardTotal }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# TODO: enable self-hosted-windows-8-cores once available
|
||||
os: [namespace-profile-ubuntu-8-cores, namespace-profile-macos-8-cores, windows-16-cores]
|
||||
shardIndex: [1, 2, 3, 4, 5, 6, 7, 8]
|
||||
shardTotal: [8]
|
||||
# TODO: enable namespace-profile-windows-latest once available
|
||||
os:
|
||||
- "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
|
||||
- namespace-profile-macos-8-cores
|
||||
- windows-latest
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
# Disable macos and windows tests on hourly e2e tests since we only care
|
||||
# about server side changes.
|
||||
# Technique from https://github.com/joaomcteixeira/python-project-skeleton/pull/31/files
|
||||
isScheduled:
|
||||
- ${{ github.event_name == 'schedule' }}
|
||||
exclude:
|
||||
- os: namespace-profile-macos-8-cores
|
||||
isScheduled: true
|
||||
- os: windows-latest
|
||||
isScheduled: true
|
||||
# TODO: add ref here for main and latest release tag
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
@ -351,7 +351,7 @@ jobs:
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
|
||||
- name: Run playwright/electron flow (with retries)
|
||||
@ -360,8 +360,8 @@ jobs:
|
||||
uses: nick-fields/retry@v3.0.2
|
||||
with:
|
||||
shell: bash
|
||||
command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
|
||||
timeout_minutes: 30
|
||||
command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{ env.OS_NAME }}
|
||||
timeout_minutes: 45
|
||||
max_attempts: 15
|
||||
env:
|
||||
CI: true
|
||||
@ -374,7 +374,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
|
||||
with:
|
||||
name: test-results-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: test-results/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
@ -383,7 +383,7 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
|
||||
with:
|
||||
name: playwright-report-${{ matrix.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
name: playwright-report-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
|
||||
path: playwright-report/
|
||||
include-hidden-files: true
|
||||
retention-days: 30
|
||||
|
||||
17296
docs/kcl/std.json
@ -126,6 +126,30 @@ A base path.
|
||||
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
|
||||
|
||||
|
||||
----
|
||||
A base path.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ArcThreePoint`| | No |
|
||||
| `p1` |`[number, number]`| Point 1 of the arc (base on the end of previous segment) | No |
|
||||
| `p2` |`[number, number]`| Point 2 of the arc (interior kwarg) | No |
|
||||
| `p3` |`[number, number]`| Point 3 of the arc (end kwarg) | No |
|
||||
| `from` |`[number, number]`| The from point. | No |
|
||||
| `to` |`[number, number]`| The to point. | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
|
||||
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
|
||||
|
||||
|
||||
----
|
||||
A path that is horizontal.
|
||||
|
||||
|
||||
@ -100,7 +100,8 @@ test(
|
||||
try {
|
||||
const outputGltf = await fsp.readFile(firstFileFullPath)
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
} catch (error: unknown) {
|
||||
void error
|
||||
return 0
|
||||
}
|
||||
},
|
||||
@ -179,7 +180,8 @@ test(
|
||||
try {
|
||||
const outputGltf = await fsp.readFile(secondFileFullPath)
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
} catch (error: unknown) {
|
||||
void error
|
||||
return 0
|
||||
}
|
||||
},
|
||||
|
||||
@ -1197,7 +1197,7 @@ test.describe('Undo and redo do not keep history when navigating between files',
|
||||
`cloned file has an incremented name and same contents`,
|
||||
{ tag: '@electron' },
|
||||
async ({ page, context, homePage }, testInfo) => {
|
||||
const { panesOpen, createNewFile, cloneFile } = await getUtils(page, test)
|
||||
const { panesOpen, cloneFile } = await getUtils(page, test)
|
||||
|
||||
const { dir } = await context.folderSetupFn(async (dir) => {
|
||||
const finalDir = join(dir, 'testDefault')
|
||||
|
||||
@ -3,25 +3,15 @@
|
||||
import type {
|
||||
BrowserContext,
|
||||
ElectronApplication,
|
||||
Fixtures as PlaywrightFixtures,
|
||||
TestInfo,
|
||||
Page,
|
||||
} from '@playwright/test'
|
||||
|
||||
import {
|
||||
_electron as electron,
|
||||
PlaywrightTestArgs,
|
||||
PlaywrightWorkerArgs,
|
||||
} from '@playwright/test'
|
||||
import { _electron as electron } from '@playwright/test'
|
||||
|
||||
import * as TOML from '@iarna/toml'
|
||||
import {
|
||||
TEST_SETTINGS_KEY,
|
||||
TEST_SETTINGS_CORRUPTED,
|
||||
TEST_SETTINGS,
|
||||
TEST_SETTINGS_DEFAULT_THEME,
|
||||
} from '../storageStates'
|
||||
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
|
||||
import { TEST_SETTINGS } from '../storageStates'
|
||||
import { SETTINGS_FILE_NAME } from 'lib/constants'
|
||||
import { getUtils, setup } from '../test-utils'
|
||||
import fsp from 'fs/promises'
|
||||
import fs from 'node:fs'
|
||||
@ -31,7 +21,6 @@ import { EditorFixture } from './editorFixture'
|
||||
import { ToolbarFixture } from './toolbarFixture'
|
||||
import { SceneFixture } from './sceneFixture'
|
||||
import { HomePageFixture } from './homePageFixture'
|
||||
import { unsafeTypedKeys } from 'lib/utils'
|
||||
import { DeepPartial } from 'lib/types'
|
||||
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
||||
|
||||
@ -278,13 +267,14 @@ export class ElectronZoo {
|
||||
if (fs.existsSync(this.projectDirName)) {
|
||||
await fsp.rm(this.projectDirName, { recursive: true })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
} catch (_e) {
|
||||
console.error(_e)
|
||||
}
|
||||
|
||||
try {
|
||||
await fsp.mkdir(this.projectDirName)
|
||||
} catch (e) {
|
||||
} catch (error: unknown) {
|
||||
void error
|
||||
// Not a problem if it already exists.
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
} from '../test-utils'
|
||||
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
|
||||
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
|
||||
import { ToolbarModeName } from 'lib/toolbar'
|
||||
|
||||
export class ToolbarFixture {
|
||||
public page: Page
|
||||
@ -120,6 +121,15 @@ export class ToolbarFixture {
|
||||
// this is for the engine animation, as it takes 500ms to complete
|
||||
await this.page.waitForTimeout(600)
|
||||
}
|
||||
private _getMode = () =>
|
||||
this.page.locator('[data-current-mode]').getAttribute('data-current-mode')
|
||||
expectToolbarMode = {
|
||||
toBe: (mode: ToolbarModeName) => expect.poll(this._getMode).toEqual(mode),
|
||||
not: {
|
||||
toBe: (mode: ToolbarModeName) =>
|
||||
expect.poll(this._getMode).not.toEqual(mode),
|
||||
},
|
||||
}
|
||||
|
||||
private _serialiseFileTree = async () => {
|
||||
return this.page
|
||||
@ -176,6 +186,22 @@ export class ToolbarFixture {
|
||||
).toBeVisible()
|
||||
await this.page.getByTestId('dropdown-circle-three-points').click()
|
||||
}
|
||||
selectArc = async () => {
|
||||
await this.page
|
||||
.getByRole('button', { name: 'caret down Tangential Arc:' })
|
||||
.click()
|
||||
await expect(this.page.getByTestId('dropdown-arc')).toBeVisible()
|
||||
await this.page.getByTestId('dropdown-arc').click()
|
||||
}
|
||||
selectThreePointArc = async () => {
|
||||
await this.page
|
||||
.getByRole('button', { name: 'caret down Tangential Arc:' })
|
||||
.click()
|
||||
await expect(
|
||||
this.page.getByTestId('dropdown-three-point-arc')
|
||||
).toBeVisible()
|
||||
await this.page.getByTestId('dropdown-three-point-arc').click()
|
||||
}
|
||||
|
||||
async closePane(paneId: SidebarType) {
|
||||
return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
||||
|
||||
@ -1956,7 +1956,6 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
// Locators
|
||||
const firstEdgeLocation = { x: 600, y: 193 }
|
||||
const secondEdgeLocation = { x: 600, y: 383 }
|
||||
const bodyLocation = { x: 630, y: 290 }
|
||||
const [clickOnFirstEdge] = scene.makeMouseHelpers(
|
||||
firstEdgeLocation.x,
|
||||
firstEdgeLocation.y
|
||||
@ -1969,7 +1968,6 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
// Colors
|
||||
const edgeColorWhite: [number, number, number] = [248, 248, 248]
|
||||
const edgeColorYellow: [number, number, number] = [251, 251, 40] // Mac:B=67 Ubuntu:B=12
|
||||
const bodyColor: [number, number, number] = [155, 155, 155]
|
||||
const chamferColor: [number, number, number] = [168, 168, 168]
|
||||
const backgroundColor: [number, number, number] = [30, 30, 30]
|
||||
const lowTolerance = 20
|
||||
|
||||
@ -539,7 +539,8 @@ test.describe('Can export from electron app', () => {
|
||||
try {
|
||||
const outputGltf = await fsp.readFile(filepath)
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
} catch (error: unknown) {
|
||||
void error
|
||||
return 0
|
||||
}
|
||||
},
|
||||
|
||||
@ -53,46 +53,47 @@ sketch003 = startSketchOn('XY')
|
||||
|> close()
|
||||
extrude003 = extrude(sketch003, length = 20)
|
||||
`
|
||||
test.describe('edit with AI example snapshots', () => {
|
||||
test(
|
||||
`change colour`,
|
||||
{ tag: '@snapshot' },
|
||||
async ({ context, homePage, cmdBar, editor, page, scene }) => {
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
test(
|
||||
`change colour`,
|
||||
{ tag: '@snapshot' },
|
||||
async ({ context, homePage, cmdBar, editor, page, scene }) => {
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
const body1CapCoords = { x: 571, y: 351 }
|
||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||
body1CapCoords.x,
|
||||
body1CapCoords.y
|
||||
)
|
||||
const yellow: [number, number, number] = [179, 179, 131]
|
||||
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
||||
|
||||
const body1CapCoords = { x: 571, y: 351 }
|
||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||
body1CapCoords.x,
|
||||
body1CapCoords.y
|
||||
)
|
||||
const yellow: [number, number, number] = [179, 179, 131]
|
||||
const submittingToast = page.getByText('Submitting to Text-to-CAD API...')
|
||||
|
||||
await test.step('wait for scene to load select body and check selection came through', async () => {
|
||||
await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15)
|
||||
await clickBody1Cap()
|
||||
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
||||
diagnostics: [],
|
||||
await test.step('wait for scene to load select body and check selection came through', async () => {
|
||||
await scene.expectPixelColor([134, 134, 134], body1CapCoords, 15)
|
||||
await clickBody1Cap()
|
||||
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
||||
diagnostics: [],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('fire off edit prompt', async () => {
|
||||
await cmdBar.captureTextToCadRequestSnapshot(test.info())
|
||||
await cmdBar.openCmdBar('promptToEdit')
|
||||
// being specific about the color with a hex means asserting pixel color is more stable
|
||||
await page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.fill('make this neon green please, use #39FF14')
|
||||
await page.waitForTimeout(100)
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(submittingToast).toBeVisible()
|
||||
})
|
||||
}
|
||||
)
|
||||
await test.step('fire off edit prompt', async () => {
|
||||
await cmdBar.captureTextToCadRequestSnapshot(test.info())
|
||||
await cmdBar.openCmdBar('promptToEdit')
|
||||
// being specific about the color with a hex means asserting pixel color is more stable
|
||||
await page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.fill('make this neon green please, use #39FF14')
|
||||
await page.waitForTimeout(100)
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(submittingToast).toBeVisible()
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -319,7 +319,6 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
'when engine fails export we handle the failure and alert the user',
|
||||
{ tag: '@skipLocalEngine' },
|
||||
async ({ scene, page, homePage, cmdBar }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(
|
||||
async ({ code }) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
@ -636,11 +635,8 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
await homePage.goToModelingScene()
|
||||
})
|
||||
|
||||
const toolBarMode = () =>
|
||||
page.locator('[data-currentMode]').getAttribute('data-currentMode')
|
||||
|
||||
await test.step('Start sketch and select a plane', async () => {
|
||||
await expect.poll(toolBarMode).toEqual('modeling')
|
||||
await toolbar.expectToolbarMode.toBe('modeling')
|
||||
// Click the start sketch button
|
||||
await toolbar.startSketchPlaneSelection()
|
||||
|
||||
@ -649,10 +645,10 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
|
||||
// Check that the modeling toolbar doesn't appear during the animation
|
||||
// The animation typically takes around 500ms, so we'll check for a second
|
||||
await expect.poll(toolBarMode, { timeout: 1000 }).not.toEqual('modeling')
|
||||
await toolbar.expectToolbarMode.not.toBe('modeling')
|
||||
|
||||
// After animation completes, we should see the sketching toolbar
|
||||
await expect.poll(toolBarMode).toEqual('sketching')
|
||||
await toolbar.expectToolbarMode.toBe('sketching')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
import { readFileSync } from 'fs'
|
||||
|
||||
const secrets: Record<string, string> = {}
|
||||
const secretsPath = './e2e/playwright/playwright-secrets.env'
|
||||
try {
|
||||
const file = readFileSync('./e2e/playwright/playwright-secrets.env', 'utf8')
|
||||
const file = readFileSync(secretsPath, 'utf8')
|
||||
file
|
||||
.split('\n')
|
||||
.filter((line) => line && line.length > 1)
|
||||
@ -13,11 +14,15 @@ try {
|
||||
// prefer env vars over secrets file
|
||||
secrets[key] = process.env[key] || (value as any).replaceAll('"', '')
|
||||
})
|
||||
} catch (err) {
|
||||
} catch (error: unknown) {
|
||||
void error
|
||||
// probably running in CI
|
||||
secrets.token = process.env.token || ''
|
||||
secrets.snapshottoken = process.env.snapshottoken || ''
|
||||
// add more env vars here to make them available in CI
|
||||
console.warn(
|
||||
`Error reading ${secretsPath}; environment variables will be used`
|
||||
)
|
||||
}
|
||||
secrets.token = secrets.token || process.env.token || ''
|
||||
secrets.snapshottoken = secrets.snapshottoken || process.env.snapshottoken || ''
|
||||
// add more env vars here to make them available in CI
|
||||
|
||||
export { secrets }
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
} from './test-utils'
|
||||
import { uuidv4, roundOff } from 'lib/utils'
|
||||
import { SceneFixture } from './fixtures/sceneFixture'
|
||||
import { CmdBarFixture } from './fixtures/cmdBarFixture'
|
||||
|
||||
test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
|
||||
test('multi-sketch file shows multiple Edit Sketch buttons', async ({
|
||||
@ -191,7 +192,8 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
page: Page,
|
||||
homePage: HomePageFixture,
|
||||
openPanes: string[],
|
||||
scene: SceneFixture
|
||||
scene: SceneFixture,
|
||||
cmdBar: CmdBarFixture
|
||||
) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
@ -201,13 +203,22 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line(end = [12.73, -0.09])
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> arcTo({
|
||||
interior = [20.18, -1.7],
|
||||
end = [11.82, -1.16]
|
||||
}, %)
|
||||
|> arc({
|
||||
radius = 5.92,
|
||||
angleStart = -89.36,
|
||||
angleEnd = 135.81
|
||||
}, %)
|
||||
|> close()`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
await scene.settled(cmdBar)
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
@ -242,7 +253,17 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
|> startProfileAt([4.61, -14.01], %)
|
||||
|> line(end = [12.73, -0.09])
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close()`)
|
||||
|> arcTo({
|
||||
interior = [20.18, -1.7],
|
||||
end = [11.82, -1.16]
|
||||
}, %)
|
||||
|> arc({
|
||||
radius = 5.92,
|
||||
angleStart = -89.36,
|
||||
angleEnd = 135.81
|
||||
}, %)
|
||||
|> close()
|
||||
`)
|
||||
} else {
|
||||
// Ensure we don't see the code.
|
||||
await expect(u.codeLocator).not.toBeVisible()
|
||||
@ -272,7 +293,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
|
||||
const step5 = { steps: 5 }
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(5)
|
||||
|
||||
// drag startProfileAt handle
|
||||
await page.mouse.move(startPX[0], startPX[1])
|
||||
@ -310,22 +331,93 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
}
|
||||
|
||||
// drag arcTo interior handle (three point arc)
|
||||
const arcToHandle = await u.getBoundingBox('[data-overlay-index="2"]')
|
||||
await page.mouse.move(arcToHandle.x, arcToHandle.y - 5)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(
|
||||
arcToHandle.x - dragPX,
|
||||
arcToHandle.y + dragPX,
|
||||
step5
|
||||
)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
|
||||
// drag arcTo end handle (three point arc)
|
||||
const arcToEndHandle = await u.getBoundingBox('[data-overlay-index="3"]')
|
||||
await page.mouse.move(arcToEndHandle.x, arcToEndHandle.y - 5)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(
|
||||
arcToEndHandle.x - dragPX,
|
||||
arcToEndHandle.y + dragPX,
|
||||
step5
|
||||
)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
}
|
||||
|
||||
// drag arc radius handle
|
||||
const arcRadiusHandle = await u.getBoundingBox('[data-overlay-index="4"]')
|
||||
await page.mouse.move(arcRadiusHandle.x, arcRadiusHandle.y - 5)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(
|
||||
arcRadiusHandle.x - dragPX,
|
||||
arcRadiusHandle.y + dragPX,
|
||||
step5
|
||||
)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
}
|
||||
|
||||
// drag arc center handle (we'll have to hardcode the position because it doesn't have a overlay near the handle)
|
||||
const arcCenterHandle = { x: 745, y: 214 }
|
||||
await page.mouse.move(arcCenterHandle.x, arcCenterHandle.y - 5)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(
|
||||
arcCenterHandle.x - dragPX,
|
||||
arcCenterHandle.y + dragPX,
|
||||
step5
|
||||
)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
if (openPanes.includes('code')) {
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
}
|
||||
|
||||
// Open the code pane
|
||||
await u.openKclCodePanel()
|
||||
|
||||
// expect the code to have changed
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([6.44, -12.07], %)
|
||||
|> line(end = [14.72, 1.97])
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> line(end = [1.97, 2.06])
|
||||
|> close()`)
|
||||
|> startProfileAt([6.44, -12.07], %)
|
||||
|> line(end = [14.72, 1.97])
|
||||
|> tangentialArcTo([26.92, -3.32], %)
|
||||
|> arcTo({
|
||||
interior = [18.11, -3.73],
|
||||
end = [9.77, -3.19]
|
||||
}, %)
|
||||
|> arc({
|
||||
radius = 3.75,
|
||||
angleStart = -58.29,
|
||||
angleEnd = 161.17
|
||||
}, %)
|
||||
|> close()
|
||||
`)
|
||||
}
|
||||
test(
|
||||
'code pane open at start-handles',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage, scene }) => {
|
||||
async ({ page, homePage, scene, cmdBar }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -338,14 +430,20 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
})
|
||||
)
|
||||
})
|
||||
await doEditSegmentsByDraggingHandle(page, homePage, ['code'], scene)
|
||||
await doEditSegmentsByDraggingHandle(
|
||||
page,
|
||||
homePage,
|
||||
['code'],
|
||||
scene,
|
||||
cmdBar
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'code pane closed at start-handles',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage, scene }) => {
|
||||
async ({ page, homePage, scene, cmdBar }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async (persistModelingContext) => {
|
||||
localStorage.setItem(
|
||||
@ -353,7 +451,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
JSON.stringify({ openPanes: [] })
|
||||
)
|
||||
}, PERSIST_MODELING_CONTEXT)
|
||||
await doEditSegmentsByDraggingHandle(page, homePage, [], scene)
|
||||
await doEditSegmentsByDraggingHandle(page, homePage, [], scene, cmdBar)
|
||||
}
|
||||
)
|
||||
})
|
||||
@ -362,6 +460,8 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
scene,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
@ -373,6 +473,8 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
})
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await scene.connectionEstablished()
|
||||
await scene.settled(cmdBar)
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
@ -1355,7 +1457,7 @@ test.describe('multi-profile sketching', () => {
|
||||
test(
|
||||
`test it removes half-finished expressions when changing tools in sketch mode`,
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ context, page, scene, toolbar, editor, homePage }) => {
|
||||
async ({ context, page, scene, toolbar, editor, homePage, cmdBar }) => {
|
||||
// We seed the scene with a single offset plane
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
@ -1375,7 +1477,10 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
|
||||
)
|
||||
})
|
||||
|
||||
const [continueProfile2Clk] = scene.makeMouseHelpers(954, 282)
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await scene.settled(cmdBar)
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -1386,7 +1491,13 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
|
||||
const [circlePoint1] = scene.makeMouseHelpers(700, 200)
|
||||
|
||||
await test.step('equip circle tool and click first point', async () => {
|
||||
await toolbar.circleBtn.click()
|
||||
// await page.waitForTimeout(100)
|
||||
await expect
|
||||
.poll(async () => {
|
||||
await toolbar.circleBtn.click()
|
||||
return toolbar.circleBtn.getAttribute('aria-pressed')
|
||||
})
|
||||
.toBe('true')
|
||||
await page.waitForTimeout(100)
|
||||
await circlePoint1()
|
||||
await editor.expectEditor.toContain(
|
||||
@ -1401,6 +1512,7 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
|
||||
|
||||
const [circle3Point1] = scene.makeMouseHelpers(650, 200)
|
||||
const [circle3Point2] = scene.makeMouseHelpers(750, 200)
|
||||
// const [circle3Point3] = scene.makeMouseHelpers(700, 150)
|
||||
|
||||
await test.step('equip three point circle tool and click first two points', async () => {
|
||||
await toolbar.selectCircleThreePoint()
|
||||
@ -1411,25 +1523,40 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
|
||||
await editor.expectEditor.toContain('profile003 = circleThreePoint(')
|
||||
})
|
||||
|
||||
await test.step('equip line tool and verify three point circle code is removed', async () => {
|
||||
await test.step('equip line tool and verify three-point circle code is removed', async () => {
|
||||
await toolbar.lineBtn.click()
|
||||
await editor.expectEditor.not.toContain(
|
||||
'profile003 = circleThreePoint('
|
||||
)
|
||||
})
|
||||
|
||||
await test.step('equip three-point-arc tool and click first two points', async () => {
|
||||
await page.waitForTimeout(200)
|
||||
await toolbar.selectThreePointArc()
|
||||
await page.waitForTimeout(200)
|
||||
await circle3Point1()
|
||||
await page.waitForTimeout(200)
|
||||
await circle3Point2()
|
||||
await editor.expectEditor.toContain('arcTo({')
|
||||
})
|
||||
|
||||
await test.step('equip line tool and verify three-point-arc code is removed after second click', async () => {
|
||||
await toolbar.lineBtn.click()
|
||||
await editor.expectEditor.not.toContain('arcTo({')
|
||||
})
|
||||
|
||||
const [cornerRectPoint1] = scene.makeMouseHelpers(600, 300)
|
||||
|
||||
await test.step('equip corner rectangle tool and click first point', async () => {
|
||||
await toolbar.rectangleBtn.click()
|
||||
await page.waitForTimeout(100)
|
||||
await cornerRectPoint1()
|
||||
await editor.expectEditor.toContain('profile003 = startProfileAt(')
|
||||
await editor.expectEditor.toContain('profile004 = startProfileAt(')
|
||||
})
|
||||
|
||||
await test.step('equip line tool and verify corner rectangle code is removed', async () => {
|
||||
await toolbar.lineBtn.click()
|
||||
await editor.expectEditor.not.toContain('profile003 = startProfileAt(')
|
||||
await editor.expectEditor.not.toContain('profile004 = startProfileAt(')
|
||||
})
|
||||
|
||||
const [centerRectPoint1] = scene.makeMouseHelpers(700, 300)
|
||||
@ -1438,12 +1565,24 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
|
||||
await toolbar.selectCenterRectangle()
|
||||
await page.waitForTimeout(100)
|
||||
await centerRectPoint1()
|
||||
await editor.expectEditor.toContain('profile003 = startProfileAt(')
|
||||
await editor.expectEditor.toContain('profile004 = startProfileAt(')
|
||||
})
|
||||
|
||||
await test.step('equip line tool and verify center rectangle code is removed', async () => {
|
||||
await toolbar.lineBtn.click()
|
||||
await editor.expectEditor.not.toContain('profile003 = startProfileAt(')
|
||||
await editor.expectEditor.not.toContain('profile004 = startProfileAt(')
|
||||
})
|
||||
|
||||
await test.step('continue profile002 with the three point arc tool, and then switch back to the line tool to verify it only removes the last expression in the pipe', async () => {
|
||||
await toolbar.selectThreePointArc()
|
||||
await page.waitForTimeout(200)
|
||||
await continueProfile2Clk()
|
||||
await page.waitForTimeout(200)
|
||||
await circle3Point1()
|
||||
await editor.expectEditor.toContain('arcTo({')
|
||||
await toolbar.lineBtn.click()
|
||||
await editor.expectEditor.not.toContain('arcTo({')
|
||||
await editor.expectEditor.toContain('profile002')
|
||||
})
|
||||
}
|
||||
)
|
||||
@ -1532,6 +1671,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
||||
}) => {
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.connectionEstablished()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -1595,7 +1735,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
||||
// timeout wait for engine animation is unavoidable
|
||||
await page.waitForTimeout(600)
|
||||
await editor.expectEditor.toContain(`sketch001 = startSketchOn('XZ')`)
|
||||
await test.step('Create a close profile stopping mid profile to equip the tangential arc, and than back to the line tool', async () => {
|
||||
await test.step('Create a close profile stopping mid profile to equip the tangential arc, then three-point arc, and then back to the line tool', async () => {
|
||||
await startProfile1()
|
||||
await editor.expectEditor.toContain(
|
||||
`profile001 = startProfileAt([4.61, 12.21], sketch001)`
|
||||
@ -1613,12 +1753,45 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
||||
await editor.expectEditor.toContain(
|
||||
`|> tangentialArcTo([16.61, 4.14], %)`
|
||||
)
|
||||
|
||||
// Add a three-point arc segment
|
||||
await toolbar.selectThreePointArc()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// select end of profile again
|
||||
await endLineStartTanArc()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Define points for the three-point arc
|
||||
const [threePointInterior, threePointInteriorMove] =
|
||||
scene.makeMouseHelpers(600, 200)
|
||||
const [threePointEnd, threePointEndMove] = scene.makeMouseHelpers(
|
||||
590,
|
||||
270
|
||||
)
|
||||
|
||||
// Create the three-point arc
|
||||
await page.waitForTimeout(300)
|
||||
await threePointInteriorMove()
|
||||
await threePointInterior()
|
||||
await page.waitForTimeout(300)
|
||||
await threePointEndMove()
|
||||
await threePointEnd()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Verify the three-point arc was created correctly
|
||||
await editor.expectEditor.toContain(`|> arcTo(`)
|
||||
|
||||
// Switch back to line tool to continue
|
||||
await toolbar.lineBtn.click()
|
||||
await page.waitForTimeout(300)
|
||||
await endArcStartLine()
|
||||
|
||||
// Continue with the original line segment
|
||||
await threePointEnd()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
await page.mouse.click(572, 110)
|
||||
await editor.expectEditor.toContain(`|> line(end = [-11.73, 5.35])`)
|
||||
await editor.expectEditor.toContain(`|> line(end = [-1.22, 10.85])`)
|
||||
await startProfile1()
|
||||
await editor.expectEditor.toContain(
|
||||
`|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
@ -1820,8 +1993,68 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
||||
)
|
||||
})
|
||||
|
||||
await test.step('double check that circle three point can be unequiped', async () => {
|
||||
// this was tested implicitly for other tools, but not for circle three point since it's last
|
||||
await test.step('create three-point arcs in a row without an unequip', async () => {
|
||||
// Define points for the first three-point arc
|
||||
const [arc1Point1, arc1Point1Move] = scene.makeMouseHelpers(700, 397)
|
||||
const [arc1Point2, arc1Point2Move] = scene.makeMouseHelpers(724, 346)
|
||||
const [arc1Point3, arc1Point3Move] = scene.makeMouseHelpers(785, 415)
|
||||
|
||||
// Define points for the second three-point arc
|
||||
const [arc2Point1, arc2Point1Move] = scene.makeMouseHelpers(792, 225)
|
||||
const [arc2Point2, arc2Point2Move] = scene.makeMouseHelpers(820, 207)
|
||||
const [arc2Point3, arc2Point3Move] = scene.makeMouseHelpers(905, 229)
|
||||
|
||||
// Select the three-point arc tool
|
||||
await toolbar.selectThreePointArc()
|
||||
|
||||
// Create the first three-point arc
|
||||
await arc1Point1Move()
|
||||
await arc1Point1()
|
||||
await page.waitForTimeout(300)
|
||||
await arc1Point2Move()
|
||||
await arc1Point2()
|
||||
await page.waitForTimeout(300)
|
||||
await arc1Point3Move()
|
||||
await arc1Point3()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Verify the first three-point arc was created correctly
|
||||
await editor.expectEditor.toContain(
|
||||
`profile011 = startProfileAt([13.56, -9.97], sketch001)
|
||||
|> arcTo({
|
||||
interior = [15.19, -6.51],
|
||||
end = [19.33, -11.19]
|
||||
}, %)`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
|
||||
// Create the second three-point arc
|
||||
await arc2Point1Move()
|
||||
await arc2Point1()
|
||||
await page.waitForTimeout(300)
|
||||
await arc2Point2Move()
|
||||
await arc2Point2()
|
||||
await page.waitForTimeout(300)
|
||||
await arc2Point3Move()
|
||||
await arc2Point3()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Verify the second three-point arc was created correctly
|
||||
await editor.expectEditor.toContain(
|
||||
` |> arcTo({
|
||||
interior = [19.8, 1.7],
|
||||
end = [21.7, 2.92]
|
||||
}, %)
|
||||
|> arcTo({
|
||||
interior = [27.47, 1.42],
|
||||
end = [27.57, 1.52]
|
||||
}, %)`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
})
|
||||
|
||||
await test.step('double check that three-point arc can be unequipped', async () => {
|
||||
// this was tested implicitly for other tools, but not for three-point arc since it's last
|
||||
await page.waitForTimeout(300)
|
||||
await expect
|
||||
.poll(async () => {
|
||||
@ -2085,7 +2318,7 @@ profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
|
||||
test(
|
||||
'can enter sketch when there is an extrude',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ homePage, scene, toolbar, page }) => {
|
||||
async ({ homePage, scene, toolbar, page, cmdBar }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
@ -2122,6 +2355,8 @@ extrude001 = extrude(profile003, length = 5)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.connectionEstablished()
|
||||
await scene.settled(cmdBar)
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -2134,9 +2369,11 @@ extrude001 = extrude(profile003, length = 5)
|
||||
await page.waitForTimeout(600)
|
||||
|
||||
await test.step('check the sketch is still drawn properly', async () => {
|
||||
await scene.expectPixelColor([255, 255, 255], { x: 596, y: 165 }, 15)
|
||||
await scene.expectPixelColor([255, 255, 255], { x: 641, y: 220 }, 15)
|
||||
await scene.expectPixelColor([255, 255, 255], { x: 763, y: 214 }, 15)
|
||||
await Promise.all([
|
||||
scene.expectPixelColor(TEST_COLORS.WHITE, { x: 596, y: 165 }, 15),
|
||||
scene.expectPixelColor(TEST_COLORS.WHITE, { x: 641, y: 220 }, 15),
|
||||
scene.expectPixelColor(TEST_COLORS.WHITE, { x: 763, y: 214 }, 15),
|
||||
])
|
||||
})
|
||||
}
|
||||
)
|
||||
@ -2293,7 +2530,7 @@ extrude001 = extrude(thePart, length = 75)
|
||||
test(
|
||||
'Can enter sketch on sketch of wall and cap for segment, solid2d, extrude-wall, extrude-cap selections',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ homePage, scene, toolbar, editor, page }) => {
|
||||
async ({ homePage, scene, toolbar, editor, page, cmdBar }) => {
|
||||
// TODO this test should include a test for selecting revolve walls and caps
|
||||
|
||||
await page.addInitScript(async () => {
|
||||
@ -2378,6 +2615,8 @@ extrude003 = extrude(profile011, length = 2.5)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.connectionEstablished()
|
||||
await scene.settled(cmdBar)
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -2387,11 +2626,6 @@ extrude003 = extrude(profile011, length = 2.5)
|
||||
{ x: 834, y: -680, z: 534 },
|
||||
{ x: -54, y: -476, z: 148 }
|
||||
)
|
||||
const camPositionForSelectingSketchOnCapProfiles = () =>
|
||||
scene.moveCameraTo(
|
||||
{ x: 404, y: 690, z: 38 },
|
||||
{ x: 16, y: -140, z: -10 }
|
||||
)
|
||||
const wallSelectionOptions = [
|
||||
{
|
||||
title: 'select wall segment',
|
||||
@ -2414,103 +2648,25 @@ extrude003 = extrude(profile011, length = 2.5)
|
||||
selectClick: scene.makeMouseHelpers(836, 103)[0],
|
||||
},
|
||||
] as const
|
||||
const capSelectionOptions = [
|
||||
{
|
||||
title: 'select cap segment',
|
||||
selectClick: scene.makeMouseHelpers(688, 91)[0],
|
||||
},
|
||||
{
|
||||
title: 'select cap solid 2d',
|
||||
selectClick: scene.makeMouseHelpers(733, 204)[0],
|
||||
},
|
||||
// TODO keeps failing
|
||||
// {
|
||||
// title: 'select cap circle',
|
||||
// selectClick: scene.makeMouseHelpers(679, 290)[0],
|
||||
// },
|
||||
{
|
||||
title: 'select cap extrude wall',
|
||||
selectClick: scene.makeMouseHelpers(649, 402)[0],
|
||||
},
|
||||
{
|
||||
title: 'select cap extrude cap',
|
||||
selectClick: scene.makeMouseHelpers(693, 408)[0],
|
||||
},
|
||||
] as const
|
||||
|
||||
const verifyWallProfilesAreDrawn = async () =>
|
||||
test.step('verify wall profiles are drawn', async () => {
|
||||
// open polygon
|
||||
await scene.expectPixelColor(
|
||||
TEST_COLORS.WHITE,
|
||||
{ x: 599, y: 168 },
|
||||
15
|
||||
)
|
||||
// closed polygon
|
||||
await scene.expectPixelColor(
|
||||
TEST_COLORS.WHITE,
|
||||
{ x: 656, y: 171 },
|
||||
15
|
||||
)
|
||||
// revolved profile
|
||||
await scene.expectPixelColor(
|
||||
TEST_COLORS.WHITE,
|
||||
{ x: 655, y: 264 },
|
||||
15
|
||||
)
|
||||
// extruded profile
|
||||
await scene.expectPixelColor(
|
||||
TEST_COLORS.WHITE,
|
||||
{ x: 808, y: 396 },
|
||||
15
|
||||
)
|
||||
// circle
|
||||
await scene.expectPixelColor(
|
||||
[
|
||||
TEST_COLORS.WHITE,
|
||||
TEST_COLORS.BLUE, // When entering via the circle, it's selected and therefore blue
|
||||
],
|
||||
{ x: 742, y: 386 },
|
||||
15
|
||||
)
|
||||
})
|
||||
|
||||
const verifyCapProfilesAreDrawn = async () =>
|
||||
test.step('verify cap profiles are drawn', async () => {
|
||||
// open polygon
|
||||
await scene.expectPixelColor(
|
||||
TEST_COLORS.WHITE,
|
||||
// TEST_COLORS.BLUE, // When entering via the circle, it's selected and therefore blue
|
||||
{ x: 620, y: 58 },
|
||||
15
|
||||
)
|
||||
// revolved profile
|
||||
await scene.expectPixelColor(
|
||||
TEST_COLORS.WHITE,
|
||||
{ x: 641, y: 110 },
|
||||
15
|
||||
)
|
||||
// closed polygon
|
||||
await scene.expectPixelColor(
|
||||
TEST_COLORS.WHITE,
|
||||
{ x: 632, y: 200 },
|
||||
15
|
||||
)
|
||||
// extruded profile
|
||||
await scene.expectPixelColor(
|
||||
TEST_COLORS.WHITE,
|
||||
{ x: 628, y: 410 },
|
||||
15
|
||||
)
|
||||
// circle
|
||||
await scene.expectPixelColor(
|
||||
[
|
||||
TEST_COLORS.WHITE,
|
||||
TEST_COLORS.BLUE, // When entering via the circle, it's selected and therefore blue
|
||||
],
|
||||
{ x: 681, y: 303 },
|
||||
15
|
||||
)
|
||||
await Promise.all([
|
||||
// open polygon
|
||||
scene.expectPixelColor(TEST_COLORS.WHITE, { x: 599, y: 168 }, 15),
|
||||
// closed polygon
|
||||
scene.expectPixelColor(TEST_COLORS.WHITE, { x: 656, y: 171 }, 15),
|
||||
// revolved profile
|
||||
scene.expectPixelColor(TEST_COLORS.WHITE, { x: 655, y: 264 }, 15),
|
||||
// extruded profile
|
||||
scene.expectPixelColor(TEST_COLORS.WHITE, { x: 808, y: 396 }, 15),
|
||||
// circle (When entering via the circle, it's selected and therefore blue)
|
||||
scene.expectPixelColor(
|
||||
[TEST_COLORS.WHITE, TEST_COLORS.BLUE],
|
||||
{ x: 742, y: 386 },
|
||||
15
|
||||
),
|
||||
])
|
||||
})
|
||||
|
||||
await test.step('select wall profiles', async () => {
|
||||
|
||||
@ -7,12 +7,7 @@ import { spawn } from 'child_process'
|
||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||
import JSZip from 'jszip'
|
||||
import path from 'path'
|
||||
import {
|
||||
IS_PLAYWRIGHT_KEY,
|
||||
TEST_SETTINGS,
|
||||
TEST_SETTINGS_KEY,
|
||||
} from './storageStates'
|
||||
import * as TOML from '@iarna/toml'
|
||||
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from './storageStates'
|
||||
import { SceneFixture } from './fixtures/sceneFixture'
|
||||
import { CmdBarFixture } from './fixtures/cmdBarFixture'
|
||||
|
||||
@ -410,9 +405,9 @@ test.describe(
|
||||
test(
|
||||
'Draft segments should look right',
|
||||
{ tag: '@snapshot' },
|
||||
async ({ page, context, scene, cmdBar }) => {
|
||||
async ({ page, scene, toolbar }) => {
|
||||
// FIXME: Skip on macos its being weird.
|
||||
test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||
// test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
@ -421,6 +416,23 @@ test(
|
||||
|
||||
await scene.connectionEstablished()
|
||||
|
||||
const startXPx = 600
|
||||
const [endOfTangentClk, endOfTangentMv] = scene.makeMouseHelpers(
|
||||
startXPx + PUR * 30,
|
||||
500 - PUR * 20,
|
||||
{ steps: 10 }
|
||||
)
|
||||
const [threePointArcMidPointClk, threePointArcMidPointMv] =
|
||||
scene.makeMouseHelpers(800, 250, { steps: 10 })
|
||||
const [threePointArcEndPointClk, threePointArcEndPointMv] =
|
||||
scene.makeMouseHelpers(750, 285, { steps: 10 })
|
||||
const [arcCenterClk, arcCenterMv] = scene.makeMouseHelpers(750, 210, {
|
||||
steps: 10,
|
||||
})
|
||||
const [arcEndClk, arcEndMv] = scene.makeMouseHelpers(750, 150, {
|
||||
steps: 10,
|
||||
})
|
||||
|
||||
// click on "Start Sketch" button
|
||||
await u.doAndWaitForImageDiff(
|
||||
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
|
||||
@ -435,7 +447,6 @@ test(
|
||||
|
||||
await page.waitForTimeout(700) // TODO detect animation ending, or disable animation
|
||||
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
code += `profile001 = startProfileAt([7.19, -9.7], sketch001)`
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
@ -471,12 +482,52 @@ test(
|
||||
await page.mouse.move(813, 392, { steps: 10 })
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
|
||||
await endOfTangentMv()
|
||||
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
await endOfTangentClk()
|
||||
|
||||
await toolbar.selectThreePointArc()
|
||||
await page.waitForTimeout(500)
|
||||
await endOfTangentClk()
|
||||
await threePointArcMidPointMv()
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
await threePointArcMidPointClk()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await threePointArcEndPointMv()
|
||||
await page.waitForTimeout(500)
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
|
||||
await threePointArcEndPointClk()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await toolbar.selectArc()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// continue the profile
|
||||
await threePointArcEndPointClk()
|
||||
await page.waitForTimeout(100)
|
||||
await arcCenterMv()
|
||||
await page.waitForTimeout(500)
|
||||
await arcCenterClk()
|
||||
|
||||
await arcEndMv()
|
||||
await page.waitForTimeout(500)
|
||||
await expect(page).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
await arcEndClk()
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 52 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 143 KiB |
|
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 127 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 69 KiB |
@ -0,0 +1,33 @@
|
||||
{
|
||||
"original_source_code": "sketch001 = startSketchOn('XZ')\nprofile001 = startProfileAt([57.81, 250.51], sketch001)\n |> line(end = [121.13, 56.63], tag = $seg02)\n |> line(end = [83.37, -34.61], tag = $seg01)\n |> line(end = [19.66, -116.4])\n |> line(end = [-221.8, -41.69])\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude001 = extrude(profile001, length = 200)\nsketch002 = startSketchOn('XZ')\n |> startProfileAt([-73.64, -42.89], %)\n |> xLine(length = 173.71)\n |> line(end = [-22.12, -94.4])\n |> xLine(length = -156.98)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude002 = extrude(sketch002, length = 50)\nsketch003 = startSketchOn('XY')\n |> startProfileAt([52.92, 157.81], %)\n |> angledLine([0, 176.4], %, $rectangleSegmentA001)\n |> angledLine([\n segAng(rectangleSegmentA001) - 90,\n 53.4\n ], %, $rectangleSegmentB001)\n |> angledLine([\n segAng(rectangleSegmentA001),\n -segLen(rectangleSegmentA001)\n ], %, $rectangleSegmentC001)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude003 = extrude(sketch003, length = 20)\n",
|
||||
"prompt": "make this neon green please, use #39FF14",
|
||||
"source_ranges": [
|
||||
{
|
||||
"prompt": "The users main selection is the end cap of a general-sweep (that is an extrusion, revolve, sweep or loft).\nThe source range most likely refers to \"startProfileAt\" simply because this is the start of the profile that was swept.\nIf you need to operate on this cap, for example for sketching on the face, you can use the special string END i.e. `startSketchOn(someSweepVariable, END)`\nWhen they made this selection they main have intended this surface directly or meant something more general like the sweep body.\nSee later source ranges for more context.",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 11,
|
||||
"column": 5
|
||||
},
|
||||
"end": {
|
||||
"line": 11,
|
||||
"column": 40
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"prompt": "This is the sweep's source range from the user's main selection of the end cap.",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 17,
|
||||
"column": 13
|
||||
},
|
||||
"end": {
|
||||
"line": 17,
|
||||
"column": 44
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"kcl_version": "0.2.50"
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
import { MouseControlType } from '@rust/kcl-lib/bindings/MouseControlType'
|
||||
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||
import { Themes } from 'lib/theme'
|
||||
|
||||
@ -2,15 +2,12 @@ import {
|
||||
expect,
|
||||
BrowserContext,
|
||||
TestInfo,
|
||||
_electron as electron,
|
||||
ElectronApplication,
|
||||
Locator,
|
||||
Page,
|
||||
} from '@playwright/test'
|
||||
import { test } from './zoo-test'
|
||||
import { EngineCommand } from 'lang/std/artifactGraph'
|
||||
import fsp from 'fs/promises'
|
||||
import fsSync from 'fs'
|
||||
import path from 'path'
|
||||
import pixelMatch from 'pixelmatch'
|
||||
import { PNG } from 'pngjs'
|
||||
@ -24,14 +21,11 @@ import {
|
||||
IS_PLAYWRIGHT_KEY,
|
||||
} from './storageStates'
|
||||
import * as TOML from '@iarna/toml'
|
||||
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
|
||||
import { SETTINGS_FILE_NAME } from 'lib/constants'
|
||||
import { isErrorWhitelisted } from './lib/console-error-whitelist'
|
||||
import { isArray } from 'lib/utils'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { DeepPartial } from 'lib/types'
|
||||
import { Configuration } from 'lang/wasm'
|
||||
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
||||
|
||||
const toNormalizedCode = (text: string) => {
|
||||
return text.replace(/\s+/g, '')
|
||||
@ -928,10 +922,6 @@ export async function setup(
|
||||
// await page.reload()
|
||||
}
|
||||
|
||||
let electronApp: ElectronApplication | undefined = undefined
|
||||
let context: BrowserContext | undefined = undefined
|
||||
let page: Page | undefined = undefined
|
||||
|
||||
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
||||
// enabled for chrome for now
|
||||
if (page.context().browser()?.browserType().name() === 'chromium') {
|
||||
|
||||
@ -4,7 +4,6 @@ import { bracket } from 'lib/exampleKcl'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { join } from 'path'
|
||||
import { FILE_EXT } from 'lib/constants'
|
||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||
|
||||
test.describe('Testing in-app sample loading', () => {
|
||||
/**
|
||||
@ -49,8 +48,6 @@ test.describe('Testing in-app sample loading', () => {
|
||||
})
|
||||
const warningText = page.getByText('Overwrite current file and units?')
|
||||
const confirmButton = page.getByRole('button', { name: 'Submit command' })
|
||||
const unitsToast = (unit: UnitLength_type) =>
|
||||
page.getByText(`Set default unit to "${unit}" for this project`)
|
||||
|
||||
await test.step(`Precondition: check the initial code`, async () => {
|
||||
await u.openKclCodePanel()
|
||||
@ -125,8 +122,6 @@ test.describe('Testing in-app sample loading', () => {
|
||||
page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name }),
|
||||
})
|
||||
const unitsToast = (unit: UnitLength_type) =>
|
||||
page.getByText(`Set default unit to "${unit}" for this project`)
|
||||
|
||||
await test.step(`Test setup`, async () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
@ -159,7 +159,6 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
const unconstrainedLocator = page.locator(
|
||||
`[data-constraint-type="${constraintType}"][data-is-constrained="false"]`
|
||||
)
|
||||
await expect(unconstrainedLocator).toBeVisible()
|
||||
await unconstrainedLocator.hover()
|
||||
await expect(
|
||||
await page.getByTestId('constraint-symbol-popover').count()
|
||||
@ -274,8 +273,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
|
||||
let ang = 0
|
||||
|
||||
const line = await u.getBoundingBox(`[data-overlay-index="${0}"]`)
|
||||
ang = await u.getAngle(`[data-overlay-index="${0}"]`)
|
||||
const line = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
ang = await u.getAngle('[data-overlay-index="0"]')
|
||||
console.log('line1', line, ang)
|
||||
await clickConstrained({
|
||||
hoverPos: { x: line.x, y: line.y },
|
||||
@ -297,8 +296,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
locator: '[data-overlay-index="0"]',
|
||||
})
|
||||
|
||||
const angledLine = await u.getBoundingBox(`[data-overlay-index="1"]`)
|
||||
ang = await u.getAngle(`[data-overlay-index="1"]`)
|
||||
const angledLine = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
ang = await u.getAngle('[data-overlay-index="1"]')
|
||||
console.log('angledLine1')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: angledLine.x, y: angledLine.y },
|
||||
@ -327,8 +326,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
await page.mouse.move(700, 250)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
let lineTo = await u.getBoundingBox(`[data-overlay-index="2"]`)
|
||||
ang = await u.getAngle(`[data-overlay-index="2"]`)
|
||||
let lineTo = await u.getBoundingBox('[data-overlay-index="2"]')
|
||||
ang = await u.getAngle('[data-overlay-index="2"]')
|
||||
console.log('lineTo1')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: lineTo.x, y: lineTo.y },
|
||||
@ -353,8 +352,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
locator: '[data-overlay-toolbar-index="2"]',
|
||||
})
|
||||
|
||||
const xLineTo = await u.getBoundingBox(`[data-overlay-index="3"]`)
|
||||
ang = await u.getAngle(`[data-overlay-index="3"]`)
|
||||
const xLineTo = await u.getBoundingBox('[data-overlay-index="3"]')
|
||||
ang = await u.getAngle('[data-overlay-index="3"]')
|
||||
console.log('xlineTo1')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: xLineTo.x, y: xLineTo.y },
|
||||
@ -419,8 +418,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
|
||||
let ang = 0
|
||||
|
||||
const yLineTo = await u.getBoundingBox(`[data-overlay-index="4"]`)
|
||||
ang = await u.getAngle(`[data-overlay-index="4"]`)
|
||||
const yLineTo = await u.getBoundingBox('[data-overlay-index="4"]')
|
||||
ang = await u.getAngle('[data-overlay-index="4"]')
|
||||
console.log('ylineTo1')
|
||||
await clickUnconstrained({
|
||||
hoverPos: { x: yLineTo.x, y: yLineTo.y - 200 },
|
||||
@ -432,8 +431,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
locator: '[data-overlay-toolbar-index="4"]',
|
||||
})
|
||||
|
||||
const xLine = await u.getBoundingBox(`[data-overlay-index="5"]`)
|
||||
ang = await u.getAngle(`[data-overlay-index="5"]`)
|
||||
const xLine = await u.getBoundingBox('[data-overlay-index="5"]')
|
||||
ang = await u.getAngle('[data-overlay-index="5"]')
|
||||
console.log('xline')
|
||||
await clickUnconstrained({
|
||||
hoverPos: { x: xLine.x, y: xLine.y },
|
||||
@ -501,8 +500,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
|
||||
let ang = 0
|
||||
|
||||
const yLine = await u.getBoundingBox(`[data-overlay-index="6"]`)
|
||||
ang = await u.getAngle(`[data-overlay-index="6"]`)
|
||||
const yLine = await u.getBoundingBox('[data-overlay-index="6"]')
|
||||
ang = await u.getAngle('[data-overlay-index="6"]')
|
||||
console.log('yline1')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: yLine.x, y: yLine.y },
|
||||
@ -515,9 +514,9 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
const angledLineOfXLength = await u.getBoundingBox(
|
||||
`[data-overlay-index="7"]`
|
||||
'[data-overlay-index="7"]'
|
||||
)
|
||||
ang = await u.getAngle(`[data-overlay-index="7"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="7"]')
|
||||
console.log('angledLineOfXLength1')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: angledLineOfXLength.x, y: angledLineOfXLength.y },
|
||||
@ -547,9 +546,9 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
const angledLineOfYLength = await u.getBoundingBox(
|
||||
`[data-overlay-index="8"]`
|
||||
'[data-overlay-index="8"]'
|
||||
)
|
||||
ang = await u.getAngle(`[data-overlay-index="8"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="8"]')
|
||||
console.log('angledLineOfYLength1')
|
||||
await clickUnconstrained({
|
||||
hoverPos: { x: angledLineOfYLength.x, y: angledLineOfYLength.y },
|
||||
@ -632,8 +631,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
|
||||
let ang = 0
|
||||
|
||||
const angledLineToX = await u.getBoundingBox(`[data-overlay-index="9"]`)
|
||||
ang = await u.getAngle(`[data-overlay-index="9"]`)
|
||||
const angledLineToX = await u.getBoundingBox('[data-overlay-index="9"]')
|
||||
ang = await u.getAngle('[data-overlay-index="9"]')
|
||||
console.log('angledLineToX')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: angledLineToX.x, y: angledLineToX.y },
|
||||
@ -659,9 +658,9 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
const angledLineToY = await u.getBoundingBox(
|
||||
`[data-overlay-index="10"]`
|
||||
'[data-overlay-index="10"]'
|
||||
)
|
||||
ang = await u.getAngle(`[data-overlay-index="10"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="10"]')
|
||||
console.log('angledLineToY')
|
||||
await clickUnconstrained({
|
||||
hoverPos: { x: angledLineToY.x, y: angledLineToY.y },
|
||||
@ -689,9 +688,9 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
const angledLineThatIntersects = await u.getBoundingBox(
|
||||
`[data-overlay-index="11"]`
|
||||
'[data-overlay-index="11"]'
|
||||
)
|
||||
ang = await u.getAngle(`[data-overlay-index="11"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="11"]')
|
||||
console.log('angledLineThatIntersects')
|
||||
await clickUnconstrained({
|
||||
hoverPos: {
|
||||
@ -821,6 +820,138 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
locator: '[data-overlay-toolbar-index="12"]',
|
||||
})
|
||||
})
|
||||
test('for segment [arcTo]', async ({
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
scene,
|
||||
cmdBar,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
profile001 = startProfileAt([56.37, 120.33], sketch001)
|
||||
|> line(end = [162.86, 106.48])
|
||||
|> arcTo({
|
||||
interior = [360.16, 231.76],
|
||||
end = [391.48, 131.54]
|
||||
}, %)
|
||||
|> yLine(-131.54, %)
|
||||
|> arc({
|
||||
radius = 126.46,
|
||||
angleStart = 33.53,
|
||||
angleEnd = -141.07
|
||||
}, %)
|
||||
`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await scene.connectionEstablished()
|
||||
await scene.settled(cmdBar)
|
||||
|
||||
// wait for execution done
|
||||
|
||||
await page.getByText('line(end = [162.86, 106.48])').click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(5)
|
||||
|
||||
const clickUnconstrained = _clickUnconstrained(page, editor)
|
||||
const clickConstrained = _clickConstrained(page, editor)
|
||||
|
||||
const arcTo = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
let ang = await u.getAngle('[data-overlay-index="1"]')
|
||||
console.log('arcTo interior x')
|
||||
await clickUnconstrained({
|
||||
hoverPos: { x: arcTo.x, y: arcTo.y },
|
||||
constraintType: 'xAbsolute',
|
||||
expectBeforeUnconstrained: `arcTo({
|
||||
interior = [360.16, 231.76],
|
||||
end = [391.48, 131.54]
|
||||
}, %)`,
|
||||
expectAfterUnconstrained: `arcTo({
|
||||
interior = [360.16, 231.76],
|
||||
end = [391.48, 131.54]
|
||||
}, %)`,
|
||||
expectFinal: `arcTo({
|
||||
interior = [xAbs001, 231.76],
|
||||
end = [391.48, 131.54]
|
||||
}, %)`,
|
||||
ang: ang,
|
||||
steps: 6,
|
||||
locator: '[data-overlay-toolbar-index="1"]',
|
||||
})
|
||||
|
||||
console.log('arcTo interior y')
|
||||
await clickUnconstrained({
|
||||
hoverPos: { x: arcTo.x, y: arcTo.y },
|
||||
constraintType: 'yAbsolute',
|
||||
expectBeforeUnconstrained: `arcTo({
|
||||
interior = [xAbs001, 231.76],
|
||||
end = [391.48, 131.54]
|
||||
}, %)`,
|
||||
expectAfterUnconstrained: `arcTo({
|
||||
interior = [xAbs001, yAbs001],
|
||||
end = [391.48, 131.54]
|
||||
}, %)`,
|
||||
expectFinal: `arcTo({
|
||||
interior = [xAbs001, 231.76],
|
||||
end = [391.48, 131.54]
|
||||
}, %)`,
|
||||
ang: ang,
|
||||
steps: 10,
|
||||
locator: '[data-overlay-toolbar-index="1"]',
|
||||
})
|
||||
|
||||
console.log('arcTo end x')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: arcTo.x, y: arcTo.y },
|
||||
constraintType: 'xAbsolute',
|
||||
expectBeforeUnconstrained: `arcTo({
|
||||
interior = [xAbs001, 231.76],
|
||||
end = [391.48, 131.54]
|
||||
}, %)`,
|
||||
expectAfterUnconstrained: `arcTo({
|
||||
interior = [xAbs001, 231.76],
|
||||
end = [391.48, 131.54]
|
||||
}, %)`,
|
||||
expectFinal: `arcTo({
|
||||
interior = [xAbs001, 231.76],
|
||||
end = [xAbs002, 131.54]
|
||||
}, %)`,
|
||||
ang: ang + 180,
|
||||
steps: 6,
|
||||
locator: '[data-overlay-toolbar-index="1"]',
|
||||
})
|
||||
|
||||
console.log('arcTo end y')
|
||||
await clickUnconstrained({
|
||||
hoverPos: { x: arcTo.x, y: arcTo.y },
|
||||
constraintType: 'yAbsolute',
|
||||
expectBeforeUnconstrained: `arcTo({
|
||||
interior = [xAbs001, 231.76],
|
||||
end = [xAbs002, 131.54]
|
||||
}, %)`,
|
||||
expectAfterUnconstrained: `arcTo({
|
||||
interior = [xAbs001, 231.76],
|
||||
end = [xAbs002, yAbs002]
|
||||
}, %)`,
|
||||
expectFinal: `arcTo({
|
||||
interior = [xAbs001, 231.76],
|
||||
end = [xAbs002, 131.54]
|
||||
}, %)`,
|
||||
ang: ang + 180,
|
||||
steps: 10,
|
||||
locator: '[data-overlay-toolbar-index="1"]',
|
||||
})
|
||||
})
|
||||
test('for segment [circle]', async ({ page, editor, homePage }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -928,36 +1059,55 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
|
||||
await page.locator(`[data-stdlib-fn-name="${stdLibFnName}"]`).click()
|
||||
await page
|
||||
.locator(`[data-stdlib-fn-name="${stdLibFnName}"]`)
|
||||
.first()
|
||||
.click()
|
||||
await page.getByText('Delete Segment').click()
|
||||
|
||||
await editor.expectEditor.not.toContain(codeToBeDeleted, {
|
||||
shouldNormalise: true,
|
||||
})
|
||||
}
|
||||
test('all segment types', async ({ page, editor, homePage }) => {
|
||||
test('all segment types', async ({
|
||||
page,
|
||||
editor,
|
||||
homePage,
|
||||
scene,
|
||||
cmdBar,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line(end = [0.5, -14 + 0])
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> line(endAbsolute = [33, 11.5 + 0])
|
||||
|> xLine(endAbsolute = 9 - 5)
|
||||
|> yLine(endAbsolute = -10.77, tag = $a)
|
||||
|> xLine(length = 26.04)
|
||||
|> yLine(length = 21.14 + 0)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 13, 1.14], %)
|
||||
|>startProfileAt([0, 0], %)
|
||||
|> line(end = [0.5, -14 + 0])
|
||||
|> angledLine({ angle = 3 + 0, length = 32 + 0 }, %)
|
||||
|> line(endAbsolute = [33, 11.5 + 0])
|
||||
|> xLine(endAbsolute = 9 - 5)
|
||||
|> yLine(endAbsolute = -10.77, tag = $a)
|
||||
|> xLine(length = 26.04)
|
||||
|> yLine(length = 21.14 + 0)
|
||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||
|> angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)
|
||||
|> angledLineToX({ angle = 3 + 0, to = 26 }, %)
|
||||
|> angledLineToY({ angle = 89, to = 9.14 + 0 }, %)
|
||||
|> angledLineThatIntersects({
|
||||
angle = 4.14,
|
||||
intersectTag = a,
|
||||
offset = 9
|
||||
}, %)
|
||||
|> tangentialArcTo([3.14 + 13, 1.14], %)
|
||||
|> arcTo({
|
||||
interior = [16.25, 5.12],
|
||||
end = [21.61, 4.15]
|
||||
}, %)
|
||||
|> arc({
|
||||
radius = 9.03,
|
||||
angleStart = 40.27,
|
||||
angleEnd = -38.05
|
||||
}, %)
|
||||
|
||||
`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
@ -966,27 +1116,55 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await scene.connectionEstablished()
|
||||
await scene.settled(cmdBar)
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.getByText('xLine(endAbsolute = 9 - 5)').click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(13)
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(16)
|
||||
const deleteSegmentSequence = _deleteSegmentSequence(page, editor)
|
||||
|
||||
let segmentToDelete
|
||||
|
||||
const getOverlayByIndex = (index: number) =>
|
||||
u.getBoundingBox(`[data-overlay-index="${index}"]`)
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(14)
|
||||
let ang = await u.getAngle('[data-overlay-index="14"]')
|
||||
|
||||
await editor.scrollToText('angleEnd')
|
||||
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: `arc({
|
||||
radius = 9.03,
|
||||
angleStart = 40.27,
|
||||
angleEnd = -38.05
|
||||
}, %)`,
|
||||
stdLibFnName: 'arc',
|
||||
ang: ang + 180,
|
||||
steps: 6,
|
||||
locator: '[data-overlay-toolbar-index="14"]',
|
||||
})
|
||||
segmentToDelete = await getOverlayByIndex(13)
|
||||
ang = await u.getAngle('[data-overlay-index="13"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: `arcTo({
|
||||
interior = [16.25, 5.12],
|
||||
end = [21.61, 4.15]
|
||||
}, %)`,
|
||||
stdLibFnName: 'arcTo',
|
||||
ang: ang,
|
||||
steps: 6,
|
||||
locator: '[data-overlay-toolbar-index="13"]',
|
||||
})
|
||||
segmentToDelete = await getOverlayByIndex(12)
|
||||
let ang = await u.getAngle(`[data-overlay-index="${12}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="12"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'tangentialArcTo([3.14 + 13, 1.14], %)',
|
||||
@ -997,7 +1175,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(11)
|
||||
ang = await u.getAngle(`[data-overlay-index="${11}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="11"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: `angledLineThatIntersects({
|
||||
@ -1012,7 +1190,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(10)
|
||||
ang = await u.getAngle(`[data-overlay-index="${10}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="10"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'angledLineToY({ angle = 89, to = 9.14 + 0 }, %)',
|
||||
@ -1022,7 +1200,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(9)
|
||||
ang = await u.getAngle(`[data-overlay-index="${9}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="9"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'angledLineToX({ angle = 3 + 0, to = 26 }, %)',
|
||||
@ -1032,7 +1210,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(8)
|
||||
ang = await u.getAngle(`[data-overlay-index="${8}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="8"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted:
|
||||
@ -1043,7 +1221,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(7)
|
||||
ang = await u.getAngle(`[data-overlay-index="${7}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="7"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted:
|
||||
@ -1054,7 +1232,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(6)
|
||||
ang = await u.getAngle(`[data-overlay-index="${6}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="6"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'yLine(length = 21.14 + 0)',
|
||||
@ -1064,7 +1242,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(5)
|
||||
ang = await u.getAngle(`[data-overlay-index="${5}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="5"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'xLine(length = 26.04)',
|
||||
@ -1074,7 +1252,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(4)
|
||||
ang = await u.getAngle(`[data-overlay-index="${4}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="4"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'yLine(endAbsolute = -10.77, tag = $a)',
|
||||
@ -1084,7 +1262,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(3)
|
||||
ang = await u.getAngle(`[data-overlay-index="${3}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="3"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'xLine(endAbsolute = 9 - 5)',
|
||||
@ -1094,7 +1272,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(2)
|
||||
ang = await u.getAngle(`[data-overlay-index="${2}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="2"]')
|
||||
await expect(page.getByText('Added variable')).not.toBeVisible()
|
||||
|
||||
const hoverPos = { x: segmentToDelete.x, y: segmentToDelete.y }
|
||||
@ -1127,7 +1305,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(1)
|
||||
ang = await u.getAngle(`[data-overlay-index="${1}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="1"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'angledLine({ angle = 3 + 0, length = 32 + 0 }, %)',
|
||||
@ -1137,7 +1315,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(0)
|
||||
ang = await u.getAngle(`[data-overlay-index="${0}"]`)
|
||||
ang = await u.getAngle('[data-overlay-index="0"]')
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'line(end = [0.5, -14 + 0])',
|
||||
@ -1208,7 +1386,8 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
return true
|
||||
} catch (_) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (_e) {
|
||||
return false
|
||||
}
|
||||
})
|
||||
@ -1366,7 +1545,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
||||
await expect(page.getByText('Added variable')).not.toBeVisible()
|
||||
|
||||
const hoverPos = await u.getBoundingBox(`[data-overlay-index="0"]`)
|
||||
let ang = await u.getAngle(`[data-overlay-index="${0}"]`)
|
||||
let ang = await u.getAngle('[data-overlay-index="0"]')
|
||||
ang += 180
|
||||
|
||||
await page.mouse.move(0, 0)
|
||||
|
||||
@ -364,7 +364,6 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
||||
await camPosition1()
|
||||
|
||||
const revolve = { x: 635, y: 253 }
|
||||
const parentExtrude = { x: 915, y: 133 }
|
||||
const solid2d = { x: 770, y: 167 }
|
||||
const individualProfile = { x: 694, y: 432 }
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import {
|
||||
createProject,
|
||||
tomlToSettings,
|
||||
} from './test-utils'
|
||||
import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes'
|
||||
import { SettingsLevel } from 'lib/settings/settingsTypes'
|
||||
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
|
||||
import {
|
||||
TEST_SETTINGS_KEY,
|
||||
@ -15,7 +15,6 @@ import {
|
||||
TEST_SETTINGS,
|
||||
TEST_SETTINGS_DEFAULT_THEME,
|
||||
} from './storageStates'
|
||||
import * as TOML from '@iarna/toml'
|
||||
import { DeepPartial } from 'lib/types'
|
||||
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
||||
|
||||
@ -1006,11 +1005,6 @@ fn cube`
|
||||
page.getByText(
|
||||
`Set highlight edges to "${String(value)}" as a user default`
|
||||
)
|
||||
const initialPath = testInfo.snapshotPath('toggle-settings-initial.png')
|
||||
const initialScreenshot = await scene.streamWrapper.screenshot({
|
||||
path: initialPath,
|
||||
mask: [page.getByTestId('model-state-indicator')],
|
||||
})
|
||||
|
||||
await test.step(`Toggle highlightEdges off`, async () => {
|
||||
await cmdBar.openCmdBar()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
/* eslint-disable react-hooks/rules-of-hooks */
|
||||
|
||||
import { test as playwrightTestFn, ElectronApplication } from '@playwright/test'
|
||||
import { test as playwrightTestFn } from '@playwright/test'
|
||||
|
||||
import {
|
||||
fixturesBasedOnProcessEnvPlatform,
|
||||
@ -8,8 +8,6 @@ import {
|
||||
ElectronZoo,
|
||||
} from './fixtures/fixtureSetup'
|
||||
|
||||
import { Settings } from '@rust/kcl-lib/bindings/Settings'
|
||||
import { DeepPartial } from 'lib/types'
|
||||
export { expect } from '@playwright/test'
|
||||
|
||||
declare module '@playwright/test' {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { defineConfig, devices } from '@playwright/test'
|
||||
import { platform } from 'os'
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
@ -13,7 +14,7 @@ export default defineConfig({
|
||||
/* Do not retry */
|
||||
retries: 0,
|
||||
/* Different amount of parallelism on CI and local. */
|
||||
workers: 1,
|
||||
workers: platform() === 'win32' ? 1 : 2,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
['dot'],
|
||||
|
||||
27
rust/Cargo.lock
generated
@ -2194,6 +2194,17 @@ dependencies = [
|
||||
"crc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lzma-sys"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "measurements"
|
||||
version = "0.11.0"
|
||||
@ -4862,6 +4873,15 @@ version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32ac00cd3f8ec9c1d33fb3e7958a82df6989c42d747bd326c822b1d625283547"
|
||||
|
||||
[[package]]
|
||||
name = "xz2"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
|
||||
dependencies = [
|
||||
"lzma-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yaml-rust"
|
||||
version = "0.4.5"
|
||||
@ -5006,9 +5026,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "2.2.3"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b280484c454e74e5fff658bbf7df8fdbe7a07c6b2de4a53def232c15ef138f3a"
|
||||
checksum = "938cc23ac49778ac8340e366ddc422b2227ea176edb447e23fc0627608dddadd"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"arbitrary",
|
||||
@ -5019,15 +5039,16 @@ dependencies = [
|
||||
"deflate64",
|
||||
"displaydoc",
|
||||
"flate2",
|
||||
"getrandom 0.3.1",
|
||||
"hmac",
|
||||
"indexmap 2.8.0",
|
||||
"lzma-rs",
|
||||
"memchr",
|
||||
"pbkdf2",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"xz2",
|
||||
"zeroize",
|
||||
"zopfli",
|
||||
"zstd",
|
||||
|
||||
@ -49,7 +49,7 @@ tokio = { version = "1" }
|
||||
tower-lsp = { version = "0.20.0", default-features = false }
|
||||
tracing-subscriber = { version = "0.3.19", features = ["registry", "std", "fmt", "smallvec", "ansi", "tracing-log", "json"] }
|
||||
uuid = { version = "1", features = ["v4", "serde"] }
|
||||
zip = { version = "2.2.2", default-features = false }
|
||||
zip = { version = "2.4.1", default-features = false }
|
||||
|
||||
[workspace.lints.clippy]
|
||||
assertions_on_result_states = "warn"
|
||||
|
||||
@ -248,6 +248,7 @@ export class Ctx {
|
||||
this.clientSubscriptions = []
|
||||
try {
|
||||
await this._client?.dispose(2000)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (e) {
|
||||
// DO nothing.
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ use tokio::sync::{mpsc, oneshot, RwLock};
|
||||
use tokio_tungstenite::tungstenite::Message as WsMsg;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::ExecutionKind;
|
||||
use super::{EngineStats, ExecutionKind};
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
@ -52,6 +52,7 @@ pub struct EngineConnection {
|
||||
session_data: Arc<RwLock<Option<ModelingSessionData>>>,
|
||||
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
stats: EngineStats,
|
||||
}
|
||||
|
||||
pub struct TcpRead {
|
||||
@ -344,6 +345,7 @@ impl EngineConnection {
|
||||
default_planes: Default::default(),
|
||||
session_data,
|
||||
execution_kind: Default::default(),
|
||||
stats: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -378,6 +380,10 @@ impl EngineManager for EngineConnection {
|
||||
original
|
||||
}
|
||||
|
||||
fn stats(&self) -> &EngineStats {
|
||||
&self.stats
|
||||
}
|
||||
|
||||
fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>> {
|
||||
self.default_planes.clone()
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ use kittycad_modeling_cmds::{self as kcmc};
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::ExecutionKind;
|
||||
use super::{EngineStats, ExecutionKind};
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
exec::DefaultPlanes,
|
||||
@ -32,6 +32,7 @@ pub struct EngineConnection {
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
/// The default planes for the scene.
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
stats: EngineStats,
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
@ -42,6 +43,7 @@ impl EngineConnection {
|
||||
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
||||
execution_kind: Default::default(),
|
||||
default_planes: Default::default(),
|
||||
stats: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -60,6 +62,10 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
Arc::new(RwLock::new(IndexMap::new()))
|
||||
}
|
||||
|
||||
fn stats(&self) -> &EngineStats {
|
||||
&self.stats
|
||||
}
|
||||
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
|
||||
self.artifact_commands.clone()
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ use uuid::Uuid;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
use crate::{
|
||||
engine::ExecutionKind,
|
||||
engine::{EngineStats, ExecutionKind},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{ArtifactCommand, DefaultPlanes, IdGenerator},
|
||||
SourceRange,
|
||||
@ -45,6 +45,7 @@ pub struct EngineConnection {
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
/// The default planes for the scene.
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
stats: EngineStats,
|
||||
}
|
||||
|
||||
// Safety: WebAssembly will only ever run in a single-threaded context.
|
||||
@ -62,6 +63,7 @@ impl EngineConnection {
|
||||
artifact_commands: Arc::new(RwLock::new(Vec::new())),
|
||||
execution_kind: Default::default(),
|
||||
default_planes: Default::default(),
|
||||
stats: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -141,6 +143,10 @@ impl crate::engine::EngineManager for EngineConnection {
|
||||
self.responses.clone()
|
||||
}
|
||||
|
||||
fn stats(&self) -> &EngineStats {
|
||||
&self.stats
|
||||
}
|
||||
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
|
||||
self.artifact_commands.clone()
|
||||
}
|
||||
|
||||
@ -8,7 +8,13 @@ pub mod conn_mock;
|
||||
#[cfg(feature = "engine")]
|
||||
pub mod conn_wasm;
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use kcmc::{
|
||||
@ -58,6 +64,21 @@ impl ExecutionKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct EngineStats {
|
||||
pub commands_batched: AtomicUsize,
|
||||
pub batches_sent: AtomicUsize,
|
||||
}
|
||||
|
||||
impl Clone for EngineStats {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
commands_batched: AtomicUsize::new(self.commands_batched.load(Ordering::Relaxed)),
|
||||
batches_sent: AtomicUsize::new(self.batches_sent.load(Ordering::Relaxed)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
/// Get the batch of commands to be sent to the engine.
|
||||
@ -97,6 +118,8 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
/// Get the default planes.
|
||||
fn get_default_planes(&self) -> Arc<RwLock<Option<DefaultPlanes>>>;
|
||||
|
||||
fn stats(&self) -> &EngineStats;
|
||||
|
||||
/// Get the default planes, creating them if they don't exist.
|
||||
async fn default_planes(
|
||||
&self,
|
||||
@ -254,6 +277,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
|
||||
// Add cmd to the batch.
|
||||
self.batch().write().await.push((req, source_range));
|
||||
self.stats().commands_batched.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -277,6 +301,9 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
for cmd in cmds {
|
||||
extended_cmds.push((WebSocketRequest::ModelingCmdReq(cmd.clone()), source_range));
|
||||
}
|
||||
self.stats()
|
||||
.commands_batched
|
||||
.fetch_add(extended_cmds.len(), Ordering::Relaxed);
|
||||
self.batch().write().await.extend(extended_cmds);
|
||||
|
||||
Ok(())
|
||||
@ -303,6 +330,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
|
||||
// Add cmd to the batch end.
|
||||
self.batch_end().write().await.insert(id, (req, source_range));
|
||||
self.stats().commands_batched.fetch_add(1, Ordering::Relaxed);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -405,6 +433,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
if batch_end {
|
||||
self.batch_end().write().await.clear();
|
||||
}
|
||||
self.stats().batches_sent.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
// We pop off the responses to cleanup our mappings.
|
||||
match final_req {
|
||||
|
||||
@ -832,6 +832,19 @@ pub enum Path {
|
||||
#[ts(type = "[number, number]")]
|
||||
p3: [f64; 2],
|
||||
},
|
||||
ArcThreePoint {
|
||||
#[serde(flatten)]
|
||||
base: BasePath,
|
||||
/// Point 1 of the arc (base on the end of previous segment)
|
||||
#[ts(type = "[number, number]")]
|
||||
p1: [f64; 2],
|
||||
/// Point 2 of the arc (interior kwarg)
|
||||
#[ts(type = "[number, number]")]
|
||||
p2: [f64; 2],
|
||||
/// Point 3 of the arc (end kwarg)
|
||||
#[ts(type = "[number, number]")]
|
||||
p3: [f64; 2],
|
||||
},
|
||||
/// A path that is horizontal.
|
||||
Horizontal {
|
||||
#[serde(flatten)]
|
||||
@ -892,6 +905,7 @@ impl From<&Path> for PathType {
|
||||
Path::AngledLineTo { .. } => Self::AngledLineTo,
|
||||
Path::Base { .. } => Self::Base,
|
||||
Path::Arc { .. } => Self::Arc,
|
||||
Path::ArcThreePoint { .. } => Self::Arc,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -908,6 +922,7 @@ impl Path {
|
||||
Path::Circle { base, .. } => base.geo_meta.id,
|
||||
Path::CircleThreePoint { base, .. } => base.geo_meta.id,
|
||||
Path::Arc { base, .. } => base.geo_meta.id,
|
||||
Path::ArcThreePoint { base, .. } => base.geo_meta.id,
|
||||
}
|
||||
}
|
||||
|
||||
@ -922,6 +937,7 @@ impl Path {
|
||||
Path::Circle { base, .. } => base.tag.clone(),
|
||||
Path::CircleThreePoint { base, .. } => base.tag.clone(),
|
||||
Path::Arc { base, .. } => base.tag.clone(),
|
||||
Path::ArcThreePoint { base, .. } => base.tag.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -936,6 +952,7 @@ impl Path {
|
||||
Path::Circle { base, .. } => base,
|
||||
Path::CircleThreePoint { base, .. } => base,
|
||||
Path::Arc { base, .. } => base,
|
||||
Path::ArcThreePoint { base, .. } => base,
|
||||
}
|
||||
}
|
||||
|
||||
@ -985,6 +1002,10 @@ impl Path {
|
||||
// TODO: Call engine utils to figure this out.
|
||||
linear_distance(self.get_from(), self.get_to())
|
||||
}
|
||||
Self::ArcThreePoint { .. } => {
|
||||
// TODO: Call engine utils to figure this out.
|
||||
linear_distance(self.get_from(), self.get_to())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -999,6 +1020,7 @@ impl Path {
|
||||
Path::Circle { base, .. } => Some(base),
|
||||
Path::CircleThreePoint { base, .. } => Some(base),
|
||||
Path::Arc { base, .. } => Some(base),
|
||||
Path::ArcThreePoint { base, .. } => Some(base),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1010,6 +1032,17 @@ impl Path {
|
||||
center: *center,
|
||||
ccw: *ccw,
|
||||
},
|
||||
Path::ArcThreePoint { p1, p2, p3, .. } => {
|
||||
let circle_center =
|
||||
crate::std::utils::calculate_circle_from_3_points([(*p1).into(), (*p2).into(), (*p3).into()]);
|
||||
let radius = linear_distance(&[circle_center.center.x, circle_center.center.y], p1);
|
||||
let center_point = [circle_center.center.x, circle_center.center.y];
|
||||
GetTangentialInfoFromPathsResult::Circle {
|
||||
center: center_point,
|
||||
ccw: true,
|
||||
radius,
|
||||
}
|
||||
}
|
||||
Path::Circle {
|
||||
center, ccw, radius, ..
|
||||
} => GetTangentialInfoFromPathsResult::Circle {
|
||||
|
||||
@ -430,21 +430,14 @@ impl ExecutorContext {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub async fn new_mock(
|
||||
fs_manager: crate::fs::wasm::FileSystemManager,
|
||||
settings: ExecutorSettings,
|
||||
) -> Result<Self, String> {
|
||||
Ok(ExecutorContext {
|
||||
engine: Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))?,
|
||||
)),
|
||||
fs: Arc::new(FileManager::new(fs_manager)),
|
||||
pub fn new_mock(engine: Arc<Box<dyn EngineManager>>, fs: Arc<FileManager>, settings: ExecutorSettings) -> Self {
|
||||
ExecutorContext {
|
||||
engine,
|
||||
fs,
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings,
|
||||
context_type: ContextType::Mock,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -757,6 +750,7 @@ impl ExecutorContext {
|
||||
"Post interpretation KCL memory stats: {:#?}",
|
||||
exec_state.stack().memory.stats
|
||||
));
|
||||
crate::log::log(format!("Engine stats: {:?}", self.engine.stats()));
|
||||
|
||||
if !self.is_mock() {
|
||||
let mut mem = exec_state.stack().deep_clone();
|
||||
|
||||
@ -81,7 +81,7 @@ mod walk;
|
||||
mod wasm;
|
||||
|
||||
pub use coredump::CoreDump;
|
||||
pub use engine::{EngineManager, ExecutionKind};
|
||||
pub use engine::{EngineManager, EngineStats, ExecutionKind};
|
||||
pub use errors::{
|
||||
CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs, Report, ReportWithOutputs,
|
||||
};
|
||||
@ -96,6 +96,8 @@ pub use modules::ModuleId;
|
||||
pub use parsing::ast::{modify::modify_ast_for_sketch, types::FormatOptions};
|
||||
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
|
||||
pub use source_range::SourceRange;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use unparser::recast_dir;
|
||||
|
||||
// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
|
||||
// Ideally we wouldn't export these things at all, they should only be used for testing.
|
||||
@ -112,6 +114,10 @@ pub mod wasm_engine {
|
||||
};
|
||||
}
|
||||
|
||||
pub mod mock_engine {
|
||||
pub use crate::engine::conn_mock::EngineConnection;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod native_engine {
|
||||
pub use crate::engine::conn::EngineConnection;
|
||||
|
||||
@ -11,7 +11,13 @@ use kcl_lib::{ExecState, ExecutorContext, ExecutorSettings, Program};
|
||||
async fn main() {
|
||||
let mut args = env::args();
|
||||
args.next();
|
||||
let filename = args.next().unwrap_or_else(|| "main.kcl".to_owned());
|
||||
let mut filename = args.next().unwrap_or_else(|| "main.kcl".to_owned());
|
||||
if !filename.ends_with(".kcl") {
|
||||
if !filename.ends_with('/') && !filename.ends_with('\\') {
|
||||
filename += "/";
|
||||
}
|
||||
filename += "main.kcl";
|
||||
}
|
||||
|
||||
let mut f = File::open(&filename).unwrap();
|
||||
let mut text = String::new();
|
||||
|
||||
@ -34,10 +34,10 @@ fn parse(dir_name: &str, dir_path: &Path) {
|
||||
}
|
||||
|
||||
#[kcl_directory_test_macro::test_all_dirs("../public/kcl-samples")]
|
||||
fn unparse(dir_name: &str, dir_path: &Path) {
|
||||
// kcl-samples don't always use correct formatting. We don't ignore the
|
||||
// test because we want to allow the just command to work. It's actually
|
||||
// fine when no test runs.
|
||||
async fn unparse(dir_name: &str, dir_path: &Path) {
|
||||
// TODO: turn this on when we fix the comments recasting.
|
||||
// let t = test(dir_name, dir_path.join("main.kcl").to_str().unwrap().to_owned());
|
||||
// super::unparse_test(&t).await;
|
||||
}
|
||||
|
||||
#[kcl_directory_test_macro::test_all_dirs("../public/kcl-samples")]
|
||||
|
||||
@ -265,6 +265,17 @@ pub(crate) async fn do_post_extrude(
|
||||
});
|
||||
Some(extrude_surface)
|
||||
}
|
||||
Path::ArcThreePoint { .. } => {
|
||||
let extrude_surface = ExtrudeSurface::ExtrudeArc(crate::execution::ExtrudeArc {
|
||||
face_id: *actual_face_id,
|
||||
tag: path.get_base().tag.clone(),
|
||||
geo_meta: GeoMeta {
|
||||
id: path.get_base().geo_meta.id,
|
||||
metadata: path.get_base().geo_meta.metadata,
|
||||
},
|
||||
});
|
||||
Some(extrude_surface)
|
||||
}
|
||||
}
|
||||
} else if no_engine_commands {
|
||||
// Only pre-populate the extrude surface if we are in mock mode.
|
||||
|
||||
@ -23,8 +23,8 @@ use crate::{
|
||||
std::{
|
||||
args::{Args, TyF64},
|
||||
utils::{
|
||||
arc_angles, arc_center_and_end, calculate_circle_center, get_tangential_arc_to_info, get_x_component,
|
||||
get_y_component, intersection_with_parallel_line, TangentialArcInfoInput,
|
||||
arc_angles, arc_center_and_end, get_tangential_arc_to_info, get_x_component, get_y_component,
|
||||
intersection_with_parallel_line, TangentialArcInfoInput,
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -1682,18 +1682,7 @@ pub(crate) async fn inner_arc_to(
|
||||
let interior = data.interior;
|
||||
let end = data.end;
|
||||
|
||||
// compute the center of the circle since we do not have the value returned from the engine
|
||||
let center = calculate_circle_center(start, interior, end);
|
||||
|
||||
// compute the radius since we do not have the value returned from the engine
|
||||
// Pick any of the 3 points since they all lie along the circle
|
||||
let sum_of_square_differences =
|
||||
(center[0] - start[0] * center[0] - start[0]) + (center[1] - start[1] * center[1] - start[1]);
|
||||
let radius = sum_of_square_differences.sqrt();
|
||||
|
||||
let ccw = is_ccw(start, interior, end);
|
||||
|
||||
let current_path = Path::Arc {
|
||||
let current_path = Path::ArcThreePoint {
|
||||
base: BasePath {
|
||||
from: from.into(),
|
||||
to: data.end,
|
||||
@ -1704,9 +1693,9 @@ pub(crate) async fn inner_arc_to(
|
||||
metadata: args.source_range.into(),
|
||||
},
|
||||
},
|
||||
center,
|
||||
radius,
|
||||
ccw,
|
||||
p1: start,
|
||||
p2: interior,
|
||||
p3: end,
|
||||
};
|
||||
|
||||
let mut new_sketch = sketch.clone();
|
||||
@ -1719,26 +1708,6 @@ pub(crate) async fn inner_arc_to(
|
||||
Ok(new_sketch)
|
||||
}
|
||||
|
||||
/// Returns true if the three-point arc is counterclockwise. The order of
|
||||
/// parameters is critical.
|
||||
///
|
||||
/// | end
|
||||
/// | /
|
||||
/// | | / interior
|
||||
/// | / /
|
||||
/// | | /
|
||||
/// |/_____________
|
||||
/// start
|
||||
///
|
||||
/// If the slope of the line from start to interior is less than the slope of
|
||||
/// the line from start to end, the arc is counterclockwise.
|
||||
fn is_ccw(start: [f64; 2], interior: [f64; 2], end: [f64; 2]) -> bool {
|
||||
let t1 = (interior[0] - start[0]) * (end[1] - start[1]);
|
||||
let t2 = (end[0] - start[0]) * (interior[1] - start[1]);
|
||||
// If these terms are equal, the points are collinear.
|
||||
t1 > t2
|
||||
}
|
||||
|
||||
/// Data to draw a tangential arc.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
|
||||
@ -842,6 +842,103 @@ impl Type {
|
||||
}
|
||||
}
|
||||
|
||||
/// Collect all the kcl files in a directory, recursively.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[async_recursion::async_recursion]
|
||||
pub(crate) async fn walk_dir(dir: &std::path::PathBuf) -> Result<Vec<std::path::PathBuf>, anyhow::Error> {
|
||||
// Make sure we actually have a directory.
|
||||
if !dir.is_dir() {
|
||||
anyhow::bail!("`{}` is not a directory", dir.display());
|
||||
}
|
||||
|
||||
let mut entries = tokio::fs::read_dir(dir).await?;
|
||||
|
||||
let mut files = Vec::new();
|
||||
while let Some(entry) = entries.next_entry().await? {
|
||||
let path = entry.path();
|
||||
|
||||
if path.is_dir() {
|
||||
files.extend(walk_dir(&path).await?);
|
||||
} else if path.extension().is_some_and(|ext| ext == "kcl") {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
/// Recast all the kcl files in a directory, recursively.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub async fn recast_dir(dir: &std::path::Path, options: &crate::FormatOptions) -> Result<(), crate::KclError> {
|
||||
let files = walk_dir(&dir.to_path_buf()).await.map_err(|err| {
|
||||
crate::KclError::Internal(crate::errors::KclErrorDetails {
|
||||
message: format!("Failed to walk directory `{}`: {:?}", dir.display(), err),
|
||||
source_ranges: vec![crate::SourceRange::default()],
|
||||
})
|
||||
})?;
|
||||
|
||||
let futures = files
|
||||
.into_iter()
|
||||
.map(|file| {
|
||||
let options = options.clone();
|
||||
tokio::spawn(async move {
|
||||
let contents = tokio::fs::read_to_string(&file).await.map_err(|err| {
|
||||
crate::KclError::Internal(crate::errors::KclErrorDetails {
|
||||
message: format!("Failed to read file `{}`: {:?}", file.display(), err),
|
||||
source_ranges: vec![crate::SourceRange::default()],
|
||||
})
|
||||
})?;
|
||||
let (program, ces) = crate::Program::parse(&contents)?;
|
||||
for ce in &ces {
|
||||
if ce.severity != crate::errors::Severity::Warning {
|
||||
return Err(crate::KclError::Semantic(ce.clone().into()));
|
||||
}
|
||||
}
|
||||
let Some(program) = program else {
|
||||
return Err(crate::KclError::Internal(crate::errors::KclErrorDetails {
|
||||
message: format!("Failed to parse file `{}`: {:?}", file.display(), ces),
|
||||
source_ranges: vec![crate::SourceRange::default()],
|
||||
}));
|
||||
};
|
||||
let recast = program.recast_with_options(&options);
|
||||
tokio::fs::write(&file, recast).await.map_err(|err| {
|
||||
crate::KclError::Internal(crate::errors::KclErrorDetails {
|
||||
message: format!("Failed to write file `{}`: {:?}", file.display(), err),
|
||||
source_ranges: vec![crate::SourceRange::default()],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok::<(), crate::KclError>(())
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Join all futures and await their completion
|
||||
let results = futures::future::join_all(futures).await;
|
||||
|
||||
// Check if any of the futures failed.
|
||||
let mut errors = Vec::new();
|
||||
for result in results {
|
||||
if let Err(err) = result.map_err(|err| {
|
||||
crate::KclError::Internal(crate::errors::KclErrorDetails {
|
||||
message: format!("Failed to recast file: {:?}", err),
|
||||
source_ranges: vec![crate::SourceRange::default()],
|
||||
})
|
||||
})? {
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
if !errors.is_empty() {
|
||||
return Err(crate::KclError::Internal(crate::errors::KclErrorDetails {
|
||||
message: format!("Failed to recast files: {:?}", errors),
|
||||
source_ranges: vec![crate::SourceRange::default()],
|
||||
}));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@ -80,8 +80,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
47,
|
||||
66,
|
||||
48,
|
||||
67,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -109,8 +109,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
76,
|
||||
113,
|
||||
77,
|
||||
114,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -129,8 +129,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
76,
|
||||
113,
|
||||
77,
|
||||
114,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -140,8 +140,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
76,
|
||||
113,
|
||||
77,
|
||||
114,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -157,8 +157,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
76,
|
||||
113,
|
||||
77,
|
||||
114,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -168,8 +168,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
119,
|
||||
136,
|
||||
120,
|
||||
137,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -189,8 +189,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
142,
|
||||
160,
|
||||
143,
|
||||
161,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -210,8 +210,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
166,
|
||||
184,
|
||||
167,
|
||||
185,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -231,8 +231,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
190,
|
||||
246,
|
||||
191,
|
||||
247,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -252,8 +252,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
252,
|
||||
259,
|
||||
253,
|
||||
260,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -264,8 +264,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -284,8 +284,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -298,8 +298,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -309,8 +309,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -321,8 +321,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -334,8 +334,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -348,8 +348,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -362,8 +362,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -376,8 +376,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -390,8 +390,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -404,8 +404,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -418,8 +418,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -432,8 +432,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -458,8 +458,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
47,
|
||||
66,
|
||||
48,
|
||||
67,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -487,8 +487,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
76,
|
||||
111,
|
||||
77,
|
||||
112,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -507,8 +507,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
76,
|
||||
111,
|
||||
77,
|
||||
112,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -518,8 +518,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
76,
|
||||
111,
|
||||
77,
|
||||
112,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -535,8 +535,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
76,
|
||||
111,
|
||||
77,
|
||||
112,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -546,8 +546,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
117,
|
||||
134,
|
||||
118,
|
||||
135,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -567,8 +567,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
140,
|
||||
158,
|
||||
141,
|
||||
159,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -588,8 +588,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
164,
|
||||
182,
|
||||
165,
|
||||
183,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -609,8 +609,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
188,
|
||||
244,
|
||||
189,
|
||||
245,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -630,8 +630,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
250,
|
||||
257,
|
||||
251,
|
||||
258,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -642,8 +642,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -662,8 +662,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -676,8 +676,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -687,8 +687,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -699,8 +699,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -712,8 +712,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -726,8 +726,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -740,8 +740,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -754,8 +754,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -768,8 +768,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -782,8 +782,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -796,8 +796,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
@ -810,8 +810,8 @@ description: Artifact commands assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"command": {
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph path2 [Path]
|
||||
2["Path<br>[76, 113, 3]"]
|
||||
3["Segment<br>[119, 136, 3]"]
|
||||
4["Segment<br>[142, 160, 3]"]
|
||||
5["Segment<br>[166, 184, 3]"]
|
||||
6["Segment<br>[190, 246, 3]"]
|
||||
7["Segment<br>[252, 259, 3]"]
|
||||
2["Path<br>[77, 114, 3]"]
|
||||
3["Segment<br>[120, 137, 3]"]
|
||||
4["Segment<br>[143, 161, 3]"]
|
||||
5["Segment<br>[167, 185, 3]"]
|
||||
6["Segment<br>[191, 247, 3]"]
|
||||
7["Segment<br>[253, 260, 3]"]
|
||||
8[Solid2d]
|
||||
end
|
||||
subgraph path25 [Path]
|
||||
25["Path<br>[76, 111, 4]"]
|
||||
26["Segment<br>[117, 134, 4]"]
|
||||
27["Segment<br>[140, 158, 4]"]
|
||||
28["Segment<br>[164, 182, 4]"]
|
||||
29["Segment<br>[188, 244, 4]"]
|
||||
30["Segment<br>[250, 257, 4]"]
|
||||
25["Path<br>[77, 112, 4]"]
|
||||
26["Segment<br>[118, 135, 4]"]
|
||||
27["Segment<br>[141, 159, 4]"]
|
||||
28["Segment<br>[165, 183, 4]"]
|
||||
29["Segment<br>[189, 245, 4]"]
|
||||
30["Segment<br>[251, 258, 4]"]
|
||||
31[Solid2d]
|
||||
end
|
||||
1["Plane<br>[47, 66, 3]"]
|
||||
9["Sweep Extrusion<br>[265, 287, 3]"]
|
||||
1["Plane<br>[48, 67, 3]"]
|
||||
9["Sweep Extrusion<br>[266, 288, 3]"]
|
||||
10[Wall]
|
||||
11[Wall]
|
||||
12[Wall]
|
||||
@ -34,8 +34,8 @@ flowchart LR
|
||||
21["SweepEdge Adjacent"]
|
||||
22["SweepEdge Opposite"]
|
||||
23["SweepEdge Adjacent"]
|
||||
24["Plane<br>[47, 66, 4]"]
|
||||
32["Sweep Extrusion<br>[263, 285, 4]"]
|
||||
24["Plane<br>[48, 67, 4]"]
|
||||
32["Sweep Extrusion<br>[264, 286, 4]"]
|
||||
33[Wall]
|
||||
34[Wall]
|
||||
35[Wall]
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
|
||||
sketch001 = startSketchOn('XY')
|
||||
cubeIn = startProfileAt([-10, -10], sketch001)
|
||||
|> xLine(length = 5)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
|
||||
sketch001 = startSketchOn('XY')
|
||||
cubeMm = startProfileAt([10, 10], sketch001)
|
||||
|> xLine(length = 5)
|
||||
|
||||
@ -11,16 +11,16 @@ description: Operations executed assembly_mixed_units_cubes.kcl
|
||||
"value": "XY"
|
||||
},
|
||||
"sourceRange": [
|
||||
61,
|
||||
65,
|
||||
62,
|
||||
66,
|
||||
3
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "startSketchOn",
|
||||
"sourceRange": [
|
||||
47,
|
||||
66,
|
||||
48,
|
||||
67,
|
||||
3
|
||||
],
|
||||
"type": "StdLibCall",
|
||||
@ -43,16 +43,16 @@ description: Operations executed assembly_mixed_units_cubes.kcl
|
||||
}
|
||||
},
|
||||
"sourceRange": [
|
||||
285,
|
||||
286,
|
||||
287,
|
||||
3
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "extrude",
|
||||
"sourceRange": [
|
||||
265,
|
||||
287,
|
||||
266,
|
||||
288,
|
||||
3
|
||||
],
|
||||
"type": "StdLibCall",
|
||||
@ -64,8 +64,8 @@ description: Operations executed assembly_mixed_units_cubes.kcl
|
||||
}
|
||||
},
|
||||
"sourceRange": [
|
||||
273,
|
||||
274,
|
||||
275,
|
||||
3
|
||||
]
|
||||
}
|
||||
@ -78,16 +78,16 @@ description: Operations executed assembly_mixed_units_cubes.kcl
|
||||
"value": "XY"
|
||||
},
|
||||
"sourceRange": [
|
||||
61,
|
||||
65,
|
||||
62,
|
||||
66,
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "startSketchOn",
|
||||
"sourceRange": [
|
||||
47,
|
||||
66,
|
||||
48,
|
||||
67,
|
||||
4
|
||||
],
|
||||
"type": "StdLibCall",
|
||||
@ -110,16 +110,16 @@ description: Operations executed assembly_mixed_units_cubes.kcl
|
||||
}
|
||||
},
|
||||
"sourceRange": [
|
||||
283,
|
||||
284,
|
||||
285,
|
||||
4
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "extrude",
|
||||
"sourceRange": [
|
||||
263,
|
||||
285,
|
||||
264,
|
||||
286,
|
||||
4
|
||||
],
|
||||
"type": "StdLibCall",
|
||||
@ -131,8 +131,8 @@ description: Operations executed assembly_mixed_units_cubes.kcl
|
||||
}
|
||||
},
|
||||
"sourceRange": [
|
||||
271,
|
||||
272,
|
||||
273,
|
||||
4
|
||||
]
|
||||
}
|
||||
|
||||
@ -80,8 +80,8 @@ description: Artifact commands assembly_non_default_units.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
172,
|
||||
191,
|
||||
173,
|
||||
192,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -109,8 +109,8 @@ description: Artifact commands assembly_non_default_units.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
197,
|
||||
232,
|
||||
198,
|
||||
233,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -129,8 +129,8 @@ description: Artifact commands assembly_non_default_units.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
197,
|
||||
232,
|
||||
198,
|
||||
233,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -140,8 +140,8 @@ description: Artifact commands assembly_non_default_units.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
197,
|
||||
232,
|
||||
198,
|
||||
233,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -157,8 +157,8 @@ description: Artifact commands assembly_non_default_units.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
197,
|
||||
232,
|
||||
198,
|
||||
233,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -168,8 +168,8 @@ description: Artifact commands assembly_non_default_units.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
197,
|
||||
232,
|
||||
198,
|
||||
233,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
@ -197,8 +197,8 @@ description: Artifact commands assembly_non_default_units.kcl
|
||||
{
|
||||
"cmdId": "[uuid]",
|
||||
"range": [
|
||||
197,
|
||||
232,
|
||||
198,
|
||||
233,
|
||||
3
|
||||
],
|
||||
"command": {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph path2 [Path]
|
||||
2["Path<br>[197, 232, 3]"]
|
||||
3["Segment<br>[197, 232, 3]"]
|
||||
2["Path<br>[198, 233, 3]"]
|
||||
3["Segment<br>[198, 233, 3]"]
|
||||
4[Solid2d]
|
||||
end
|
||||
subgraph path6 [Path]
|
||||
@ -10,7 +10,7 @@ flowchart LR
|
||||
7["Segment<br>[114, 149, 4]"]
|
||||
8[Solid2d]
|
||||
end
|
||||
1["Plane<br>[172, 191, 3]"]
|
||||
1["Plane<br>[173, 192, 3]"]
|
||||
5["Plane<br>[89, 108, 4]"]
|
||||
1 --- 2
|
||||
2 --- 3
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
|
||||
export radius = 1
|
||||
|
||||
@ -11,16 +11,16 @@ description: Operations executed assembly_non_default_units.kcl
|
||||
"value": "XZ"
|
||||
},
|
||||
"sourceRange": [
|
||||
186,
|
||||
190,
|
||||
187,
|
||||
191,
|
||||
3
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "startSketchOn",
|
||||
"sourceRange": [
|
||||
172,
|
||||
191,
|
||||
173,
|
||||
192,
|
||||
3
|
||||
],
|
||||
"type": "StdLibCall",
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
@settings(defaultLengthUnit = in)
|
||||
|
||||
|
||||
// This is not used, but it triggers the problem.
|
||||
import radius from "globals.kcl"
|
||||
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
@settings(defaultLengthUnit = mm)
|
||||
import three from "import_cycle3.kcl"
|
||||
|
||||
export fn two = () => { return three() - 1 }
|
||||
export fn two() {
|
||||
return three() - 1
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
@settings(defaultLengthUnit = in)
|
||||
import one from "input.kcl"
|
||||
|
||||
export fn three = () => { return one() + one() + one() }
|
||||
export fn three() {
|
||||
return one() + one() + one()
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
|
||||
export part001 = startSketchOn('XY')
|
||||
|> startProfileAt([4, 12], %)
|
||||
|> line(end = [2, 0])
|
||||
@ -12,4 +13,6 @@ export part001 = startSketchOn('XY')
|
||||
|> close()
|
||||
|> revolve({ axis = 'y' }, %) // default angle is 360
|
||||
|
||||
export fn two = () => { return 5 }
|
||||
export fn two() {
|
||||
return 5
|
||||
}
|
||||
|
||||
@ -11,16 +11,16 @@ description: Operations executed import_function_not_sketch.kcl
|
||||
"value": "XY"
|
||||
},
|
||||
"sourceRange": [
|
||||
66,
|
||||
70,
|
||||
67,
|
||||
71,
|
||||
3
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "startSketchOn",
|
||||
"sourceRange": [
|
||||
52,
|
||||
71,
|
||||
53,
|
||||
72,
|
||||
3
|
||||
],
|
||||
"type": "StdLibCall",
|
||||
@ -39,8 +39,8 @@ description: Operations executed import_function_not_sketch.kcl
|
||||
}
|
||||
},
|
||||
"sourceRange": [
|
||||
312,
|
||||
326,
|
||||
313,
|
||||
327,
|
||||
3
|
||||
]
|
||||
},
|
||||
@ -52,16 +52,16 @@ description: Operations executed import_function_not_sketch.kcl
|
||||
}
|
||||
},
|
||||
"sourceRange": [
|
||||
328,
|
||||
329,
|
||||
330,
|
||||
3
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "revolve",
|
||||
"sourceRange": [
|
||||
304,
|
||||
330,
|
||||
305,
|
||||
331,
|
||||
3
|
||||
],
|
||||
"type": "StdLibCall",
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export fn foo = () => { return 0 }
|
||||
export fn foo() {
|
||||
return 0
|
||||
}
|
||||
|
||||
// This interacts with the engine.
|
||||
part001 = startSketchOn('XY')
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Operations executed import_side_effect.kcl
|
||||
---
|
||||
[
|
||||
@ -11,16 +11,16 @@ description: Operations executed import_side_effect.kcl
|
||||
"value": "XY"
|
||||
},
|
||||
"sourceRange": [
|
||||
91,
|
||||
95,
|
||||
99,
|
||||
3
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "startSketchOn",
|
||||
"sourceRange": [
|
||||
81,
|
||||
100,
|
||||
77,
|
||||
96,
|
||||
3
|
||||
],
|
||||
"type": "StdLibCall",
|
||||
|
||||
@ -8,9 +8,9 @@ mod TEST_NAME_HERE {
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[test]
|
||||
fn unparse() {
|
||||
super::unparse(TEST_NAME)
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
|
||||
@ -490,7 +490,7 @@ async fn execute_code_and_export(code: String, export_format: FileExportFormat)
|
||||
.map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?
|
||||
}
|
||||
|
||||
/// Format the kcl code.
|
||||
/// Format the kcl code. This will return the formatted code.
|
||||
#[pyfunction]
|
||||
fn format(code: String) -> PyResult<String> {
|
||||
let program = kcl_lib::Program::parse_no_errs(&code).map_err(|err| into_miette_for_parse("", &code, err))?;
|
||||
@ -499,6 +499,19 @@ fn format(code: String) -> PyResult<String> {
|
||||
Ok(recasted)
|
||||
}
|
||||
|
||||
/// Format a whole directory of kcl code.
|
||||
#[pyfunction]
|
||||
async fn format_dir(dir: String) -> PyResult<()> {
|
||||
tokio()
|
||||
.spawn(async move {
|
||||
kcl_lib::recast_dir(std::path::Path::new(&dir), &Default::default())
|
||||
.await
|
||||
.map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))
|
||||
})
|
||||
.await
|
||||
.map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?
|
||||
}
|
||||
|
||||
/// Lint the kcl code.
|
||||
#[pyfunction]
|
||||
fn lint(code: String) -> PyResult<Vec<Discovered>> {
|
||||
@ -528,6 +541,7 @@ fn kcl(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(execute_and_export, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(execute_code_and_export, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(format, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(format_dir, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(lint, m)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -129,6 +129,9 @@ def test_kcl_format():
|
||||
assert formatted_code is not None
|
||||
assert len(formatted_code) > 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_kcl_format_dir():
|
||||
await kcl.format_dir(walkie_talkie_dir)
|
||||
|
||||
def test_kcl_lint():
|
||||
# Read from a file.
|
||||
|
||||
@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
use kcl_lib::{
|
||||
exec::{ArtifactCommand, DefaultPlanes, IdGenerator},
|
||||
ExecutionKind, KclError,
|
||||
EngineStats, ExecutionKind, KclError,
|
||||
};
|
||||
use kittycad_modeling_cmds::{
|
||||
self as kcmc,
|
||||
@ -26,6 +26,7 @@ pub struct EngineConnection {
|
||||
execution_kind: Arc<RwLock<ExecutionKind>>,
|
||||
/// The default planes for the scene.
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
stats: EngineStats,
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
@ -38,6 +39,7 @@ impl EngineConnection {
|
||||
core_test: result,
|
||||
execution_kind: Default::default(),
|
||||
default_planes: Default::default(),
|
||||
stats: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -369,6 +371,10 @@ impl kcl_lib::EngineManager for EngineConnection {
|
||||
Arc::new(RwLock::new(IndexMap::new()))
|
||||
}
|
||||
|
||||
fn stats(&self) -> &EngineStats {
|
||||
&self.stats
|
||||
}
|
||||
|
||||
fn artifact_commands(&self) -> Arc<RwLock<Vec<ArtifactCommand>>> {
|
||||
Arc::new(RwLock::new(Vec::new()))
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ use wasm_bindgen::prelude::*;
|
||||
pub struct Context {
|
||||
engine: Arc<Box<dyn EngineManager>>,
|
||||
fs: Arc<FileManager>,
|
||||
mock_engine: Arc<Box<dyn EngineManager>>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -28,16 +29,34 @@ impl Context {
|
||||
.map_err(|e| format!("{:?}", e))?,
|
||||
)),
|
||||
fs: Arc::new(FileManager::new(fs_manager)),
|
||||
mock_engine: Arc::new(Box::new(
|
||||
kcl_lib::mock_engine::EngineConnection::new()
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))?,
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
fn create_executor_ctx(&self, settings: &str, path: Option<String>) -> Result<kcl_lib::ExecutorContext, String> {
|
||||
fn create_executor_ctx(
|
||||
&self,
|
||||
settings: &str,
|
||||
path: Option<String>,
|
||||
is_mock: bool,
|
||||
) -> Result<kcl_lib::ExecutorContext, String> {
|
||||
let config: kcl_lib::Configuration = serde_json::from_str(settings).map_err(|e| e.to_string())?;
|
||||
let mut settings: kcl_lib::ExecutorSettings = config.into();
|
||||
if let Some(path) = path {
|
||||
settings.with_current_file(std::path::PathBuf::from(path));
|
||||
}
|
||||
|
||||
if is_mock {
|
||||
return Ok(kcl_lib::ExecutorContext::new_mock(
|
||||
self.mock_engine.clone(),
|
||||
self.fs.clone(),
|
||||
settings.into(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(kcl_lib::ExecutorContext::new(
|
||||
self.engine.clone(),
|
||||
self.fs.clone(),
|
||||
@ -57,7 +76,7 @@ impl Context {
|
||||
|
||||
let program: Program = serde_json::from_str(program_ast_json).map_err(|e| e.to_string())?;
|
||||
|
||||
let ctx = self.create_executor_ctx(settings, path)?;
|
||||
let ctx = self.create_executor_ctx(settings, path, false)?;
|
||||
match ctx.run_with_caching(program).await {
|
||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues.
|
||||
// DO NOT USE serde_wasm_bindgen::to_value it will break the frontend.
|
||||
@ -65,4 +84,26 @@ impl Context {
|
||||
Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?),
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a program in mock mode.
|
||||
#[wasm_bindgen(js_name = executeMock)]
|
||||
pub async fn execute_mock(
|
||||
&self,
|
||||
program_ast_json: &str,
|
||||
path: Option<String>,
|
||||
settings: &str,
|
||||
use_prev_memory: bool,
|
||||
) -> Result<JsValue, String> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let program: Program = serde_json::from_str(program_ast_json).map_err(|e| e.to_string())?;
|
||||
|
||||
let ctx = self.create_executor_ctx(settings, path, true)?;
|
||||
match ctx.run_mock(program, use_prev_memory).await {
|
||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues.
|
||||
// DO NOT USE serde_wasm_bindgen::to_value it will break the frontend.
|
||||
Ok(outcome) => JsValue::from_serde(&outcome).map_err(|e| e.to_string()),
|
||||
Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,9 +3,13 @@
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod context;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod lsp;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod wasm;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use context::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use lsp::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use wasm::*;
|
||||
|
||||
147
rust/kcl-wasm-lib/src/lsp.rs
Normal file
@ -0,0 +1,147 @@
|
||||
//! Wasm interface for our LSP servers.
|
||||
|
||||
use futures::stream::TryStreamExt;
|
||||
use tower_lsp::{LspService, Server};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct LspServerConfig {
|
||||
into_server: js_sys::AsyncIterator,
|
||||
from_server: web_sys::WritableStream,
|
||||
fs: kcl_lib::wasm_engine::FileSystemManager,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl LspServerConfig {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
into_server: js_sys::AsyncIterator,
|
||||
from_server: web_sys::WritableStream,
|
||||
fs: kcl_lib::wasm_engine::FileSystemManager,
|
||||
) -> Self {
|
||||
Self {
|
||||
into_server,
|
||||
from_server,
|
||||
fs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the `kcl` lsp server.
|
||||
//
|
||||
// NOTE: we don't use web_sys::ReadableStream for input here because on the
|
||||
// browser side we need to use a ReadableByteStreamController to construct it
|
||||
// and so far only Chromium-based browsers support that functionality.
|
||||
|
||||
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
|
||||
#[wasm_bindgen]
|
||||
pub async fn lsp_run_kcl(config: LspServerConfig, token: String, baseurl: String) -> Result<(), JsValue> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let LspServerConfig {
|
||||
into_server,
|
||||
from_server,
|
||||
fs,
|
||||
} = config;
|
||||
|
||||
let executor_ctx = None;
|
||||
|
||||
let mut zoo_client = kittycad::Client::new(token);
|
||||
zoo_client.set_base_url(baseurl.as_str());
|
||||
|
||||
// Check if we can send telemetry for this user.
|
||||
let can_send_telemetry = match zoo_client.users().get_privacy_settings().await {
|
||||
Ok(privacy_settings) => privacy_settings.can_train_on_data,
|
||||
Err(err) => {
|
||||
// In the case of dev we don't always have a sub set, but prod we should.
|
||||
if err
|
||||
.to_string()
|
||||
.contains("The modeling app subscription type is missing.")
|
||||
{
|
||||
true
|
||||
} else {
|
||||
web_sys::console::warn_1(&format!("Failed to get privacy settings: {err:?}").into());
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (service, socket) = LspService::build(|client| {
|
||||
kcl_lib::KclLspBackend::new_wasm(client, executor_ctx, fs, zoo_client, can_send_telemetry).unwrap()
|
||||
})
|
||||
.custom_method("kcl/updateUnits", kcl_lib::KclLspBackend::update_units)
|
||||
.custom_method("kcl/updateCanExecute", kcl_lib::KclLspBackend::update_can_execute)
|
||||
.finish();
|
||||
|
||||
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
|
||||
let input = input
|
||||
.map_ok(|value| {
|
||||
value
|
||||
.dyn_into::<js_sys::Uint8Array>()
|
||||
.expect("could not cast stream item to Uint8Array")
|
||||
.to_vec()
|
||||
})
|
||||
.map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
|
||||
.into_async_read();
|
||||
|
||||
let output = wasm_bindgen::JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
|
||||
let output = wasm_streams::WritableStream::from_raw(output);
|
||||
let output = output.try_into_async_write().map_err(|err| err.0)?;
|
||||
|
||||
Server::new(input, output, socket).serve(service).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run the `copilot` lsp server.
|
||||
//
|
||||
// NOTE: we don't use web_sys::ReadableStream for input here because on the
|
||||
// browser side we need to use a ReadableByteStreamController to construct it
|
||||
// and so far only Chromium-based browsers support that functionality.
|
||||
|
||||
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
|
||||
#[wasm_bindgen]
|
||||
pub async fn lsp_run_copilot(config: LspServerConfig, token: String, baseurl: String) -> Result<(), JsValue> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let LspServerConfig {
|
||||
into_server,
|
||||
from_server,
|
||||
fs,
|
||||
} = config;
|
||||
|
||||
let mut zoo_client = kittycad::Client::new(token);
|
||||
zoo_client.set_base_url(baseurl.as_str());
|
||||
|
||||
let dev_mode = baseurl == "https://api.dev.zoo.dev";
|
||||
|
||||
let (service, socket) =
|
||||
LspService::build(|client| kcl_lib::CopilotLspBackend::new_wasm(client, fs, zoo_client, dev_mode))
|
||||
.custom_method("copilot/setEditorInfo", kcl_lib::CopilotLspBackend::set_editor_info)
|
||||
.custom_method(
|
||||
"copilot/getCompletions",
|
||||
kcl_lib::CopilotLspBackend::get_completions_cycling,
|
||||
)
|
||||
.custom_method("copilot/notifyAccepted", kcl_lib::CopilotLspBackend::accept_completion)
|
||||
.custom_method("copilot/notifyRejected", kcl_lib::CopilotLspBackend::reject_completions)
|
||||
.finish();
|
||||
|
||||
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
|
||||
let input = input
|
||||
.map_ok(|value| {
|
||||
value
|
||||
.dyn_into::<js_sys::Uint8Array>()
|
||||
.expect("could not cast stream item to Uint8Array")
|
||||
.to_vec()
|
||||
})
|
||||
.map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
|
||||
.into_async_read();
|
||||
|
||||
let output = wasm_bindgen::JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
|
||||
let output = wasm_streams::WritableStream::from_raw(output);
|
||||
let output = output.try_into_async_write().map_err(|err| err.0)?;
|
||||
|
||||
Server::new(input, output, socket).serve(service).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -1,38 +1,9 @@
|
||||
//! Wasm bindings for `kcl`.
|
||||
|
||||
use futures::stream::TryStreamExt;
|
||||
use gloo_utils::format::JsValueSerdeExt;
|
||||
use kcl_lib::{pretty::NumericSuffix, CoreDump, Point2d, Program};
|
||||
use tower_lsp::{LspService, Server};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// wasm_bindgen wrapper for mock execute
|
||||
#[wasm_bindgen]
|
||||
pub async fn execute_mock(
|
||||
program_ast_json: &str,
|
||||
path: Option<String>,
|
||||
settings: &str,
|
||||
use_prev_memory: bool,
|
||||
fs_manager: kcl_lib::wasm_engine::FileSystemManager,
|
||||
) -> Result<JsValue, String> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let program: Program = serde_json::from_str(program_ast_json).map_err(|e| e.to_string())?;
|
||||
let config: kcl_lib::Configuration = serde_json::from_str(settings).map_err(|e| e.to_string())?;
|
||||
let mut settings: kcl_lib::ExecutorSettings = config.into();
|
||||
if let Some(path) = path {
|
||||
settings.with_current_file(std::path::PathBuf::from(path));
|
||||
}
|
||||
|
||||
let ctx = kcl_lib::ExecutorContext::new_mock(fs_manager, settings.into()).await?;
|
||||
match ctx.run_mock(program, use_prev_memory).await {
|
||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues.
|
||||
// DO NOT USE serde_wasm_bindgen::to_value it will break the frontend.
|
||||
Ok(outcome) => JsValue::from_serde(&outcome).map_err(|e| e.to_string()),
|
||||
Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?),
|
||||
}
|
||||
}
|
||||
|
||||
// wasm_bindgen wrapper for execute
|
||||
#[wasm_bindgen]
|
||||
pub async fn kcl_lint(program_ast_json: &str) -> Result<JsValue, JsValue> {
|
||||
@ -94,148 +65,6 @@ pub fn format_number(value: f64, suffix_json: &str) -> Result<String, JsError> {
|
||||
Ok(kcl_lib::pretty::format_number(value, suffix))
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct ServerConfig {
|
||||
into_server: js_sys::AsyncIterator,
|
||||
from_server: web_sys::WritableStream,
|
||||
fs: kcl_lib::wasm_engine::FileSystemManager,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl ServerConfig {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(
|
||||
into_server: js_sys::AsyncIterator,
|
||||
from_server: web_sys::WritableStream,
|
||||
fs: kcl_lib::wasm_engine::FileSystemManager,
|
||||
) -> Self {
|
||||
Self {
|
||||
into_server,
|
||||
from_server,
|
||||
fs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run the `kcl` lsp server.
|
||||
//
|
||||
// NOTE: we don't use web_sys::ReadableStream for input here because on the
|
||||
// browser side we need to use a ReadableByteStreamController to construct it
|
||||
// and so far only Chromium-based browsers support that functionality.
|
||||
|
||||
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
|
||||
#[wasm_bindgen]
|
||||
pub async fn kcl_lsp_run(config: ServerConfig, token: String, baseurl: String) -> Result<(), JsValue> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let ServerConfig {
|
||||
into_server,
|
||||
from_server,
|
||||
fs,
|
||||
} = config;
|
||||
|
||||
let executor_ctx = None;
|
||||
|
||||
let mut zoo_client = kittycad::Client::new(token);
|
||||
zoo_client.set_base_url(baseurl.as_str());
|
||||
|
||||
// Check if we can send telemetry for this user.
|
||||
let can_send_telemetry = match zoo_client.users().get_privacy_settings().await {
|
||||
Ok(privacy_settings) => privacy_settings.can_train_on_data,
|
||||
Err(err) => {
|
||||
// In the case of dev we don't always have a sub set, but prod we should.
|
||||
if err
|
||||
.to_string()
|
||||
.contains("The modeling app subscription type is missing.")
|
||||
{
|
||||
true
|
||||
} else {
|
||||
web_sys::console::warn_1(&format!("Failed to get privacy settings: {err:?}").into());
|
||||
false
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (service, socket) = LspService::build(|client| {
|
||||
kcl_lib::KclLspBackend::new_wasm(client, executor_ctx, fs, zoo_client, can_send_telemetry).unwrap()
|
||||
})
|
||||
.custom_method("kcl/updateUnits", kcl_lib::KclLspBackend::update_units)
|
||||
.custom_method("kcl/updateCanExecute", kcl_lib::KclLspBackend::update_can_execute)
|
||||
.finish();
|
||||
|
||||
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
|
||||
let input = input
|
||||
.map_ok(|value| {
|
||||
value
|
||||
.dyn_into::<js_sys::Uint8Array>()
|
||||
.expect("could not cast stream item to Uint8Array")
|
||||
.to_vec()
|
||||
})
|
||||
.map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
|
||||
.into_async_read();
|
||||
|
||||
let output = wasm_bindgen::JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
|
||||
let output = wasm_streams::WritableStream::from_raw(output);
|
||||
let output = output.try_into_async_write().map_err(|err| err.0)?;
|
||||
|
||||
Server::new(input, output, socket).serve(service).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run the `copilot` lsp server.
|
||||
//
|
||||
// NOTE: we don't use web_sys::ReadableStream for input here because on the
|
||||
// browser side we need to use a ReadableByteStreamController to construct it
|
||||
// and so far only Chromium-based browsers support that functionality.
|
||||
|
||||
// NOTE: input needs to be an AsyncIterator<Uint8Array, never, void> specifically
|
||||
#[wasm_bindgen]
|
||||
pub async fn copilot_lsp_run(config: ServerConfig, token: String, baseurl: String) -> Result<(), JsValue> {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
let ServerConfig {
|
||||
into_server,
|
||||
from_server,
|
||||
fs,
|
||||
} = config;
|
||||
|
||||
let mut zoo_client = kittycad::Client::new(token);
|
||||
zoo_client.set_base_url(baseurl.as_str());
|
||||
|
||||
let dev_mode = baseurl == "https://api.dev.zoo.dev";
|
||||
|
||||
let (service, socket) =
|
||||
LspService::build(|client| kcl_lib::CopilotLspBackend::new_wasm(client, fs, zoo_client, dev_mode))
|
||||
.custom_method("copilot/setEditorInfo", kcl_lib::CopilotLspBackend::set_editor_info)
|
||||
.custom_method(
|
||||
"copilot/getCompletions",
|
||||
kcl_lib::CopilotLspBackend::get_completions_cycling,
|
||||
)
|
||||
.custom_method("copilot/notifyAccepted", kcl_lib::CopilotLspBackend::accept_completion)
|
||||
.custom_method("copilot/notifyRejected", kcl_lib::CopilotLspBackend::reject_completions)
|
||||
.finish();
|
||||
|
||||
let input = wasm_bindgen_futures::stream::JsStream::from(into_server);
|
||||
let input = input
|
||||
.map_ok(|value| {
|
||||
value
|
||||
.dyn_into::<js_sys::Uint8Array>()
|
||||
.expect("could not cast stream item to Uint8Array")
|
||||
.to_vec()
|
||||
})
|
||||
.map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
|
||||
.into_async_read();
|
||||
|
||||
let output = wasm_bindgen::JsCast::unchecked_into::<wasm_streams::writable::sys::WritableStream>(from_server);
|
||||
let output = wasm_streams::WritableStream::from_raw(output);
|
||||
let output = output.try_into_async_write().map_err(|err| err.0)?;
|
||||
|
||||
Server::new(input, output, socket).serve(service).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn is_points_ccw(points: &[f64]) -> i32 {
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||
import { Stream } from './components/Stream'
|
||||
import { AppHeader } from './components/AppHeader'
|
||||
@ -60,7 +60,6 @@ export function App() {
|
||||
const projectPath = project?.path || null
|
||||
|
||||
const [commands] = useEngineCommands()
|
||||
const [capturedCanvas, setCapturedCanvas] = useState(false)
|
||||
const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||
const lastCommandType = commands[commands.length - 1]?.type
|
||||
|
||||
@ -109,11 +108,7 @@ export function App() {
|
||||
|
||||
// Generate thumbnail.png when loading the app
|
||||
useEffect(() => {
|
||||
if (
|
||||
isDesktop() &&
|
||||
!capturedCanvas &&
|
||||
lastCommandType === 'execution-done'
|
||||
) {
|
||||
if (isDesktop() && lastCommandType === 'execution-done') {
|
||||
setTimeout(() => {
|
||||
const projectDirectoryWithoutEndingSlash = loaderData?.project?.path
|
||||
if (!projectDirectoryWithoutEndingSlash) {
|
||||
|
||||
@ -168,7 +168,7 @@ export function Toolbar({
|
||||
|
||||
return (
|
||||
<menu
|
||||
data-currentMode={currentMode}
|
||||
data-current-mode={currentMode}
|
||||
className="max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-30 dark:border-chalkboard-80 border-t-0 shadow-sm"
|
||||
>
|
||||
<ul
|
||||
|
||||
@ -39,7 +39,7 @@ import { getConstraintInfo, getConstraintInfoKw } from 'lang/std/sketch'
|
||||
import { Dialog, Popover, Transition } from '@headlessui/react'
|
||||
import toast from 'react-hot-toast'
|
||||
import { InstanceProps, create } from 'react-modal-promise'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import { executeAstMock } from 'lang/langHelpers'
|
||||
import {
|
||||
deleteSegmentFromPipeExpression,
|
||||
removeSingleConstraintInfo,
|
||||
@ -147,7 +147,8 @@ export const ClientSideScene = ({
|
||||
state.matches({ Sketch: 'Tangential arc to' }) ||
|
||||
state.matches({ Sketch: 'Rectangle tool' }) ||
|
||||
state.matches({ Sketch: 'Circle tool' }) ||
|
||||
state.matches({ Sketch: 'Circle three point tool' })
|
||||
state.matches({ Sketch: 'Circle three point tool' }) ||
|
||||
state.matches({ Sketch: 'Arc three point tool' })
|
||||
) {
|
||||
cursor = 'crosshair'
|
||||
} else {
|
||||
@ -436,12 +437,10 @@ export async function deleteSegment({
|
||||
if (err(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
modifiedAst = pResult.program
|
||||
|
||||
const testExecute = await executeAst({
|
||||
const testExecute = await executeAstMock({
|
||||
ast: modifiedAst,
|
||||
engineCommandManager: engineCommandManager,
|
||||
isMock: true,
|
||||
rustContext,
|
||||
usePrevMemory: false,
|
||||
rustContext: rustContext,
|
||||
})
|
||||
if (testExecute.errors.length) {
|
||||
toast.error('Segment tag used outside of current Sketch. Could not delete.')
|
||||
@ -490,14 +489,19 @@ const SegmentMenu = ({
|
||||
verticalPosition === 'top' ? 'bottom-full' : 'top-full'
|
||||
} z-10 w-36 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-100 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50`}
|
||||
>
|
||||
<button
|
||||
className="!border-transparent rounded-sm text-left p-1 text-nowrap"
|
||||
onClick={() => {
|
||||
send({ type: 'Constrain remove constraints', data: pathToNode })
|
||||
}}
|
||||
>
|
||||
Remove constraints
|
||||
</button>
|
||||
{stdLibFnName !== 'arcTo' && (
|
||||
<button
|
||||
className="!border-transparent rounded-sm text-left p-1 text-nowrap"
|
||||
onClick={() => {
|
||||
send({
|
||||
type: 'Constrain remove constraints',
|
||||
data: pathToNode,
|
||||
})
|
||||
}}
|
||||
>
|
||||
Remove constraints
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
className="!border-transparent rounded-sm text-left p-1 text-nowrap"
|
||||
title={
|
||||
|
||||
@ -49,7 +49,19 @@ import {
|
||||
TANGENTIAL_ARC_TO_SEGMENT,
|
||||
TANGENTIAL_ARC_TO_SEGMENT_BODY,
|
||||
TANGENTIAL_ARC_TO__SEGMENT_DASH,
|
||||
ARC_SEGMENT,
|
||||
ARC_SEGMENT_BODY,
|
||||
ARC_SEGMENT_DASH,
|
||||
ARC_ANGLE_END,
|
||||
getParentGroup,
|
||||
ARC_CENTER_TO_FROM,
|
||||
ARC_CENTER_TO_TO,
|
||||
ARC_ANGLE_REFERENCE_LINE,
|
||||
THREE_POINT_ARC_SEGMENT,
|
||||
THREE_POINT_ARC_SEGMENT_BODY,
|
||||
THREE_POINT_ARC_SEGMENT_DASH,
|
||||
THREE_POINT_ARC_HANDLE2,
|
||||
THREE_POINT_ARC_HANDLE3,
|
||||
} from './sceneEntities'
|
||||
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
|
||||
import {
|
||||
@ -61,7 +73,7 @@ import {
|
||||
SEGMENT_LENGTH_LABEL_TEXT,
|
||||
} from './sceneInfra'
|
||||
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
|
||||
import { normaliseAngle, roundOff } from 'lib/utils'
|
||||
import { isClockwise, normaliseAngle, roundOff } from 'lib/utils'
|
||||
import {
|
||||
SegmentOverlay,
|
||||
SegmentOverlayPayload,
|
||||
@ -74,6 +86,7 @@ import { Selections } from 'lib/selections'
|
||||
import { calculate_circle_from_3_points } from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
|
||||
const ANGLE_INDICATOR_RADIUS = 30 // in px
|
||||
interface CreateSegmentArgs {
|
||||
input: SegmentInputs
|
||||
prevSegment: Sketch['paths'][number]
|
||||
@ -412,14 +425,28 @@ class TangentialArcToSegment implements SegmentUtils {
|
||||
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
|
||||
const extraSegmentGroup = group.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||
|
||||
const previousPoint =
|
||||
prevSegment?.type === 'TangentialArcTo'
|
||||
? getTangentPointFromPreviousArc(
|
||||
prevSegment.center,
|
||||
prevSegment.ccw,
|
||||
prevSegment.to
|
||||
)
|
||||
: prevSegment.from
|
||||
let previousPoint = prevSegment.from
|
||||
if (prevSegment?.type === 'TangentialArcTo') {
|
||||
previousPoint = getTangentPointFromPreviousArc(
|
||||
prevSegment.center,
|
||||
prevSegment.ccw,
|
||||
prevSegment.to
|
||||
)
|
||||
} else if (prevSegment?.type === 'ArcThreePoint') {
|
||||
const arcDetails = calculate_circle_from_3_points(
|
||||
prevSegment.p1[0],
|
||||
prevSegment.p1[1],
|
||||
prevSegment.p2[0],
|
||||
prevSegment.p2[1],
|
||||
prevSegment.p3[0],
|
||||
prevSegment.p3[1]
|
||||
)
|
||||
previousPoint = getTangentPointFromPreviousArc(
|
||||
[arcDetails.center_x, arcDetails.center_y],
|
||||
!isClockwise([prevSegment.p1, prevSegment.p2, prevSegment.p3]),
|
||||
prevSegment.p3
|
||||
)
|
||||
}
|
||||
|
||||
const arcInfo = getTangentialArcToInfo({
|
||||
arcStartPoint: from,
|
||||
@ -591,7 +618,6 @@ class CircleSegment implements SegmentUtils {
|
||||
}
|
||||
const { from, center, radius } = input
|
||||
group.userData.from = from
|
||||
// group.userData.to = to
|
||||
group.userData.center = center
|
||||
group.userData.radius = radius
|
||||
group.userData.prevSegment = prevSegment
|
||||
@ -635,8 +661,7 @@ class CircleSegment implements SegmentUtils {
|
||||
}
|
||||
|
||||
if (radiusLengthIndicator) {
|
||||
// The radius indicator is placed at the midpoint of the radius,
|
||||
// at a 45 degree CCW angle from the positive X-axis
|
||||
// The radius indicator is placed halfway between the center and the start angle point
|
||||
const indicatorPoint = {
|
||||
x: center[0] + (Math.cos(Math.PI / 4) * radius) / 2,
|
||||
y: center[1] + (Math.sin(Math.PI / 4) * radius) / 2,
|
||||
@ -648,6 +673,8 @@ class CircleSegment implements SegmentUtils {
|
||||
const label = labelWrapperElem.children[0] as HTMLParagraphElement
|
||||
label.innerText = `${roundOff(radius)}`
|
||||
label.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
|
||||
|
||||
// Calculate the angle for the label
|
||||
const isPlaneBackFace = center[0] > indicatorPoint.x
|
||||
label.style.setProperty(
|
||||
'--degree',
|
||||
@ -925,6 +952,585 @@ class CircleThreePointSegment implements SegmentUtils {
|
||||
}
|
||||
}
|
||||
|
||||
class ArcSegment implements SegmentUtils {
|
||||
init: SegmentUtils['init'] = ({
|
||||
prevSegment,
|
||||
input,
|
||||
id,
|
||||
pathToNode,
|
||||
isDraftSegment,
|
||||
scale = 1,
|
||||
theme,
|
||||
isSelected,
|
||||
sceneInfra,
|
||||
}) => {
|
||||
if (input.type !== 'arc-segment') {
|
||||
return new Error('Invalid segment type')
|
||||
}
|
||||
const { from, to, center, radius, ccw } = input
|
||||
const baseColor = getThemeColorForThreeJs(theme)
|
||||
const color = isSelected ? 0x0000ff : baseColor
|
||||
|
||||
// Calculate start and end angles
|
||||
const startAngle = Math.atan2(from[1] - center[1], from[0] - center[0])
|
||||
const endAngle = Math.atan2(to[1] - center[1], to[0] - center[0])
|
||||
|
||||
const group = new Group()
|
||||
const geometry = createArcGeometry({
|
||||
center,
|
||||
radius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
ccw,
|
||||
isDashed: isDraftSegment,
|
||||
scale,
|
||||
})
|
||||
const mat = new MeshBasicMaterial({ color })
|
||||
const arcMesh = new Mesh(geometry, mat)
|
||||
const meshType = isDraftSegment ? ARC_SEGMENT_DASH : ARC_SEGMENT_BODY
|
||||
|
||||
// Create handles for the arc
|
||||
|
||||
const endAngleHandle = createArrowhead(scale, theme, color)
|
||||
endAngleHandle.name = ARC_ANGLE_END
|
||||
endAngleHandle.userData.type = ARC_ANGLE_END
|
||||
|
||||
const circleCenterGroup = createCircleCenterHandle(scale, theme, color)
|
||||
|
||||
// A radius indicator that appears from the center to the perimeter
|
||||
const radiusIndicatorGroup = createLengthIndicator({
|
||||
from: center,
|
||||
to: from,
|
||||
scale,
|
||||
})
|
||||
|
||||
const grey = 0xaaaaaa
|
||||
|
||||
// Create a line from the center to the 'to' point
|
||||
const centerToFromLine = createLine({
|
||||
from: center,
|
||||
to: from,
|
||||
scale,
|
||||
color: grey, // Light gray color for the line
|
||||
})
|
||||
centerToFromLine.name = ARC_CENTER_TO_FROM
|
||||
const centerToToLine = createLine({
|
||||
from: center,
|
||||
to,
|
||||
scale,
|
||||
color: grey, // Light gray color for the line
|
||||
})
|
||||
centerToToLine.name = ARC_CENTER_TO_TO
|
||||
const angleReferenceLine = createLine({
|
||||
from: [center[0] + (ANGLE_INDICATOR_RADIUS - 2) * scale, center[1]],
|
||||
to: [center[0] + (ANGLE_INDICATOR_RADIUS + 2) * scale, center[1]],
|
||||
scale,
|
||||
color: grey, // Light gray color for the line
|
||||
})
|
||||
angleReferenceLine.name = ARC_ANGLE_REFERENCE_LINE
|
||||
|
||||
// Create a curved line with an arrow to indicate the angle
|
||||
const angleIndicator = createAngleIndicator({
|
||||
center,
|
||||
radius: ANGLE_INDICATOR_RADIUS, // Half the radius for the indicator
|
||||
startAngle: 0,
|
||||
endAngle,
|
||||
scale,
|
||||
color: grey, // Red color for the angle indicator
|
||||
}) as Line
|
||||
angleIndicator.name = 'angleIndicator'
|
||||
|
||||
// Create a new angle indicator for the end angle
|
||||
const endAngleIndicator = createAngleIndicator({
|
||||
center,
|
||||
radius: ANGLE_INDICATOR_RADIUS, // Half the radius for the indicator
|
||||
startAngle: 0,
|
||||
endAngle: (endAngle * Math.PI) / 180,
|
||||
scale,
|
||||
color: grey, // Green color for the end angle indicator
|
||||
}) as Line
|
||||
endAngleIndicator.name = 'endAngleIndicator'
|
||||
|
||||
// Create a length indicator for the end angle
|
||||
const endAngleLengthIndicator = createLengthIndicator({
|
||||
from: center,
|
||||
to: [
|
||||
center[0] + Math.cos(endAngle) * radius,
|
||||
center[1] + Math.sin(endAngle) * radius,
|
||||
],
|
||||
scale,
|
||||
})
|
||||
endAngleLengthIndicator.name = 'endAngleLengthIndicator'
|
||||
|
||||
arcMesh.userData.type = meshType
|
||||
arcMesh.name = meshType
|
||||
group.userData = {
|
||||
type: ARC_SEGMENT,
|
||||
draft: isDraftSegment,
|
||||
id,
|
||||
from,
|
||||
to,
|
||||
radius,
|
||||
center,
|
||||
ccw,
|
||||
prevSegment,
|
||||
pathToNode,
|
||||
isSelected,
|
||||
baseColor,
|
||||
}
|
||||
group.name = ARC_SEGMENT
|
||||
|
||||
group.add(
|
||||
arcMesh,
|
||||
endAngleHandle,
|
||||
circleCenterGroup,
|
||||
radiusIndicatorGroup,
|
||||
centerToFromLine,
|
||||
centerToToLine,
|
||||
angleReferenceLine,
|
||||
angleIndicator,
|
||||
endAngleIndicator,
|
||||
endAngleLengthIndicator
|
||||
)
|
||||
const updateOverlaysCallback = this.update({
|
||||
prevSegment,
|
||||
input,
|
||||
group,
|
||||
scale,
|
||||
sceneInfra,
|
||||
})
|
||||
if (err(updateOverlaysCallback)) return updateOverlaysCallback
|
||||
|
||||
return {
|
||||
group,
|
||||
updateOverlaysCallback,
|
||||
}
|
||||
}
|
||||
|
||||
update: SegmentUtils['update'] = ({
|
||||
prevSegment,
|
||||
input,
|
||||
group,
|
||||
scale = 1,
|
||||
sceneInfra,
|
||||
}) => {
|
||||
if (input.type !== 'arc-segment') {
|
||||
return new Error('Invalid segment type')
|
||||
}
|
||||
const { from, to, center, radius, ccw } = input
|
||||
group.userData.from = from
|
||||
group.userData.to = to
|
||||
group.userData.center = center
|
||||
group.userData.radius = radius
|
||||
group.userData.ccw = ccw
|
||||
group.userData.prevSegment = prevSegment
|
||||
|
||||
// Calculate start and end angles
|
||||
const startAngle = Math.atan2(from[1] - center[1], from[0] - center[0])
|
||||
const endAngle = Math.atan2(to[1] - center[1], to[0] - center[0])
|
||||
|
||||
// Normalize the angle to -180 to 180 degrees
|
||||
// const normalizedStartAngle = ((startAngle * 180 / Math.PI) + 180) % 360 - 180
|
||||
const normalizedStartAngle = normaliseAngle((startAngle * 180) / Math.PI)
|
||||
const normalizedEndAngle = (((endAngle * 180) / Math.PI + 180) % 360) - 180
|
||||
|
||||
const endAngleHandle = group.getObjectByName(ARC_ANGLE_END) as Group
|
||||
const radiusLengthIndicator = group.getObjectByName(
|
||||
SEGMENT_LENGTH_LABEL
|
||||
) as Group
|
||||
const circleCenterHandle = group.getObjectByName(
|
||||
CIRCLE_CENTER_HANDLE
|
||||
) as Group
|
||||
|
||||
// Calculate arc length
|
||||
let arcAngle = endAngle - startAngle
|
||||
if (ccw && arcAngle > 0) arcAngle = arcAngle - 2 * Math.PI
|
||||
if (!ccw && arcAngle < 0) arcAngle = arcAngle + 2 * Math.PI
|
||||
|
||||
const arcLength = Math.abs(arcAngle) * radius
|
||||
const pxLength = arcLength / scale
|
||||
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
|
||||
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
|
||||
|
||||
const hoveredParent =
|
||||
sceneInfra.hoveredObject &&
|
||||
getParentGroup(sceneInfra.hoveredObject, [ARC_SEGMENT])
|
||||
let isHandlesVisible = !shouldHideIdle
|
||||
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
|
||||
isHandlesVisible = !shouldHideHover
|
||||
}
|
||||
|
||||
if (endAngleHandle) {
|
||||
endAngleHandle.position.set(to[0], to[1], 0)
|
||||
|
||||
const tangentAngle = endAngle + (Math.PI / 2) * (ccw ? 1 : -1)
|
||||
endAngleHandle.quaternion.setFromUnitVectors(
|
||||
new Vector3(0, 1, 0),
|
||||
new Vector3(Math.cos(tangentAngle), Math.sin(tangentAngle), 0)
|
||||
)
|
||||
endAngleHandle.scale.set(scale, scale, scale)
|
||||
endAngleHandle.visible = isHandlesVisible
|
||||
}
|
||||
|
||||
if (radiusLengthIndicator) {
|
||||
// The radius indicator is placed halfway between the center and the start angle point
|
||||
const indicatorPoint = {
|
||||
x: center[0] + (Math.cos(startAngle) * radius) / 2,
|
||||
y: center[1] + (Math.sin(startAngle) * radius) / 2,
|
||||
}
|
||||
const labelWrapper = radiusLengthIndicator.getObjectByName(
|
||||
SEGMENT_LENGTH_LABEL_TEXT
|
||||
) as CSS2DObject
|
||||
const labelWrapperElem = labelWrapper.element as HTMLDivElement
|
||||
const label = labelWrapperElem.children[0] as HTMLParagraphElement
|
||||
label.innerText = `R:${roundOff(radius)}${'\n'}A:${roundOff(
|
||||
roundOff((startAngle * 180) / Math.PI)
|
||||
)}`
|
||||
label.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
|
||||
|
||||
// Calculate the angle for the label
|
||||
label.style.setProperty('--degree', `-${startAngle}rad`)
|
||||
label.style.setProperty('--x', `0px`)
|
||||
label.style.setProperty('--y', `0px`)
|
||||
labelWrapper.position.set(indicatorPoint.x, indicatorPoint.y, 0)
|
||||
radiusLengthIndicator.visible = isHandlesVisible
|
||||
}
|
||||
|
||||
if (circleCenterHandle) {
|
||||
circleCenterHandle.position.set(center[0], center[1], 0)
|
||||
circleCenterHandle.scale.set(scale, scale, scale)
|
||||
circleCenterHandle.visible = isHandlesVisible
|
||||
}
|
||||
|
||||
const arcSegmentBody = group.children.find(
|
||||
(child) => child.userData.type === ARC_SEGMENT_BODY
|
||||
) as Mesh
|
||||
|
||||
if (arcSegmentBody) {
|
||||
const newGeo = createArcGeometry({
|
||||
radius,
|
||||
center,
|
||||
startAngle,
|
||||
endAngle,
|
||||
ccw,
|
||||
scale,
|
||||
})
|
||||
arcSegmentBody.geometry = newGeo
|
||||
}
|
||||
|
||||
const arcSegmentBodyDashed = group.getObjectByName(ARC_SEGMENT_DASH)
|
||||
if (arcSegmentBodyDashed instanceof Mesh) {
|
||||
arcSegmentBodyDashed.geometry = createArcGeometry({
|
||||
center,
|
||||
radius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
ccw,
|
||||
isDashed: true,
|
||||
scale,
|
||||
})
|
||||
}
|
||||
|
||||
const centerToFromLine = group.getObjectByName(ARC_CENTER_TO_FROM) as Line
|
||||
if (centerToFromLine) {
|
||||
updateLine(centerToFromLine, { from: center, to: from, scale })
|
||||
centerToFromLine.visible = isHandlesVisible
|
||||
}
|
||||
const centerToToLine = group.getObjectByName(ARC_CENTER_TO_TO) as Line
|
||||
if (centerToToLine) {
|
||||
updateLine(centerToToLine, { from: center, to, scale })
|
||||
centerToToLine.visible = isHandlesVisible
|
||||
}
|
||||
const angleReferenceLine = group.getObjectByName(
|
||||
ARC_ANGLE_REFERENCE_LINE
|
||||
) as Line
|
||||
if (angleReferenceLine) {
|
||||
updateLine(angleReferenceLine, {
|
||||
from: center,
|
||||
to: [center[0] + 34 * scale, center[1]],
|
||||
scale,
|
||||
})
|
||||
angleReferenceLine.visible = isHandlesVisible
|
||||
}
|
||||
|
||||
const angleIndicator = group.getObjectByName('angleIndicator') as Line
|
||||
if (angleIndicator) {
|
||||
updateAngleIndicator(angleIndicator, {
|
||||
center,
|
||||
radiusPx: ANGLE_INDICATOR_RADIUS - 10,
|
||||
startAngle: 0,
|
||||
endAngle: (normalizedStartAngle * Math.PI) / 180,
|
||||
scale,
|
||||
})
|
||||
angleIndicator.visible = isHandlesVisible
|
||||
}
|
||||
|
||||
const endAngleIndicator = group.getObjectByName('endAngleIndicator') as Line
|
||||
if (endAngleIndicator) {
|
||||
updateAngleIndicator(endAngleIndicator, {
|
||||
center,
|
||||
radiusPx: ANGLE_INDICATOR_RADIUS,
|
||||
startAngle: 0,
|
||||
endAngle: (normalizedEndAngle * Math.PI) / 180,
|
||||
scale,
|
||||
})
|
||||
endAngleIndicator.visible = isHandlesVisible
|
||||
}
|
||||
|
||||
const endAngleLengthIndicator = group.getObjectByName(
|
||||
'endAngleLengthIndicator'
|
||||
) as Group
|
||||
if (endAngleLengthIndicator) {
|
||||
const labelWrapper = endAngleLengthIndicator.getObjectByName(
|
||||
SEGMENT_LENGTH_LABEL_TEXT
|
||||
) as CSS2DObject
|
||||
const labelWrapperElem = labelWrapper.element as HTMLDivElement
|
||||
const label = labelWrapperElem.children[0] as HTMLParagraphElement
|
||||
label.innerText = `A:${roundOff(normalizedEndAngle)}`
|
||||
label.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
|
||||
|
||||
// Position the label
|
||||
const indicatorPoint = {
|
||||
x: center[0] + (Math.cos(endAngle) * radius) / 2,
|
||||
y: center[1] + (Math.sin(endAngle) * radius) / 2,
|
||||
}
|
||||
labelWrapper.position.set(indicatorPoint.x, indicatorPoint.y, 0)
|
||||
endAngleLengthIndicator.visible = isHandlesVisible
|
||||
}
|
||||
|
||||
return () =>
|
||||
sceneInfra.updateOverlayDetails({
|
||||
handle: endAngleHandle,
|
||||
group,
|
||||
isHandlesVisible,
|
||||
from,
|
||||
to,
|
||||
angle: endAngle + (Math.PI / 2) * (ccw ? 1 : -1),
|
||||
hasThreeDotMenu: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
class ThreePointArcSegment implements SegmentUtils {
|
||||
init: SegmentUtils['init'] = ({
|
||||
input,
|
||||
id,
|
||||
pathToNode,
|
||||
isDraftSegment,
|
||||
scale = 1,
|
||||
theme,
|
||||
isSelected = false,
|
||||
sceneInfra,
|
||||
prevSegment,
|
||||
}) => {
|
||||
if (input.type !== 'circle-three-point-segment') {
|
||||
return new Error('Invalid segment type')
|
||||
}
|
||||
const { p1, p2, p3 } = input
|
||||
const { center_x, center_y, radius } = calculate_circle_from_3_points(
|
||||
p1[0],
|
||||
p1[1],
|
||||
p2[0],
|
||||
p2[1],
|
||||
p3[0],
|
||||
p3[1]
|
||||
)
|
||||
const center: [number, number] = [center_x, center_y]
|
||||
const baseColor = getThemeColorForThreeJs(theme)
|
||||
const color = isSelected ? 0x0000ff : baseColor
|
||||
|
||||
// Calculate start and end angles
|
||||
const startAngle = Math.atan2(p1[1] - center[1], p1[0] - center[0])
|
||||
const endAngle = Math.atan2(p3[1] - center[1], p3[0] - center[0])
|
||||
|
||||
const group = new Group()
|
||||
const geometry = createArcGeometry({
|
||||
center,
|
||||
radius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
ccw: !isClockwise([p1, p2, p3]),
|
||||
isDashed: isDraftSegment,
|
||||
scale,
|
||||
})
|
||||
const mat = new MeshBasicMaterial({ color })
|
||||
const arcMesh = new Mesh(geometry, mat)
|
||||
const meshType = isDraftSegment
|
||||
? THREE_POINT_ARC_SEGMENT_DASH
|
||||
: THREE_POINT_ARC_SEGMENT_BODY
|
||||
|
||||
// Create handles for p2 and p3 using createCircleThreePointHandle
|
||||
const p2Handle = createCircleThreePointHandle(
|
||||
scale,
|
||||
theme,
|
||||
THREE_POINT_ARC_HANDLE2,
|
||||
color
|
||||
)
|
||||
p2Handle.position.set(p2[0], p2[1], 0)
|
||||
|
||||
const p3Handle = createCircleThreePointHandle(
|
||||
scale,
|
||||
theme,
|
||||
THREE_POINT_ARC_HANDLE3,
|
||||
color
|
||||
)
|
||||
p3Handle.position.set(p3[0], p3[1], 0)
|
||||
|
||||
arcMesh.userData.type = meshType
|
||||
arcMesh.name = meshType
|
||||
group.userData = {
|
||||
type: THREE_POINT_ARC_SEGMENT,
|
||||
draft: isDraftSegment,
|
||||
id,
|
||||
from: p1,
|
||||
to: p3,
|
||||
p1,
|
||||
p2,
|
||||
p3,
|
||||
radius,
|
||||
center,
|
||||
ccw: false,
|
||||
prevSegment,
|
||||
pathToNode,
|
||||
isSelected,
|
||||
baseColor,
|
||||
}
|
||||
group.name = THREE_POINT_ARC_SEGMENT
|
||||
|
||||
group.add(arcMesh, p2Handle, p3Handle)
|
||||
const updateOverlaysCallback = this.update({
|
||||
prevSegment,
|
||||
input,
|
||||
group,
|
||||
scale,
|
||||
sceneInfra,
|
||||
})
|
||||
if (err(updateOverlaysCallback)) return updateOverlaysCallback
|
||||
|
||||
return {
|
||||
group,
|
||||
updateOverlaysCallback,
|
||||
}
|
||||
}
|
||||
|
||||
update: SegmentUtils['update'] = ({
|
||||
prevSegment,
|
||||
input,
|
||||
group,
|
||||
scale = 1,
|
||||
sceneInfra,
|
||||
}) => {
|
||||
if (input.type !== 'circle-three-point-segment') {
|
||||
return new Error('Invalid segment type')
|
||||
}
|
||||
const { p1, p2, p3 } = input
|
||||
const { center_x, center_y, radius } = calculate_circle_from_3_points(
|
||||
p1[0],
|
||||
p1[1],
|
||||
p2[0],
|
||||
p2[1],
|
||||
p3[0],
|
||||
p3[1]
|
||||
)
|
||||
const center: [number, number] = [center_x, center_y]
|
||||
group.userData.from = p1
|
||||
group.userData.to = p3
|
||||
group.userData.p1 = p1
|
||||
group.userData.p2 = p2
|
||||
group.userData.p3 = p3
|
||||
group.userData.center = center
|
||||
group.userData.radius = radius
|
||||
group.userData.prevSegment = prevSegment
|
||||
|
||||
// Calculate start and end angles
|
||||
const startAngle = Math.atan2(p1[1] - center[1], p1[0] - center[0])
|
||||
const endAngle = Math.atan2(p3[1] - center[1], p3[0] - center[0])
|
||||
|
||||
const p2Handle = group.getObjectByName(THREE_POINT_ARC_HANDLE2) as Group
|
||||
const p3Handle = group.getObjectByName(THREE_POINT_ARC_HANDLE3) as Group
|
||||
|
||||
const arcSegmentBody = group.children.find(
|
||||
(child) => child.userData.type === THREE_POINT_ARC_SEGMENT_BODY
|
||||
) as Mesh
|
||||
|
||||
if (arcSegmentBody) {
|
||||
const newGeo = createArcGeometry({
|
||||
radius,
|
||||
center,
|
||||
startAngle,
|
||||
endAngle,
|
||||
ccw: !isClockwise([p1, p2, p3]),
|
||||
scale,
|
||||
})
|
||||
arcSegmentBody.geometry = newGeo
|
||||
}
|
||||
|
||||
const arcSegmentBodyDashed = group.getObjectByName(
|
||||
THREE_POINT_ARC_SEGMENT_DASH
|
||||
)
|
||||
if (arcSegmentBodyDashed instanceof Mesh) {
|
||||
arcSegmentBodyDashed.geometry = createArcGeometry({
|
||||
center,
|
||||
radius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
ccw: !isClockwise([p1, p2, p3]),
|
||||
isDashed: true,
|
||||
scale,
|
||||
})
|
||||
}
|
||||
|
||||
if (p2Handle) {
|
||||
p2Handle.position.set(p2[0], p2[1], 0)
|
||||
p2Handle.scale.set(scale, scale, scale)
|
||||
p2Handle.visible = true
|
||||
}
|
||||
|
||||
if (p3Handle) {
|
||||
p3Handle.position.set(p3[0], p3[1], 0)
|
||||
p3Handle.scale.set(scale, scale, scale)
|
||||
p3Handle.visible = true
|
||||
}
|
||||
|
||||
return () => {
|
||||
const overlays: SegmentOverlays = {}
|
||||
const overlayDetails = [p2Handle, p3Handle].map((handle, index) =>
|
||||
sceneInfra.updateOverlayDetails({
|
||||
handle: handle,
|
||||
group,
|
||||
isHandlesVisible: true,
|
||||
from: p1,
|
||||
to: p3,
|
||||
angle: endAngle + Math.PI / 2,
|
||||
hasThreeDotMenu: true,
|
||||
})
|
||||
)
|
||||
const segmentOverlays: SegmentOverlay[] = []
|
||||
|
||||
overlayDetails.forEach((payload, index) => {
|
||||
if (payload?.type === 'set-one') {
|
||||
overlays[payload.pathToNodeString] = payload.seg
|
||||
// Add filterValue: 'interior' for p2 and 'end' for p3
|
||||
segmentOverlays.push({
|
||||
...payload.seg[0],
|
||||
filterValue: index === 0 ? 'interior' : 'end',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const segmentOverlayPayload: SegmentOverlayPayload = {
|
||||
type: 'set-one',
|
||||
pathToNodeString:
|
||||
overlayDetails[0]?.type === 'set-one'
|
||||
? overlayDetails[0].pathToNodeString
|
||||
: '',
|
||||
seg: segmentOverlays,
|
||||
}
|
||||
return segmentOverlayPayload
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createProfileStartHandle({
|
||||
from,
|
||||
isDraft = false,
|
||||
@ -1010,7 +1616,7 @@ function createCircleCenterHandle(
|
||||
function createCircleThreePointHandle(
|
||||
scale = 1,
|
||||
theme: Themes,
|
||||
name: `circle-three-point-handle${'1' | '2' | '3'}`,
|
||||
name: string,
|
||||
color?: number
|
||||
): Group {
|
||||
const circleCenterGroup = new Group()
|
||||
@ -1249,9 +1855,11 @@ export function createArcGeometry({
|
||||
)
|
||||
)
|
||||
const remainingArcGeometry = new ExtrudeGeometry(shape, {
|
||||
steps: 50,
|
||||
steps: 1,
|
||||
bevelEnabled: false,
|
||||
extrudePath: remainingArcPath,
|
||||
extrudePath: new CatmullRomCurve3(
|
||||
remainingArcPoints.map((p) => new Vector3(p.x, p.y, 0))
|
||||
),
|
||||
})
|
||||
dashGeometries.push(remainingArcGeometry)
|
||||
}
|
||||
@ -1351,10 +1959,111 @@ export function dashedStraight(
|
||||
geo.userData.type = 'dashed'
|
||||
return geo
|
||||
}
|
||||
function createLine({
|
||||
from,
|
||||
to,
|
||||
scale,
|
||||
color,
|
||||
}: {
|
||||
from: [number, number]
|
||||
to: [number, number]
|
||||
scale: number
|
||||
color: number
|
||||
}): Line {
|
||||
// Implementation for creating a line
|
||||
const lineGeometry = new BufferGeometry().setFromPoints([
|
||||
new Vector3(from[0], from[1], 0),
|
||||
new Vector3(to[0], to[1], 0),
|
||||
])
|
||||
const lineMaterial = new LineBasicMaterial({ color })
|
||||
return new Line(lineGeometry, lineMaterial)
|
||||
}
|
||||
|
||||
function updateLine(
|
||||
line: Line,
|
||||
{
|
||||
from,
|
||||
to,
|
||||
scale,
|
||||
}: { from: [number, number]; to: [number, number]; scale: number }
|
||||
) {
|
||||
// Implementation for updating a line
|
||||
const points = [
|
||||
new Vector3(from[0], from[1], 0),
|
||||
new Vector3(to[0], to[1], 0),
|
||||
]
|
||||
line.geometry.setFromPoints(points)
|
||||
}
|
||||
|
||||
function createAngleIndicator({
|
||||
center,
|
||||
radius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
scale,
|
||||
color,
|
||||
}: {
|
||||
center: [number, number]
|
||||
radius: number
|
||||
startAngle: number
|
||||
endAngle: number
|
||||
scale: number
|
||||
color: number
|
||||
}): Line {
|
||||
// Implementation for creating an angle indicator
|
||||
const curve = new EllipseCurve(
|
||||
center[0],
|
||||
center[1],
|
||||
radius,
|
||||
radius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
false,
|
||||
0
|
||||
)
|
||||
const points = curve.getPoints(50)
|
||||
const geometry = new BufferGeometry().setFromPoints(points)
|
||||
const material = new LineBasicMaterial({ color })
|
||||
return new Line(geometry, material)
|
||||
}
|
||||
|
||||
function updateAngleIndicator(
|
||||
angleIndicator: Line,
|
||||
{
|
||||
center,
|
||||
radiusPx,
|
||||
startAngle,
|
||||
endAngle,
|
||||
scale,
|
||||
}: {
|
||||
center: [number, number]
|
||||
radiusPx: number
|
||||
startAngle: number
|
||||
endAngle: number
|
||||
scale: number
|
||||
}
|
||||
) {
|
||||
// Implementation for updating an angle indicator
|
||||
|
||||
const curve = new EllipseCurve(
|
||||
center[0],
|
||||
center[1],
|
||||
radiusPx * scale,
|
||||
radiusPx * scale,
|
||||
startAngle,
|
||||
endAngle,
|
||||
endAngle < startAngle,
|
||||
0
|
||||
)
|
||||
const points = curve.getPoints(50)
|
||||
angleIndicator.geometry.setFromPoints(points)
|
||||
}
|
||||
|
||||
export const segmentUtils = {
|
||||
straight: new StraightSegment(),
|
||||
tangentialArcTo: new TangentialArcToSegment(),
|
||||
circle: new CircleSegment(),
|
||||
circleThreePoint: new CircleThreePointSegment(),
|
||||
arc: new ArcSegment(),
|
||||
threePointArc: new ThreePointArcSegment(),
|
||||
} as const
|
||||
|
||||
@ -6,7 +6,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import { trap } from 'lib/trap'
|
||||
import { codeToIdSelections } from 'lib/selections'
|
||||
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
||||
import { defaultSourceRange, SourceRange, topLevelRange } from 'lang/wasm'
|
||||
import { defaultSourceRange, topLevelRange } from 'lang/wasm'
|
||||
import { isArray } from 'lib/utils'
|
||||
|
||||
export function AstExplorer() {
|
||||
|
||||
@ -4,7 +4,6 @@ import {
|
||||
Selections,
|
||||
canSubmitSelectionArg,
|
||||
getSelectionCountByType,
|
||||
getSelectionTypeDisplayText,
|
||||
} from 'lib/selections'
|
||||
import { useSelector } from '@xstate/react'
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
|
||||
@ -1,13 +1,9 @@
|
||||
import { Dialog } from '@headlessui/react'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import { useState } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { CREATE_FILE_URL_PARAM } from 'lib/constants'
|
||||
import { useSettings } from 'machines/appMachine'
|
||||
|
||||
const DownloadAppBanner = () => {
|
||||
const [searchParams] = useSearchParams()
|
||||
const hasCreateFileParam = searchParams.has(CREATE_FILE_URL_PARAM)
|
||||
const settings = useSettings()
|
||||
const [isBannerDismissed, setIsBannerDismissed] = useState(
|
||||
settings.app.dismissWebBanner.current
|
||||
|
||||
@ -70,6 +70,7 @@ import {
|
||||
import {
|
||||
KclValue,
|
||||
PathToNode,
|
||||
PipeExpression,
|
||||
Program,
|
||||
VariableDeclaration,
|
||||
parse,
|
||||
@ -110,7 +111,6 @@ import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { useSettings } from 'machines/appMachine'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -1497,6 +1497,48 @@ export const ModelingMachineProvider = ({
|
||||
return result
|
||||
}
|
||||
),
|
||||
'set-up-draft-arc-three-point': fromPromise(
|
||||
async ({ input: { sketchDetails, data } }) => {
|
||||
if (!sketchDetails || !data)
|
||||
return reject('No sketch details or data')
|
||||
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||
const result = await sceneEntitiesManager.setupDraftArcThreePoint(
|
||||
sketchDetails.sketchEntryNodePath,
|
||||
sketchDetails.sketchNodePaths,
|
||||
sketchDetails.planeNodePath,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin,
|
||||
data
|
||||
)
|
||||
if (err(result)) return reject(result)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||
|
||||
return result
|
||||
}
|
||||
),
|
||||
'set-up-draft-arc': fromPromise(
|
||||
async ({ input: { sketchDetails, data } }) => {
|
||||
if (!sketchDetails || !data)
|
||||
return reject('No sketch details or data')
|
||||
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
|
||||
const result = await sceneEntitiesManager.setupDraftArc(
|
||||
sketchDetails.sketchEntryNodePath,
|
||||
sketchDetails.sketchNodePaths,
|
||||
sketchDetails.planeNodePath,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin,
|
||||
data
|
||||
)
|
||||
if (err(result)) return reject(result)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||
|
||||
return result
|
||||
}
|
||||
),
|
||||
'setup-client-side-sketch-segments': fromPromise(
|
||||
async ({ input: { sketchDetails, selectionRanges } }) => {
|
||||
if (!sketchDetails) return
|
||||
@ -1568,24 +1610,53 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
|
||||
const indexToDelete = sketchDetails?.expressionIndexToDelete || -1
|
||||
let isLastInPipeThreePointArc = false
|
||||
if (indexToDelete >= 0) {
|
||||
// this is the expression that was added when as sketch tool was used but not completed
|
||||
// i.e first click for the center of the circle, but not the second click for the radius
|
||||
// we added a circle to editor, but they bailed out early so we should remove it
|
||||
moddedAst.body.splice(indexToDelete, 1)
|
||||
// make sure the deleted expression is removed from the sketchNodePaths
|
||||
updatedSketchNodePaths = updatedSketchNodePaths.filter(
|
||||
(path) => path[1][0] !== indexToDelete
|
||||
|
||||
const pipe = getNodeFromPath<PipeExpression>(
|
||||
moddedAst,
|
||||
pathToProfile,
|
||||
'PipeExpression'
|
||||
)
|
||||
// if the deleted expression was the entryNodePath, we should just make it the first sketchNodePath
|
||||
// as a safe default
|
||||
pathToProfile =
|
||||
pathToProfile[1][0] !== indexToDelete
|
||||
? pathToProfile
|
||||
: updatedSketchNodePaths[0]
|
||||
if (err(pipe)) {
|
||||
isLastInPipeThreePointArc = false
|
||||
} else {
|
||||
const lastInPipe = pipe?.node?.body?.[pipe.node.body.length - 1]
|
||||
if (
|
||||
lastInPipe &&
|
||||
Number(pathToProfile[1][0]) === indexToDelete &&
|
||||
lastInPipe.type === 'CallExpression' &&
|
||||
lastInPipe.callee.type === 'Identifier' &&
|
||||
lastInPipe.callee.name === 'arcTo'
|
||||
) {
|
||||
isLastInPipeThreePointArc = true
|
||||
pipe.node.body = pipe.node.body.slice(0, -1)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isLastInPipeThreePointArc) {
|
||||
moddedAst.body.splice(indexToDelete, 1)
|
||||
// make sure the deleted expression is removed from the sketchNodePaths
|
||||
updatedSketchNodePaths = updatedSketchNodePaths.filter(
|
||||
(path) => path[1][0] !== indexToDelete
|
||||
)
|
||||
// if the deleted expression was the entryNodePath, we should just make it the first sketchNodePath
|
||||
// as a safe default
|
||||
pathToProfile =
|
||||
pathToProfile[1][0] !== indexToDelete
|
||||
? pathToProfile
|
||||
: updatedSketchNodePaths[0]
|
||||
}
|
||||
}
|
||||
|
||||
if (doesNeedSplitting || indexToDelete >= 0) {
|
||||
if (
|
||||
doesNeedSplitting ||
|
||||
indexToDelete >= 0 ||
|
||||
isLastInPipeThreePointArc
|
||||
) {
|
||||
await kclManager.executeAstMock(moddedAst)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(moddedAst)
|
||||
}
|
||||
|
||||
@ -4,12 +4,11 @@ import {
|
||||
useLocation,
|
||||
useNavigate,
|
||||
useRouteLoaderData,
|
||||
redirect,
|
||||
} from 'react-router-dom'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { markOnce } from 'lib/performance'
|
||||
import { useAuthNavigation } from 'hooks/useAuthNavigation'
|
||||
import { useAuthState, useSettings } from 'machines/appMachine'
|
||||
import { useSettings } from 'machines/appMachine'
|
||||
import { IndexLoaderData } from 'lib/types'
|
||||
import { getAppSettingsFilePath } from 'lib/desktop'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
@ -17,9 +16,7 @@ import { trap } from 'lib/trap'
|
||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
||||
import { loadAndValidateSettings } from 'lib/settings/settingsUtils'
|
||||
import { settingsActor } from 'machines/appMachine'
|
||||
import makeUrlPathRelative from 'lib/makeUrlPathRelative'
|
||||
import { OnboardingStatus } from '@rust/kcl-lib/bindings/OnboardingStatus'
|
||||
import { SnapshotFrom } from 'xstate'
|
||||
|
||||
export const RouteProviderContext = createContext({})
|
||||
|
||||
@ -35,7 +32,6 @@ export function RouteProvider({ children }: { children: ReactNode }) {
|
||||
const location = useLocation()
|
||||
const settings = useSettings()
|
||||
|
||||
const authState = useAuthState()
|
||||
useEffect(() => {
|
||||
// On initialization, the react-router-dom does not send a 'loading' state event.
|
||||
// it sends an idle event first.
|
||||
|
||||
@ -3,7 +3,6 @@ import { CustomIcon } from 'components/CustomIcon'
|
||||
import decamelize from 'decamelize'
|
||||
import Fuse from 'fuse.js'
|
||||
import { interactionMap } from 'lib/settings/initialKeybindings'
|
||||
import { Setting } from 'lib/settings/initialSettings'
|
||||
import { SettingsLevel } from 'lib/settings/settingsTypes'
|
||||
import { useSettings } from 'machines/appMachine'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
|
||||
@ -28,7 +28,7 @@ import { base64Decode } from 'lang/wasm'
|
||||
import { sendTelemetry } from 'lib/textToCad'
|
||||
import { Themes } from 'lib/theme'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import { commandBarActor, commandBarMachine } from 'machines/commandBarMachine'
|
||||
import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { EventFrom } from 'xstate'
|
||||
import { fileMachine } from 'machines/fileMachine'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
|
||||
@ -6,9 +6,9 @@ import {
|
||||
} from '@kittycad/codemirror-lsp-client'
|
||||
import { fileSystemManager } from 'lang/std/fileSystemManager'
|
||||
import init, {
|
||||
ServerConfig,
|
||||
copilot_lsp_run,
|
||||
kcl_lsp_run,
|
||||
LspServerConfig,
|
||||
lsp_run_copilot,
|
||||
lsp_run_kcl,
|
||||
} from '@rust/kcl-wasm-lib/pkg/kcl_wasm_lib'
|
||||
import * as jsrpc from 'json-rpc-2.0'
|
||||
import {
|
||||
@ -30,13 +30,13 @@ const initialise = async (wasmUrl: string) => {
|
||||
}
|
||||
|
||||
export async function copilotLspRun(
|
||||
config: ServerConfig,
|
||||
config: LspServerConfig,
|
||||
token: string,
|
||||
baseUrl: string
|
||||
) {
|
||||
try {
|
||||
console.log('starting copilot lsp')
|
||||
await copilot_lsp_run(config, token, baseUrl)
|
||||
await lsp_run_copilot(config, token, baseUrl)
|
||||
} catch (e: any) {
|
||||
console.log('copilot lsp failed', e)
|
||||
// We can't restart here because a moved value, we should do this another way.
|
||||
@ -44,13 +44,13 @@ export async function copilotLspRun(
|
||||
}
|
||||
|
||||
export async function kclLspRun(
|
||||
config: ServerConfig,
|
||||
config: LspServerConfig,
|
||||
token: string,
|
||||
baseUrl: string
|
||||
) {
|
||||
try {
|
||||
console.log('start kcl lsp')
|
||||
await kcl_lsp_run(config, token, baseUrl)
|
||||
await lsp_run_kcl(config, token, baseUrl)
|
||||
} catch (e: any) {
|
||||
console.log('kcl lsp failed', e)
|
||||
// We can't restart here because a moved value, we should do this another way.
|
||||
@ -70,7 +70,7 @@ onmessage = function (event: MessageEvent) {
|
||||
initialise(wasmUrl)
|
||||
.then(async (instantiatedModule) => {
|
||||
console.log('Worker: WASM module loaded', worker, instantiatedModule)
|
||||
const config = new ServerConfig(
|
||||
const config = new LspServerConfig(
|
||||
intoServer,
|
||||
fromServer,
|
||||
fileSystemManager
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useLayoutEffect, useEffect, useRef } from 'react'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { Themes } from 'lib/theme'
|
||||
import { useModelingContext } from './useModelingContext'
|
||||
@ -93,10 +93,6 @@ export function useSetupEngineManager(
|
||||
engineCommandManager.settings = settings
|
||||
|
||||
const handleResize = deferExecution(() => {
|
||||
const { width, height } = getDimensions(
|
||||
streamRef?.current?.offsetWidth ?? 0,
|
||||
streamRef?.current?.offsetHeight ?? 0
|
||||
)
|
||||
engineCommandManager.handleResize(engineCommandManager.streamDimensions)
|
||||
}, 500)
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { executeAst, lintAst } from 'lang/langHelpers'
|
||||
import { executeAst, executeAstMock, lintAst } from 'lang/langHelpers'
|
||||
import { handleSelectionBatch, Selections } from 'lib/selections'
|
||||
import {
|
||||
KCLError,
|
||||
@ -324,6 +324,7 @@ export class KclManager {
|
||||
if (this.wasmInitFailed) {
|
||||
this.wasmInitFailed = false
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (e) {
|
||||
this.wasmInitFailed = true
|
||||
}
|
||||
@ -338,7 +339,7 @@ export class KclManager {
|
||||
if (this.isExecuting) {
|
||||
this.executeIsStale = args
|
||||
|
||||
// The previous execteAst will be rejected and cleaned up. The execution will be marked as stale.
|
||||
// The previous executeAst will be rejected and cleaned up. The execution will be marked as stale.
|
||||
// A new executeAst will start.
|
||||
this.engineCommandManager.rejectAllModelingCommands(
|
||||
EXECUTE_AST_INTERRUPT_ERROR_MESSAGE
|
||||
@ -358,9 +359,7 @@ export class KclManager {
|
||||
const { logs, errors, execState, isInterrupted } = await executeAst({
|
||||
ast,
|
||||
path: codeManager.currentFilePath || undefined,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
rustContext,
|
||||
isMock: false,
|
||||
})
|
||||
|
||||
// Program was not interrupted, setup the scene
|
||||
@ -476,11 +475,9 @@ export class KclManager {
|
||||
}
|
||||
this._ast = { ...newAst }
|
||||
|
||||
const { logs, errors, execState } = await executeAst({
|
||||
const { logs, errors, execState } = await executeAstMock({
|
||||
ast: newAst,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
rustContext,
|
||||
isMock: true,
|
||||
})
|
||||
|
||||
this._logs = logs
|
||||
|
||||
@ -7,7 +7,6 @@ import {
|
||||
sketchFromKclValue,
|
||||
defaultArtifactGraph,
|
||||
topLevelRange,
|
||||
VariableMap,
|
||||
} from './wasm'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { KCLError } from './errors'
|
||||
|
||||
@ -5,7 +5,6 @@ import {
|
||||
assertParse,
|
||||
initPromise,
|
||||
Parameter,
|
||||
SourceRange,
|
||||
topLevelRange,
|
||||
} from './wasm'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
@ -38,6 +38,7 @@ afterAll(async () => {
|
||||
try {
|
||||
process.chdir('..')
|
||||
await fs.rm(DIR_KCL_SAMPLES, { recursive: true })
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (e) {}
|
||||
})
|
||||
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import {
|
||||
Program,
|
||||
executeMock,
|
||||
kclLint,
|
||||
emptyExecState,
|
||||
ExecState,
|
||||
jsAppSettings,
|
||||
} from 'lang/wasm'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
import { KCLError } from 'lang/errors'
|
||||
import { Diagnostic } from '@codemirror/lint'
|
||||
import { Node } from '@rust/kcl-lib/bindings/Node'
|
||||
@ -28,6 +26,8 @@ export type ToolTip =
|
||||
| 'tangentialArcTo'
|
||||
| 'circle'
|
||||
| 'circleThreePoint'
|
||||
| 'arcTo'
|
||||
| 'arc'
|
||||
|
||||
export const toolTips: Array<ToolTip> = [
|
||||
'line',
|
||||
@ -44,35 +44,31 @@ export const toolTips: Array<ToolTip> = [
|
||||
'angledLineThatIntersects',
|
||||
'tangentialArcTo',
|
||||
'circleThreePoint',
|
||||
'arc',
|
||||
'arcTo',
|
||||
]
|
||||
|
||||
export async function executeAst({
|
||||
ast,
|
||||
path,
|
||||
engineCommandManager,
|
||||
isMock,
|
||||
usePrevMemory,
|
||||
rustContext,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
path?: string
|
||||
engineCommandManager: EngineCommandManager
|
||||
rustContext: RustContext
|
||||
isMock: boolean
|
||||
usePrevMemory?: boolean
|
||||
isInterrupted?: boolean
|
||||
}): Promise<{
|
||||
interface ExecutionResult {
|
||||
logs: string[]
|
||||
errors: KCLError[]
|
||||
execState: ExecState
|
||||
isInterrupted: boolean
|
||||
}> {
|
||||
try {
|
||||
const execState = await (isMock
|
||||
? executeMock(ast, usePrevMemory, path)
|
||||
: rustContext.execute(ast, { settings: await jsAppSettings() }, path))
|
||||
}
|
||||
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
export async function executeAst({
|
||||
ast,
|
||||
rustContext,
|
||||
path,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
rustContext: RustContext
|
||||
path?: string
|
||||
}): Promise<ExecutionResult> {
|
||||
try {
|
||||
const settings = { settings: await jsAppSettings() }
|
||||
const execState = await rustContext.execute(ast, settings, path)
|
||||
|
||||
await rustContext.waitForAllEngineCommands()
|
||||
return {
|
||||
logs: [],
|
||||
errors: [],
|
||||
@ -80,29 +76,65 @@ export async function executeAst({
|
||||
isInterrupted: false,
|
||||
}
|
||||
} catch (e: any) {
|
||||
let isInterrupted = false
|
||||
if (e instanceof KCLError) {
|
||||
// Detect if it is a force interrupt error which is not a KCL processing error.
|
||||
if (
|
||||
e.msg ===
|
||||
'Failed to wait for promise from engine: JsValue("Force interrupt, executionIsStale, new AST requested")'
|
||||
) {
|
||||
isInterrupted = true
|
||||
}
|
||||
return {
|
||||
errors: [e],
|
||||
logs: [],
|
||||
execState: emptyExecState(),
|
||||
isInterrupted,
|
||||
}
|
||||
} else {
|
||||
console.log(e)
|
||||
return {
|
||||
logs: [e],
|
||||
errors: [],
|
||||
execState: emptyExecState(),
|
||||
isInterrupted,
|
||||
}
|
||||
return handleExecuteError(e)
|
||||
}
|
||||
}
|
||||
|
||||
export async function executeAstMock({
|
||||
ast,
|
||||
rustContext,
|
||||
path,
|
||||
usePrevMemory,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
rustContext: RustContext
|
||||
path?: string
|
||||
usePrevMemory?: boolean
|
||||
}): Promise<ExecutionResult> {
|
||||
try {
|
||||
const settings = { settings: await jsAppSettings() }
|
||||
const execState = await rustContext.executeMock(
|
||||
ast,
|
||||
settings,
|
||||
path,
|
||||
usePrevMemory
|
||||
)
|
||||
|
||||
await rustContext.waitForAllEngineCommands()
|
||||
return {
|
||||
logs: [],
|
||||
errors: [],
|
||||
execState,
|
||||
isInterrupted: false,
|
||||
}
|
||||
} catch (e: any) {
|
||||
return handleExecuteError(e)
|
||||
}
|
||||
}
|
||||
|
||||
function handleExecuteError(e: any): ExecutionResult {
|
||||
let isInterrupted = false
|
||||
if (e instanceof KCLError) {
|
||||
// Detect if it is a force interrupt error which is not a KCL processing error.
|
||||
if (
|
||||
e.msg ===
|
||||
'Failed to wait for promise from engine: JsValue("Force interrupt, executionIsStale, new AST requested")'
|
||||
) {
|
||||
isInterrupted = true
|
||||
}
|
||||
return {
|
||||
errors: [e],
|
||||
logs: [],
|
||||
execState: emptyExecState(),
|
||||
isInterrupted,
|
||||
}
|
||||
} else {
|
||||
console.log(e)
|
||||
return {
|
||||
logs: [e],
|
||||
errors: [],
|
||||
execState: emptyExecState(),
|
||||
isInterrupted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@ import {
|
||||
BinaryExpression,
|
||||
PathToNode,
|
||||
SourceRange,
|
||||
sketchFromKclValue,
|
||||
isPathToNodeNumber,
|
||||
parse,
|
||||
formatNumber,
|
||||
@ -75,7 +74,6 @@ import {
|
||||
import { BodyItem } from '@rust/kcl-lib/bindings/BodyItem'
|
||||
import { findKwArg } from './util'
|
||||
import { deleteEdgeTreatment } from './modifyAst/addEdgeTreatment'
|
||||
import { codeManager } from 'lib/singletons'
|
||||
|
||||
export function startSketchOnDefault(
|
||||
node: Node<Program>,
|
||||
|
||||
@ -13,8 +13,6 @@ import {
|
||||
} from '../wasm'
|
||||
import {
|
||||
createCallExpressionStdLib,
|
||||
createPipeSubstitution,
|
||||
createObjectExpression,
|
||||
createArrayExpression,
|
||||
createIdentifier,
|
||||
createPipeExpression,
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
rustContext,
|
||||
} from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import { executeAstMock } from 'lang/langHelpers'
|
||||
|
||||
export const deletionErrorMessage =
|
||||
'Unable to delete selection. Please edit manually in code pane.'
|
||||
@ -29,11 +29,9 @@ export async function deleteSelectionPromise(
|
||||
return new Error(deletionErrorMessage)
|
||||
}
|
||||
|
||||
const testExecute = await executeAst({
|
||||
const testExecute = await executeAstMock({
|
||||
ast: modifiedAst,
|
||||
engineCommandManager,
|
||||
rustContext,
|
||||
isMock: true,
|
||||
rustContext: rustContext,
|
||||
})
|
||||
if (testExecute.errors.length) {
|
||||
return new Error(deletionErrorMessage)
|
||||
|
||||
@ -5,9 +5,6 @@ import {
|
||||
PathToNode,
|
||||
Identifier,
|
||||
topLevelRange,
|
||||
PipeExpression,
|
||||
CallExpression,
|
||||
VariableDeclarator,
|
||||
} from './wasm'
|
||||
import {
|
||||
findAllPreviousVariables,
|
||||
|
||||
@ -39,7 +39,7 @@ import {
|
||||
import { err, Reason } from 'lib/trap'
|
||||
import { Node } from '@rust/kcl-lib/bindings/Node'
|
||||
import { findKwArg } from './util'
|
||||
import { codeRefFromRange, getPlaneFromArtifact } from './std/artifactGraph'
|
||||
import { codeRefFromRange } from './std/artifactGraph'
|
||||
import { FunctionExpression } from '@rust/kcl-lib/bindings/FunctionExpression'
|
||||
import { ImportStatement } from '@rust/kcl-lib/bindings/ImportStatement'
|
||||
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
|
||||
@ -595,6 +595,7 @@ export function isLinesParallelAndConstrained(
|
||||
artifact: artifactGraph.get(prevSegment.__geoMeta.id),
|
||||
},
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
} catch (e) {
|
||||
return {
|
||||
isParallelAndConstrained: false,
|
||||
|
||||