Merge remote-tracking branch 'origin/main' into paultag/import

This commit is contained in:
Paul R. Tagliamonte
2025-03-13 11:17:34 -04:00
122 changed files with 1813 additions and 1666 deletions

View File

@ -33,26 +33,63 @@ jobs:
- run: yarn install
- id: filter
name: Check for Rust changes
uses: dorny/paths-filter@v3
with:
filters: |
rust:
- 'rust/**'
- name: Download Wasm Cache
id: download-wasm
if: ${{ github.event_name == 'pull_request' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7
continue-on-error: true
with:
github_token: ${{secrets.GITHUB_TOKEN}}
name: wasm-bundle
workflow: build-and-store-wasm.yml
branch: main
path: rust/kcl-wasm-lib/pkg
- name: Build WASM condition
id: wasm
run: |
set -euox pipefail
# Build wasm if this is a push to main or tag, there are Rust changes, or
# downloading from the wasm cache failed.
if [[ ${{github.event_name}} == 'push' || ${{steps.filter.outputs.rust}} == 'true' || ${{steps.download-wasm.outcome}} == 'failure' ]]; then
echo "should-build-wasm=true" >> $GITHUB_OUTPUT
else
echo "should-build-wasm=false" >> $GITHUB_OUTPUT
fi
- name: Use correct Rust toolchain
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false # Configured below.
# TODO: see if we can fetch from main instead if no diff at rust
- uses: taiki-e/install-action@955a6ff1416eae278c9f833008a9beb4b7f9afe3
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
with:
tool: wasm-pack
- name: Rust Cache
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2
with:
workspaces: rust
- name: Run build:wasm
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
run: "yarn build:wasm"
- name: Set nightly version, product name, release notes, and icons

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import {
getUtils,
TEST_COLORS,

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { HomePageFixture } from './fixtures/homePageFixture'
import { getUtils } from './test-utils'
import { EngineCommand } from 'lang/std/artifactGraph'

View File

@ -10,7 +10,11 @@ import fsp from 'fs/promises'
test(
'export works on the first try',
{ tag: ['@electron', '@skipLocalEngine'] },
async ({ page, context, scene }, testInfo) => {
async ({ page, context, scene, tronApp }, testInfo) => {
if (!tronApp) {
fail()
}
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
@ -86,7 +90,7 @@ test(
await expect(exportingToastMessage).not.toBeVisible()
const firstFileFullPath = path.resolve(
getPlaywrightDownloadDir(page),
getPlaywrightDownloadDir(tronApp.projectDirName),
exportFileName
)
await test.step('Check the export size', async () => {
@ -165,7 +169,7 @@ test(
]))
const secondFileFullPath = path.resolve(
getPlaywrightDownloadDir(page),
getPlaywrightDownloadDir(tronApp.projectDirName),
exportFileName
)
await test.step('Check the export size', async () => {

View File

@ -158,11 +158,14 @@ test.describe('when using the file tree to', () => {
await createNewFile('lee')
await test.step('Postcondition: there are 5 new lee-*.kcl files', async () => {
await expect(
await expect
.poll(() =>
page
.locator('[data-testid="file-pane-scroll-container"] button')
.filter({ hasText: /lee[-]?[0-5]?/ })
).toHaveCount(5)
.count()
)
.toEqual(5)
})
}
)

View File

@ -27,28 +27,19 @@ type CmdBarSerialised =
export class CmdBarFixture {
public page: Page
get cmdBarOpenBtn() {
return this.page.getByTestId('command-bar-open-button')
}
get cmdBarElement() {
return this.page.getByTestId('command-bar')
}
public cmdBarOpenBtn!: Locator
public cmdBarElement!: Locator
constructor(page: Page) {
this.page = page
this.cmdBarOpenBtn = this.page.getByTestId('command-bar-open-button')
this.cmdBarElement = this.page.getByTestId('command-bar')
}
get currentArgumentInput() {
return this.page.getByTestId('cmd-bar-arg-value')
}
// Put all selectors here because this method is re-run on fixture creation.
reConstruct = (page: Page) => {
this.page = page
}
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
return { stage: 'commandBarClosed' }

View File

@ -24,11 +24,6 @@ export class EditorFixture {
constructor(page: Page) {
this.page = page
this.reConstruct(page)
}
reConstruct = (page: Page) => {
this.page = page
this.codeContent = page.locator('.cm-content[data-language="kcl"]')
this.diagnosticsTooltip = page.locator('.cm-tooltip-lint')
this.diagnosticsGutterIcon = page.locator('.cm-lint-marker-error')

View File

@ -1,13 +1,31 @@
/* eslint-disable react-hooks/rules-of-hooks */
import type {
BrowserContext,
ElectronApplication,
Fixtures as PlaywrightFixtures,
TestInfo,
Page,
} from '@playwright/test'
import { getUtils, setup, setupElectron } from '../test-utils'
import {
_electron as electron,
PlaywrightTestArgs,
PlaywrightWorkerArgs,
} 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 { getUtils, setup } from '../test-utils'
import fsp from 'fs/promises'
import { join } from 'path'
import fs from 'node:fs'
import path from 'path'
import { CmdBarFixture } from './cmdBarFixture'
import { EditorFixture } from './editorFixture'
import { ToolbarFixture } from './toolbarFixture'
@ -23,7 +41,7 @@ export class AuthenticatedApp {
public readonly testInfo: TestInfo
public readonly viewPortSize = { width: 1200, height: 500 }
public electronApp: undefined | ElectronApplication
public dir: string = ''
public projectDirName: string = ''
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
this.context = context
@ -46,7 +64,7 @@ export class AuthenticatedApp {
}
getInputFile = (fileName: string) => {
return fsp.readFile(
join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName),
path.join('rust', 'kcl-lib', 'e2e', 'executor', 'inputs', fileName),
'utf-8'
)
}
@ -59,101 +77,300 @@ export interface Fixtures {
scene: SceneFixture
homePage: HomePageFixture
}
export class AuthenticatedTronApp {
public originalPage: Page
public page: Page
public browserContext: BrowserContext
public context: BrowserContext
public readonly testInfo: TestInfo
public electronApp: ElectronApplication | undefined
public readonly viewPortSize = { width: 1200, height: 500 }
public dir: string = ''
constructor(
browserContext: BrowserContext,
originalPage: Page,
testInfo: TestInfo
) {
this.page = originalPage
this.originalPage = originalPage
this.browserContext = browserContext
// Will be overwritten in the initializer
this.context = browserContext
this.testInfo = testInfo
export class ElectronZoo {
public available: boolean = true
public electron!: ElectronApplication
public firstUrl = ''
public viewPortSize = { width: 1200, height: 500 }
public projectDirName = ''
public page!: Page
public context!: BrowserContext
constructor() {}
async makeAvailableAgain() {
// Help remote end by signaling we're done with the connection.
await this.page.evaluate(async () => {
return new Promise((resolve) => {
if (!window.engineCommandManager.engineConnection?.state?.type) {
return resolve(undefined)
}
async initialise(
arg: {
fixtures: Partial<Fixtures>
folderSetupFn?: (projectDirName: string) => Promise<void>
cleanProjectDir?: boolean
appSettings?: DeepPartial<Settings>
} = { fixtures: {} }
) {
const { electronApp, page, context, dir } = await setupElectron({
testInfo: this.testInfo,
folderSetupFn: arg.folderSetupFn,
cleanProjectDir: arg.cleanProjectDir,
appSettings: arg.appSettings,
viewport: this.viewPortSize,
})
this.page = page
// These assignments "fix" some brokenness in the Playwright Workbench when
// running against electron applications.
// The timeline is still broken but failure screenshots work again.
this.context = context
// TODO: try to get this to work again for screenshots, but it messed with test ends when enabled
// Object.assign(this.browserContext, this.context)
this.electronApp = electronApp
this.dir = dir
// Easier to access throughout utils
this.page.dir = dir
// Setup localStorage, addCookies, reload
await setup(this.context, this.page, this.testInfo)
for (const key of unsafeTypedKeys(arg.fixtures)) {
const fixture = arg.fixtures[key]
window.engineCommandManager.tearDown()
// Keep polling (per js event tick) until state is Disconnected.
const checkDisconnected = () => {
// It's possible we never even created an engineConnection
// e.g. never left Projects view.
if (
!fixture ||
fixture instanceof AuthenticatedApp ||
fixture instanceof AuthenticatedTronApp
)
continue
fixture.reConstruct(page)
window.engineCommandManager?.engineConnection?.state.type ===
'disconnected'
) {
return resolve(undefined)
}
setTimeout(checkDisconnected, 0)
}
checkDisconnected()
})
})
await this.context.tracing.stopChunk({ path: 'trace.zip' })
// Only after cleanup we're ready.
this.available = true
}
close = async () => {
await this.electronApp?.close?.()
async createInstanceIfMissing(testInfo: TestInfo) {
// Create or otherwise clear the folder.
this.projectDirName = testInfo.outputPath('electron-test-projects-dir')
// We need to expose this in order for some tests that require folder
// creation and some code below.
const that = this
const options = {
args: ['.', '--no-sandbox'],
env: {
...process.env,
TEST_SETTINGS_FILE_KEY: this.projectDirName,
IS_PLAYWRIGHT: 'true',
},
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
? {
executablePath:
process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron',
}
debugPause = () =>
new Promise(() => {
console.log('UN-RESOLVING PROMISE')
: {}),
...(process.env.PLAYWRIGHT_RECORD_VIDEO
? {
recordVideo: {
dir: testInfo.snapshotPath(),
size: this.viewPortSize,
},
}
: {}),
}
// Do this once and then reuse window on subsequent calls.
if (!this.electron) {
this.electron = await electron.launch(options)
this.context = this.electron.context()
this.page = await this.electron.firstWindow()
await this.context.tracing.start({ screenshots: true, snapshots: true })
}
await this.context.tracing.startChunk()
await setup(this.context, this.page, testInfo)
await this.cleanProjectDir()
// Create a consistent way to resize the page across electron and web.
// (lee) I had to do everything in the book to make electron change its
// damn window size. I succeeded in making it consistently and reliably
// do it after a whole afternoon.
this.page.setBodyDimensions = async function (dims: {
width: number
height: number
}) {
await this.setViewportSize(dims)
await that.electron?.evaluateHandle(async ({ app }, dims) => {
// @ts-ignore sorry jon but see comment in main.ts why this is ignored
await app.resizeWindow(dims.width, dims.height)
}, dims)
return this.evaluate(async (dims: { width: number; height: number }) => {
await window.electron.resizeWindow(dims.width, dims.height)
window.document.body.style.width = dims.width + 'px'
window.document.body.style.height = dims.height + 'px'
window.document.documentElement.style.width = dims.width + 'px'
window.document.documentElement.style.height = dims.height + 'px'
}, dims)
}
await this.page.setBodyDimensions(this.viewPortSize)
this.context.folderSetupFn = async function (fn) {
return fn(that.projectDirName)
.then(() => that.page.reload())
.then(() => ({
dir: that.projectDirName,
}))
}
// We need to patch this because addInitScript will bind too late in our
// electron tests, never running. We need to call reload() after each call
// to guarantee it runs.
const oldContextAddInitScript = this.context.addInitScript
this.context.addInitScript = async function (a, b) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldContextAddInitScript.apply(this, [a, b])
await that.page.reload()
}
// No idea why we mix and match page and context's addInitScript but we do
const oldPageAddInitScript = this.page.addInitScript
this.page.addInitScript = async function (a: any, b: any) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldPageAddInitScript.apply(this, [a, b])
await that.page.reload()
}
if (!this.firstUrl) {
await this.page.getByText('Your Projects').count()
this.firstUrl = this.page.url()
}
// Due to the app controlling its own window context we need to inject new
// options and context here.
// NOTE TO LEE: Seems to destroy page context when calling an electron loadURL.
// await tronApp.electronApp.evaluate(({ app }) => {
// return app.reuseWindowForTest();
// });
await this.electron?.evaluate(({ app }, projectDirName) => {
// @ts-ignore can't declaration merge see main.ts
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
}, this.projectDirName)
// Always start at the root view
await this.page.goto(this.firstUrl)
// Force a hard reload, destroying the stream and other state
await this.page.reload()
}
async cleanProjectDir(appSettings?: DeepPartial<Settings>) {
try {
if (fs.existsSync(this.projectDirName)) {
await fsp.rm(this.projectDirName, { recursive: true })
}
} catch (e) {
console.error(e)
}
try {
await fsp.mkdir(this.projectDirName)
} catch (e) {
// Not a problem if it already exists.
}
const tempSettingsFilePath = path.join(
this.projectDirName,
SETTINGS_FILE_NAME
)
let settingsOverridesToml = ''
if (appSettings) {
settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: {
...TEST_SETTINGS,
...appSettings,
app: {
...TEST_SETTINGS.app,
project_directory: this.projectDirName,
...appSettings.app,
},
},
})
} else {
settingsOverridesToml = TOML.stringify({
// @ts-expect-error
settings: {
...TEST_SETTINGS,
app: {
...TEST_SETTINGS.app,
project_directory: this.projectDirName,
},
},
})
}
await fsp.writeFile(tempSettingsFilePath, settingsOverridesToml)
}
}
export const fixtures = {
cmdBar: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
// If yee encounter this, please try to type it.
type FnUse = any
const fixturesForElectron = {
page: async (
{ tronApp }: { tronApp: ElectronZoo },
use: FnUse,
testInfo: TestInfo
) => {
await tronApp.createInstanceIfMissing(testInfo)
await use(tronApp.page)
await tronApp?.makeAvailableAgain()
},
context: async (
{ tronApp }: { tronApp: ElectronZoo },
use: FnUse,
testInfo: TestInfo
) => {
await tronApp.createInstanceIfMissing(testInfo)
await use(tronApp.context)
},
}
const fixturesForWeb = {
page: async (
{ page, context }: { page: Page; context: BrowserContext },
use: FnUse,
testInfo: TestInfo
) => {
page.setBodyDimensions = page.setViewportSize
// We do the same thing in ElectronZoo. addInitScript simply doesn't fire
// at the correct time, so we reload the page and it fires appropriately.
const oldPageAddInitScript = page.addInitScript
page.addInitScript = async function (...args) {
// @ts-expect-error
await oldPageAddInitScript.apply(this, args)
await page.reload()
}
const oldContextAddInitScript = context.addInitScript
context.addInitScript = async function (...args) {
// @ts-expect-error
await oldContextAddInitScript.apply(this, args)
await page.reload()
}
const webApp = new AuthenticatedApp(context, page, testInfo)
await webApp.initialise()
await use(page)
},
}
const fixturesBasedOnProcessEnvPlatform = {
cmdBar: async ({ page }: { page: Page }, use: FnUse) => {
await use(new CmdBarFixture(page))
},
editor: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
editor: async ({ page }: { page: Page }, use: FnUse) => {
await use(new EditorFixture(page))
},
toolbar: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
toolbar: async ({ page }: { page: Page }, use: FnUse) => {
await use(new ToolbarFixture(page))
},
scene: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
scene: async ({ page }: { page: Page }, use: FnUse) => {
await use(new SceneFixture(page))
},
homePage: async ({ page }: { page: Page }, use: any) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
homePage: async ({ page }: { page: Page }, use: FnUse) => {
await use(new HomePageFixture(page))
},
}
if (process.env.PLATFORM === 'web') {
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForWeb)
} else {
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForElectron)
}
export { fixturesBasedOnProcessEnvPlatform }

View File

@ -27,10 +27,6 @@ export class HomePageFixture {
constructor(page: Page) {
this.page = page
this.reConstruct(page)
}
reConstruct = (page: Page) => {
this.page = page
this.projectSection = this.page.getByTestId('home-section')
@ -96,8 +92,12 @@ export class HomePageFixture {
}
}
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
projectsLoaded = async () => {
await expect(this.projectSection).not.toHaveText('Loading your Projects...')
}
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
await this.projectsLoaded()
await this.projectButtonNew.click()
await this.projectTextName.click()
await this.projectTextName.fill(projectTitle)

View File

@ -53,7 +53,12 @@ export class SceneFixture {
constructor(page: Page) {
this.page = page
this.reConstruct(page)
this.streamWrapper = page.getByTestId('stream')
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))
}
private _serialiseScene = async (): Promise<SceneSerialised> => {
const camera = await this.getCameraInfo()
@ -72,17 +77,6 @@ export class SceneFixture {
.toEqual(expected)
}
reConstruct = (page: Page) => {
this.page = page
this.streamWrapper = page.getByTestId('stream')
this.networkToggleConnected = page.getByTestId('network-toggle-ok')
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))
}
makeMouseHelpers = (
x: number,
y: number,
@ -253,7 +247,7 @@ export class SceneFixture {
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await u.closeDebugPanel()
await this.waitForExecutionDone()
await expect(this.startEditSketchBtn).not.toBeDisabled()

View File

@ -37,13 +37,12 @@ export class ToolbarFixture {
featureTreeId = 'feature-tree' as const
/** The pane element for the Feature Tree */
featureTreePane!: Locator
gizmo!: Locator
gizmoDisabled!: Locator
constructor(page: Page) {
this.page = page
this.reConstruct(page)
}
reConstruct = (page: Page) => {
this.page = page
this.extrudeButton = page.getByTestId('extrude')
this.loftButton = page.getByTestId('loft')
this.sweepButton = page.getByTestId('sweep')
@ -67,6 +66,13 @@ export class ToolbarFixture {
this.filePane = page.locator('#files-pane')
this.featureTreePane = page.locator('#feature-tree-pane')
this.fileCreateToast = page.getByText('Successfully created')
// Note to test writers: having two locators like this is preferable to one
// which changes another el property because it means our test "signal" is
// completely decoupled from the elements themselves. It means the same
// element or two different elements can represent these states.
this.gizmo = page.getByTestId('gizmo')
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
}
get editSketchBtn() {
@ -86,6 +92,18 @@ export class ToolbarFixture {
startSketchPlaneSelection = async () =>
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
waitUntilSketchingReady = async () => {
await expect(this.gizmoDisabled).toBeVisible()
}
startSketchThenCallbackThenWaitUntilReady = async (
cb: () => Promise<void>
) => {
await this.startSketchBtn.click()
await cb()
await this.waitUntilSketchingReady()
}
exitSketch = async () => {
await this.exitSketchBtn.click()
await expect(

View File

@ -21,58 +21,54 @@ import { expectPixelColor } from './fixtures/sceneFixture'
// we must set it to empty for the tests where we want to see the onboarding immediately.
test.describe('Onboarding tests', () => {
test(
'Onboarding code is shown in the editor',
{
appSettings: {
test('Onboarding code is shown in the editor', async ({
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: '',
},
},
cleanProjectDir: true,
},
async ({ page, homePage }) => {
})
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
// Test that the onboarding pane loaded
await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
// Test that the onboarding pane loaded
await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
// *and* that the code is shown in the editor
await expect(page.locator('.cm-content')).toContainText(
'// Shelf Bracket'
)
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
// Make sure the model loaded
const XYPlanePoint = { x: 774, y: 116 } as const
const modelColor: [number, number, number] = [45, 45, 45]
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(
8
)
}
)
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(8)
})
test(
'Desktop: fresh onboarding executes and loads',
{
tag: '@electron',
appSettings: {
},
async ({ page, tronApp }) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: '',
},
},
cleanProjectDir: true,
},
async ({ page }) => {
})
const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 }
@ -107,22 +103,30 @@ test.describe('Onboarding tests', () => {
}
)
test(
'Code resets after confirmation',
{
cleanProjectDir: true,
},
async ({ context, page, homePage }) => {
test('Code resets after confirmation', async ({
context,
page,
homePage,
tronApp,
scene,
cmdBar,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir()
const initialCode = `sketch001 = startSketchOn('XZ')`
// Load the page up with some code so we see the confirmation warning
// when we go to replay onboarding
await context.addInitScript((code) => {
await page.addInitScript((code) => {
localStorage.setItem('persistCode', code)
}, initialCode)
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await scene.connectionEstablished()
// Replay the onboarding
await page.getByRole('link', { name: 'Settings' }).last().click()
@ -142,26 +146,27 @@ test.describe('Onboarding tests', () => {
// Ensure we see the introduction and that the code has been reset
await expect(page.getByText('Welcome to Modeling App!')).toBeVisible()
await expect(page.locator('.cm-content')).toContainText(
'// Shelf Bracket'
)
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
// There used to be old code here that checked if we stored the reset
// code into localStorage but that isn't the case on desktop. It gets
// saved to the file system, which we have other tests for.
}
)
})
test(
'Click through each onboarding step and back',
{
appSettings: {
test('Click through each onboarding step and back', async ({
context,
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: '',
},
},
},
async ({ context, page, homePage }) => {
})
// Override beforeEach test setup
await context.addInitScript(
async ({ settingsKey, settings }) => {
@ -181,9 +186,7 @@ test.describe('Onboarding tests', () => {
await homePage.goToModelingScene()
// Test that the onboarding pane loaded
await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
await expect(page.getByText('Welcome to Modeling App! This')).toBeVisible()
const nextButton = page.getByTestId('onboarding-next')
const prevButton = page.getByTestId('onboarding-prev')
@ -205,20 +208,23 @@ test.describe('Onboarding tests', () => {
// Test that the onboarding pane is gone
await expect(page.getByTestId('onboarding-content')).not.toBeVisible()
await expect.poll(() => page.url()).not.toContain('/onboarding')
}
)
})
test(
'Onboarding redirects and code updating',
{
appSettings: {
test('Onboarding redirects and code updating', async ({
context,
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: '/export',
},
},
cleanProjectDir: true,
},
async ({ context, page, homePage }) => {
})
const originalCode = 'sigmaAllow = 15000'
// Override beforeEach test setup
@ -260,21 +266,22 @@ test.describe('Onboarding tests', () => {
await page.locator('[data-testid="onboarding-next"]').hover()
await page.locator('[data-testid="onboarding-next"]').click()
await expect(page.locator('.cm-content')).toHaveText(/.+/)
}
)
})
test(
'Onboarding code gets reset to demo on Interactive Numbers step',
{
appSettings: {
test('Onboarding code gets reset to demo on Interactive Numbers step', async ({
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: '/parametric-modeling',
},
},
cleanProjectDir: true,
},
})
async ({ page, homePage }) => {
const u = await getUtils(page)
const badCode = `// This is bad code we shouldn't see`
@ -307,23 +314,24 @@ test.describe('Onboarding tests', () => {
// Check that the code has been reset
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
}
)
})
// (lee) The two avatar tests are weird because even on main, we don't have
// anything to do with the avatar inside the onboarding test. Due to the
// low impact of an avatar not showing I'm changing this to fixme.
test.fixme(
'Avatar text updates depending on image load success',
{
appSettings: {
async ({ context, page, homePage, tronApp }) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: '',
},
},
cleanProjectDir: true,
},
async ({ context, page, homePage }) => {
})
// Override beforeEach test setup
await context.addInitScript(
async ({ settingsKey, settings }) => {
@ -388,15 +396,16 @@ test.describe('Onboarding tests', () => {
test.fixme(
"Avatar text doesn't mention avatar when no avatar",
{
appSettings: {
async ({ context, page, homePage, tronApp }) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: '',
},
},
cleanProjectDir: true,
},
async ({ context, page, homePage }) => {
})
// Override beforeEach test setup
await context.addInitScript(
async ({ settingsKey, settings }) => {
@ -444,15 +453,17 @@ test.describe('Onboarding tests', () => {
test.fixme(
'Restarting onboarding on desktop takes one attempt',
{
appSettings: {
async ({ context, page, tronApp }) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
onboarding_status: 'dismissed',
},
},
cleanProjectDir: true,
},
async ({ context, page }) => {
})
await context.folderSetupFn(async (dir) => {
const routerTemplateDir = join(dir, 'router-template-slate')
await fsp.mkdir(routerTemplateDir, { recursive: true })

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { EditorFixture } from './fixtures/editorFixture'
import { SceneFixture } from './fixtures/sceneFixture'
import { ToolbarFixture } from './fixtures/toolbarFixture'

View File

@ -163,7 +163,7 @@ test(
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
timeout: 10_000,
})
.toBeLessThan(15)
.toBeLessThan(20)
})
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
@ -464,7 +464,11 @@ test.describe('Can export from electron app', () => {
test(
`Can export using ${method}`,
{ tag: ['@electron', '@skipLocalEngine'] },
async ({ context, page }, testInfo) => {
async ({ context, page, tronApp }, testInfo) => {
if (!tronApp) {
fail()
}
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
@ -516,6 +520,7 @@ test.describe('Can export from electron app', () => {
storage: 'embedded',
presentation: 'pretty',
},
tronApp.projectDirName,
page,
method
)
@ -523,7 +528,7 @@ test.describe('Can export from electron app', () => {
})
const filepath = path.resolve(
getPlaywrightDownloadDir(page),
getPlaywrightDownloadDir(tronApp.projectDirName),
'main.gltf'
)
@ -781,6 +786,7 @@ test(
page.on('console', console.log)
await expect(page.getByText('router-template-slate')).toBeVisible()
await expect(page.getByText('Loading your Projects...')).not.toBeVisible()
await expect(page.getByText('Your Projects')).toBeVisible()
await page.keyboard.press('Delete')
@ -858,7 +864,7 @@ test.describe(`Project management commands`, () => {
test(
`Delete from project page`,
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
async ({ context, page, scene, cmdBar }, testInfo) => {
const projectName = `my_project_to_delete`
await context.folderSetupFn(async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
@ -887,6 +893,8 @@ test.describe(`Project management commands`, () => {
await projectHomeLink.click()
await u.waitForPageLoad()
await scene.connectionEstablished()
await scene.settled(cmdBar)
})
await test.step(`Run delete command via command palette`, async () => {
@ -909,7 +917,7 @@ test.describe(`Project management commands`, () => {
test(
`Rename from home page`,
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
async ({ context, page, homePage }, testInfo) => {
const projectName = `my_project_to_rename`
await context.folderSetupFn(async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
@ -936,6 +944,7 @@ test.describe(`Project management commands`, () => {
await test.step(`Setup`, async () => {
await page.setBodyDimensions({ width: 1200, height: 500 })
page.on('console', console.log)
await homePage.projectsLoaded()
await expect(projectHomeLink).toBeVisible()
})
@ -1682,7 +1691,11 @@ test(
test(
'You can change the root projects directory and nothing is lost',
{ tag: '@electron' },
async ({ context, page, electronApp }, testInfo) => {
async ({ context, page, tronApp, homePage }, testInfo) => {
if (!tronApp) {
fail()
}
await context.folderSetupFn(async (dir) => {
await Promise.all([
fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }),
@ -1712,6 +1725,8 @@ test(
await fsp.rm(newProjectDirName, { recursive: true })
}
await homePage.projectsLoaded()
await test.step('We can change the root project directory', async () => {
// expect to see the project directory settings link
await expect(
@ -1725,7 +1740,7 @@ test(
.locator('section#projectDirectory input')
.inputValue()
const handleFile = electronApp?.evaluate(
const handleFile = tronApp.electron.evaluate(
async ({ dialog }, filePaths) => {
dialog.showOpenDialog = () =>
Promise.resolve({ canceled: false, filePaths })
@ -1741,6 +1756,8 @@ test(
await page.getByTestId('settings-close-button').click()
await homePage.projectsLoaded()
await expect(page.getByText('No Projects found')).toBeVisible()
await createProject({ name: 'project-000', page, returnHome: true })
await expect(
@ -1755,7 +1772,7 @@ test(
await page.getByTestId('project-directory-settings-link').click()
const handleFile = electronApp?.evaluate(
const handleFile = tronApp.electron.evaluate(
async ({ dialog }, filePaths) => {
dialog.showOpenDialog = () =>
Promise.resolve({ canceled: false, filePaths })
@ -1767,6 +1784,7 @@ test(
await page.getByTestId('project-directory-button').click()
await handleFile
await homePage.projectsLoaded()
await expect(page.locator('section#projectDirectory input')).toHaveValue(
originalProjectDirName
)
@ -2000,8 +2018,8 @@ test(
test(
'Settings persist across restarts',
{ tag: '@electron', cleanProjectDir: true },
async ({ page }, testInfo) => {
{ tag: '@electron' },
async ({ page, scene, cmdBar }, testInfo) => {
await test.step('We can change a user setting like theme', async () => {
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -2014,6 +2032,10 @@ test(
await expect(page.getByTestId('app-theme')).toHaveValue('dark')
await page.getByTestId('app-theme').selectOption('light')
await expect(page.getByTestId('app-theme')).toHaveValue('light')
// Give time to system for writing to a persistent store
await page.waitForTimeout(1000)
})
await test.step('Starting the app again and we can see the same theme', async () => {

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import path from 'path'
import * as fsp from 'fs/promises'
import { getUtils, executorInputPath } from './test-utils'

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import fs from 'node:fs/promises'
import path from 'node:path'
import { HomePageFixture } from './fixtures/homePageFixture'
@ -2153,6 +2154,8 @@ extrude001 = extrude(profile003, length = 5)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await page.waitForTimeout(5000)
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
@ -2165,7 +2168,7 @@ extrude001 = extrude(profile003, length = 5)
await page.waitForTimeout(600)
await editor.expectEditor.toContain(`sketch001 = startSketchOn('XZ')`)
await toolbar.exitSketchBtn.click()
await toolbar.exitSketch()
await editor.expectEditor.not.toContain(`sketch001 = startSketchOn('XZ')`)
@ -2181,6 +2184,8 @@ extrude001 = extrude(profile003, length = 5)
)`
)
await scene.settled(cmdBar)
await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
})
})

View File

@ -31,8 +31,7 @@ test.beforeEach(async ({ page, context }) => {
// Help engine-manager: tear shit down.
test.afterEach(async ({ page }) => {
await page.evaluate(() => {
// @ts-expect-error
window.tearDown()
window.engineCommandManager.tearDown()
})
})
@ -45,7 +44,11 @@ test.setTimeout(60_000)
test.skip(
'exports of each format should work',
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
async ({ page, context, scene, cmdBar }) => {
async ({ page, context, scene, cmdBar, tronApp }) => {
if (!tronApp) {
fail()
}
// FYI this test doesn't work with only engine running locally
// And you will need to have the KittyCAD CLI installed
const u = await getUtils(page)
@ -134,6 +137,7 @@ part001 = startSketchOn('-XZ')
storage: 'ascii',
units: 'in',
},
tronApp.projectDirName,
page
)
)
@ -146,6 +150,7 @@ part001 = startSketchOn('-XZ')
selection: { type: 'default_scene' },
units: 'in',
},
tronApp.projectDirName,
page
)
)
@ -158,6 +163,7 @@ part001 = startSketchOn('-XZ')
selection: { type: 'default_scene' },
units: 'in',
},
tronApp.projectDirName,
page
)
)
@ -170,6 +176,7 @@ part001 = startSketchOn('-XZ')
units: 'in',
selection: { type: 'default_scene' },
},
tronApp.projectDirName,
page
)
)
@ -182,6 +189,7 @@ part001 = startSketchOn('-XZ')
units: 'in',
selection: { type: 'default_scene' },
},
tronApp.projectDirName,
page
)
)
@ -193,6 +201,7 @@ part001 = startSketchOn('-XZ')
coords: sysType,
units: 'in',
},
tronApp.projectDirName,
page
)
)
@ -203,6 +212,7 @@ part001 = startSketchOn('-XZ')
storage: 'embedded',
presentation: 'pretty',
},
tronApp.projectDirName,
page
)
)
@ -213,6 +223,7 @@ part001 = startSketchOn('-XZ')
storage: 'binary',
presentation: 'pretty',
},
tronApp.projectDirName,
page
)
)
@ -223,6 +234,7 @@ part001 = startSketchOn('-XZ')
storage: 'standard',
presentation: 'pretty',
},
tronApp.projectDirName,
page
)
)

View File

@ -84,7 +84,7 @@ test.describe('Test network and connection issues', () => {
'Engine disconnect & reconnect in sketch mode',
{ tag: '@skipLocalEngine' },
async ({ page, homePage }) => {
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
// TODO: Don't skip Mac for these. After `window.engineCommandManager.tearDown` is working in Safari, these should work on webkit
const networkToggle = page.getByTestId('network-toggle')
const u = await getUtils(page)

View File

@ -5,8 +5,9 @@ import {
_electron as electron,
ElectronApplication,
Locator,
Page,
} from '@playwright/test'
import { test, Page } from './zoo-test'
import { test } from './zoo-test'
import { EngineCommand } from 'lang/std/artifactGraph'
import fsp from 'fs/promises'
import fsSync from 'fs'
@ -337,7 +338,7 @@ export const getMovementUtils = (opts: any) => {
async function waitForAuthAndLsp(page: Page) {
const waitForLspPromise = page.waitForEvent('console', {
predicate: async (message) => {
predicate: async (message: any) => {
// it would be better to wait for a message that the kcl lsp has started by looking for the message message.text().includes('[lsp] [window/logMessage]')
// but that doesn't seem to make it to the console for macos/safari :(
if (message.text().includes('start kcl lsp')) {
@ -420,7 +421,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
const overlay = page.locator(locator)
const bbox = await overlay
.boundingBox({ timeout: 5_000 })
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 }))
.then((box: any) => ({ ...box, x: box?.x || 0, y: box?.y || 0 }))
const angle = Number(await overlay.getAttribute('data-overlay-angle'))
const angleXOffset = Math.cos(((angle - 180) * Math.PI) / 180) * px
const angleYOffset = Math.sin(((angle - 180) * Math.PI) / 180) * px
@ -437,7 +438,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
page
.locator(locator)
.boundingBox({ timeout: 5_000 })
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
.then((box: any) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
codeLocator: page.locator('.cm-content'),
crushKclCodeIntoOneLineAndThenMaybeSome: async () => {
const code = await page.locator('.cm-content').innerText()
@ -504,7 +505,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
) => {
if (cdpSession === null) {
// Use a fail safe if we can't simulate disconnect (on Safari)
return page.evaluate('window.tearDown()')
return page.evaluate('window.engineCommandManager.tearDown()')
}
return cdpSession?.send(
@ -631,7 +632,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
panesOpen: async (paneIds: PaneId[]) => {
return test?.step(`Setting ${paneIds} panes to be open`, async () => {
await page.addInitScript(
({ PERSIST_MODELING_CONTEXT, paneIds }) => {
({ PERSIST_MODELING_CONTEXT, paneIds }: any) => {
localStorage.setItem(
PERSIST_MODELING_CONTEXT,
JSON.stringify({ openPanes: paneIds })
@ -722,14 +723,14 @@ export const makeTemplate: (
const PLAYWRIGHT_DOWNLOAD_DIR = 'downloads-during-playwright'
export const getPlaywrightDownloadDir = (page: Page) => {
return path.resolve(page.dir, PLAYWRIGHT_DOWNLOAD_DIR)
export const getPlaywrightDownloadDir = (rootDir: string) => {
return path.resolve(rootDir, PLAYWRIGHT_DOWNLOAD_DIR)
}
const moveDownloadedFileTo = async (page: Page, toLocation: string) => {
const moveDownloadedFileTo = async (rootDir: string, toLocation: string) => {
await fsp.mkdir(path.dirname(toLocation), { recursive: true })
const downloadDir = getPlaywrightDownloadDir(page)
const downloadDir = getPlaywrightDownloadDir(rootDir)
// Expect there to be at least one file
await expect
@ -756,6 +757,7 @@ export interface Paths {
export const doExport = async (
output: Models['OutputFormat_type'],
rootDir: string,
page: Page,
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
): Promise<Paths> => {
@ -836,7 +838,7 @@ export const doExport = async (
// (declared in src/lib/exportSave)
// To remain consistent with our old web tests, we want to move some downloads
// (images) to another directory.
await moveDownloadedFileTo(page, downloadLocation)
await moveDownloadedFileTo(rootDir, downloadLocation)
}
return {
@ -859,12 +861,6 @@ export async function tearDown(page: Page, testInfo: TestInfo) {
downloadThroughput: -1,
uploadThroughput: -1,
})
// It seems it's best to give the browser about 3s to close things
// It's not super reliable but we have no real other choice for now
await page.waitForTimeout(3000)
await testInfo.tronApp?.close()
}
// settingsOverrides may need to be augmented to take more generic items,
@ -936,107 +932,11 @@ let electronApp: ElectronApplication | undefined = undefined
let context: BrowserContext | undefined = undefined
let page: Page | undefined = undefined
export async function setupElectron({
testInfo,
cleanProjectDir = true,
appSettings,
viewport,
}: {
testInfo: TestInfo
folderSetupFn?: (projectDirName: string) => Promise<void>
cleanProjectDir?: boolean
appSettings?: DeepPartial<Settings>
viewport: {
width: number
height: number
}
}): Promise<{
electronApp: ElectronApplication
context: BrowserContext
page: Page
dir: string
}> {
// create or otherwise clear the folder
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
try {
if (fsSync.existsSync(projectDirName) && cleanProjectDir) {
await fsp.rm(projectDirName, { recursive: true })
}
} catch (e) {
console.error(e)
}
if (cleanProjectDir) {
await fsp.mkdir(projectDirName)
}
const options = {
args: ['.', '--no-sandbox'],
env: {
...process.env,
TEST_SETTINGS_FILE_KEY: projectDirName,
IS_PLAYWRIGHT: 'true',
},
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' }
: {}),
...(process.env.PLAYWRIGHT_RECORD_VIDEO
? {
recordVideo: {
dir: testInfo.snapshotPath(),
size: viewport,
},
}
: {}),
}
// Do this once and then reuse window on subsequent calls.
if (!electronApp) {
electronApp = await electron.launch(options)
}
if (!context || !page) {
context = electronApp.context()
page = await electronApp.firstWindow()
context.on('console', console.log)
page.on('console', console.log)
}
if (cleanProjectDir) {
const tempSettingsFilePath = path.join(projectDirName, SETTINGS_FILE_NAME)
const settingsOverrides = settingsToToml(
appSettings
? {
settings: {
...TEST_SETTINGS,
...appSettings,
app: {
...TEST_SETTINGS.app,
project_directory: projectDirName,
...appSettings.app,
},
},
}
: {
settings: {
...TEST_SETTINGS,
app: {
...TEST_SETTINGS.app,
project_directory: projectDirName,
},
},
}
)
await fsp.writeFile(tempSettingsFilePath, settingsOverrides)
}
return { electronApp, page, context, dir: projectDirName }
}
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
// enabled for chrome for now
if (page.context().browser()?.browserType().name() === 'chromium') {
page.on('pageerror', (exception) => {
// No idea wtf exception is
page.on('pageerror', (exception: any) => {
if (isErrorWhitelisted(exception)) {
return
}

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { deg, getUtils, wiggleMove } from './test-utils'
import { LineInputsType } from 'lang/std/sketchcombos'

View File

@ -20,14 +20,20 @@ import { DeepPartial } from 'lib/types'
import { Settings } from '@rust/kcl-lib/bindings/Settings'
test.describe('Testing settings', () => {
test(
'Stored settings are validated and fall back to defaults',
test('Stored settings are validated and fall back to defaults', async ({
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
// Override beforeEach test setup
// with corrupted settings
{
appSettings: TEST_SETTINGS_CORRUPTED as DeepPartial<Settings>,
},
async ({ page, homePage }) => {
await tronApp.cleanProjectDir(
TEST_SETTINGS_CORRUPTED as DeepPartial<Settings>
)
await page.setBodyDimensions({ width: 1200, height: 500 })
// Check the settings were reset
@ -47,8 +53,7 @@ test.describe('Testing settings', () => {
expect(storedSettings.settings?.project?.default_project_name).toBe(
'project-$nnn'
)
}
)
})
// The behavior is actually broken. Parent always takes precedence
test.fixme(
@ -357,8 +362,6 @@ test.describe('Testing settings', () => {
`Load desktop app with no settings file`,
{
tag: '@electron',
// This is what makes no settings file get created
cleanProjectDir: false,
},
async ({ page }, testInfo) => {
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -379,13 +382,17 @@ test.describe('Testing settings', () => {
`Load desktop app with a settings file, but no project directory setting`,
{
tag: '@electron',
appSettings: {
},
async ({ context, page, tronApp }, testInfo) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
theme_color: '259',
},
},
},
async ({ context, page }, testInfo) => {
})
await page.setBodyDimensions({ width: 1200, height: 500 })
// Selectors and constants
@ -405,15 +412,20 @@ test.describe('Testing settings', () => {
'user settings reload on external change, on project and modeling view',
{
tag: '@electron',
appSettings: {
},
async ({ context, page, tronApp }, testInfo) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
app: {
// Doesn't matter what you set it to. It will
// default to 264.5
theme_color: '0',
},
},
},
async ({ context, page }, testInfo) => {
})
const { dir: projectDirName } = await context.folderSetupFn(
async () => {}
)
@ -783,13 +795,20 @@ test.describe('Testing settings', () => {
})
})
test(
`Changing system theme preferences (via media query) should update UI and stream`,
{
test(`Changing system theme preferences (via media query) should update UI and stream`, async ({
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
// Override the settings so that the theme is set to `system`
appSettings: TEST_SETTINGS_DEFAULT_THEME,
},
async ({ page, homePage }) => {
...TEST_SETTINGS_DEFAULT_THEME,
})
const u = await getUtils(page)
// Selectors and constants
@ -829,29 +848,31 @@ test.describe('Testing settings', () => {
.poll(() => streamBackgroundPixelIsColor(darkBackgroundColor))
.toBeLessThan(15)
})
}
)
})
test(
`Turning off "Show debug panel" with debug panel open leaves no phantom panel`,
{
test(`Turning off "Show debug panel" with debug panel open leaves no phantom panel`, async ({
context,
page,
homePage,
tronApp,
}) => {
if (!tronApp) {
fail()
}
await tronApp.cleanProjectDir({
// Override beforeEach test setup
// with debug panel open
// but "show debug panel" set to false
appSettings: {
...TEST_SETTINGS,
app: { ...TEST_SETTINGS.app, show_debug_panel: false },
modeling: { ...TEST_SETTINGS.modeling },
},
},
async ({ context, page, homePage }) => {
})
const u = await getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
'persistModelingContext',
'{"openPanes":["debug"]}'
)
localStorage.setItem('persistModelingContext', '{"openPanes":["debug"]}')
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
@ -903,8 +924,7 @@ test.describe('Testing settings', () => {
await expect(debugPaneButton).not.toBeVisible()
await expect(resizeHandle).not.toBeVisible()
})
}
)
})
test(`Change inline units setting`, async ({
page,

View File

@ -1,4 +1,5 @@
import { test, expect, Page } from './zoo-test'
import { Page } from '@playwright/test'
import { test, expect } from './zoo-test'
import { getUtils, createProject } from './test-utils'
import { join } from 'path'
import fs from 'fs'

View File

@ -35,7 +35,7 @@ test.fixme('Units menu', async ({ page, homePage }) => {
test(
'Successful export shows a success toast',
{ tag: '@skipLocalEngine' },
async ({ page, homePage }) => {
async ({ page, homePage, tronApp }) => {
// FYI this test doesn't work with only engine running locally
// And you will need to have the KittyCAD CLI installed
const u = await getUtils(page)
@ -92,12 +92,17 @@ part001 = startSketchOn('-XZ')
await page.waitForTimeout(1000)
await u.clearAndCloseDebugPanel()
if (!tronApp?.projectDirName) {
fail()
}
await doExport(
{
type: 'gltf',
storage: 'embedded',
presentation: 'pretty',
},
tronApp?.projectDirName,
page
)
}
@ -465,7 +470,7 @@ test('Delete key does not navigate back', async ({ page, homePage }) => {
await expect.poll(() => page.url()).not.toContain('/settings')
})
test('Sketch on face', async ({ page, homePage, scene, cmdBar }) => {
test('Sketch on face', async ({ page, homePage, scene, cmdBar, toolbar }) => {
test.setTimeout(90_000)
const u = await getUtils(page)
await page.addInitScript(async () => {
@ -491,17 +496,12 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(300)
await scene.connectionEstablished()
await scene.settled(cmdBar)
let previousCodeContent = await page.locator('.cm-content').innerText()
await toolbar.startSketchThenCallbackThenWaitUntilReady(async () => {
await u.openAndClearDebugPanel()
await u.doAndWaitForCmd(
() => page.mouse.click(625, 165),
@ -510,6 +510,8 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
)
await page.waitForTimeout(150)
await u.closeDebugPanel()
})
await page.waitForTimeout(300)
const firstClickPosition = [612, 238]
const secondClickPosition = [661, 242]

View File

@ -1,21 +1,11 @@
import {
test as playwrightTestFn,
TestInfo as TestInfoPlaywright,
BrowserContext as BrowserContextPlaywright,
Page as PagePlaywright,
TestDetails as TestDetailsPlaywright,
PlaywrightTestArgs,
PlaywrightTestOptions,
PlaywrightWorkerArgs,
PlaywrightWorkerOptions,
ElectronApplication,
} from '@playwright/test'
/* eslint-disable react-hooks/rules-of-hooks */
import { test as playwrightTestFn, ElectronApplication } from '@playwright/test'
import {
fixtures,
fixturesBasedOnProcessEnvPlatform,
Fixtures,
AuthenticatedTronApp,
AuthenticatedApp,
ElectronZoo,
} from './fixtures/fixtureSetup'
import { Settings } from '@rust/kcl-lib/bindings/Settings'
@ -23,9 +13,6 @@ import { DeepPartial } from 'lib/types'
export { expect } from '@playwright/test'
declare module '@playwright/test' {
interface TestInfo {
tronApp?: AuthenticatedTronApp
}
interface BrowserContext {
folderSetupFn: (
cb: (dir: string) => Promise<void>
@ -41,288 +28,29 @@ declare module '@playwright/test' {
}
}
export type TestInfo = TestInfoPlaywright
export type BrowserContext = BrowserContextPlaywright
export type Page = PagePlaywright
export type TestDetails = TestDetailsPlaywright & {
cleanProjectDir?: boolean
appSettings?: DeepPartial<Settings>
}
// Each worker spawns a new thread, which will spawn its own ElectronZoo.
// So in some sense there is an implicit pool.
// For example, the variable just beneath this text is reused many times
// *for one worker*.
const electronZooInstance = new ElectronZoo()
// Our custom decorated Zoo test object. Makes it easier to add fixtures, and
// switch between web and electron if needed.
const pwTestFnWithFixtures = playwrightTestFn.extend<Fixtures>(fixtures)
// In JavaScript you cannot replace a function's body only (despite functions
// are themselves objects, which you'd expect a body property or something...)
// So we must redefine the function and then re-attach properties.
type PWFunction = (
args: PlaywrightTestArgs &
Fixtures &
PlaywrightWorkerArgs &
PlaywrightTestOptions &
PlaywrightWorkerOptions & {
electronApp?: ElectronApplication
},
testInfo: TestInfo
) => void | Promise<void>
let firstUrl = ''
export const test = (
desc: string,
objOrFn: PWFunction | TestDetails,
fnMaybe?: PWFunction
) => {
const hasTestConf = typeof objOrFn === 'object'
const fn = hasTestConf ? fnMaybe : objOrFn
return pwTestFnWithFixtures(
desc,
hasTestConf ? objOrFn : {},
async (
{
page,
context,
cmdBar,
editor,
toolbar,
scene,
homePage,
request,
playwright,
browser,
acceptDownloads,
bypassCSP,
colorScheme,
clientCertificates,
deviceScaleFactor,
extraHTTPHeaders,
geolocation,
hasTouch,
httpCredentials,
ignoreHTTPSErrors,
isMobile,
javaScriptEnabled,
locale,
offline,
permissions,
proxy,
storageState,
timezoneId,
userAgent,
viewport,
baseURL,
contextOptions,
actionTimeout,
navigationTimeout,
serviceWorkers,
testIdAttribute,
browserName,
defaultBrowserType,
headless,
channel,
launchOptions,
connectOptions,
screenshot,
trace,
video,
},
testInfo
) => {
// To switch to web, use PLATFORM=web environment variable.
// Only use this for debugging, since the playwright tracer is busted
// for electron.
let tronApp
const playwrightTestFnWithFixtures_ = playwrightTestFn.extend<{
tronApp?: ElectronZoo
}>({
tronApp: async ({}, use, testInfo) => {
if (process.env.PLATFORM === 'web') {
tronApp = new AuthenticatedApp(context, page, testInfo)
} else {
tronApp = new AuthenticatedTronApp(context, page, testInfo)
}
const fixtures: Fixtures = { cmdBar, editor, toolbar, scene, homePage }
if (tronApp instanceof AuthenticatedTronApp) {
const options = {
fixtures,
}
if (hasTestConf) {
Object.assign(options, {
appSettings: objOrFn?.appSettings,
cleanProjectDir: objOrFn?.cleanProjectDir,
})
}
await tronApp.initialise(options)
} else {
await tronApp.initialise('')
}
// We need to patch this because addInitScript will bind too late in our
// electron tests, never running. We need to call reload() after each call
// to guarantee it runs.
const oldContextAddInitScript = tronApp.context.addInitScript
tronApp.context.addInitScript = async function (a, b) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldContextAddInitScript.apply(this, [a, b])
await tronApp.page.reload()
}
// No idea why we mix and match page and context's addInitScript but we do
const oldPageAddInitScript = tronApp.page.addInitScript
tronApp.page.addInitScript = async function (a: any, b: any) {
// @ts-ignore pretty sure way out of tsc's type checking capabilities.
// This code works perfectly fine.
await oldPageAddInitScript.apply(this, [a, b])
await tronApp.page.reload()
}
// Create a consistent way to resize the page across electron and web.
// (lee) I had to do everything in the book to make electron change its
// damn window size. I succeeded in making it consistently and reliably
// do it after a whole afternoon.
tronApp.page.setBodyDimensions = async function (dims: {
width: number
height: number
}) {
await tronApp.page.setViewportSize(dims)
if (!(tronApp instanceof AuthenticatedTronApp)) {
await use(undefined)
return
}
await tronApp.electronApp?.evaluateHandle(async ({ app }, dims) => {
// @ts-ignore sorry jon but see comment in main.ts why this is ignored
await app.resizeWindow(dims.width, dims.height)
}, dims)
return tronApp.page.evaluate(
async (dims: { width: number; height: number }) => {
await window.electron.resizeWindow(dims.width, dims.height)
window.document.body.style.width = dims.width + 'px'
window.document.body.style.height = dims.height + 'px'
window.document.documentElement.style.width = dims.width + 'px'
window.document.documentElement.style.height = dims.height + 'px'
await use(electronZooInstance)
},
dims
)
}
})
await tronApp.page.setBodyDimensions(tronApp.viewPortSize)
const test = playwrightTestFnWithFixtures_.extend<Fixtures>(
fixturesBasedOnProcessEnvPlatform
)
// We need to expose this in order for some tests that require folder
// creation. Before they used to do this by their own electronSetup({...})
// calls.
if (tronApp instanceof AuthenticatedTronApp) {
tronApp.context.folderSetupFn = async function (fn) {
return fn(tronApp.dir)
.then(() => tronApp.page.reload())
.then(() => ({
dir: tronApp.dir,
}))
}
}
if (!firstUrl) {
await tronApp.page.getByText('Your Projects').count()
firstUrl = tronApp.page.url()
}
// Due to the app controlling its own window context we need to inject new
// options and context here.
// NOTE TO LEE: Seems to destroy page context when calling an electron loadURL.
// await tronApp.electronApp.evaluate(({ app }) => {
// return app.reuseWindowForTest();
// });
await tronApp.electronApp?.evaluate(({ app }, projectDirName) => {
// @ts-ignore can't declaration merge see main.ts
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
}, tronApp.dir)
// Always start at the root view
await tronApp.page.goto(firstUrl)
// Force a hard reload, destroying the stream and other state
await tronApp.page.reload()
// tsc aint smart enough to know this'll never be undefined
// but I dont blame it, the logic to know is complex
if (fn) {
await fn(
{
context: tronApp.context,
page: tronApp.page,
electronApp:
tronApp instanceof AuthenticatedTronApp
? tronApp.electronApp
: undefined,
...fixtures,
request,
playwright,
browser,
acceptDownloads,
bypassCSP,
colorScheme,
clientCertificates,
deviceScaleFactor,
extraHTTPHeaders,
geolocation,
hasTouch,
httpCredentials,
ignoreHTTPSErrors,
isMobile,
javaScriptEnabled,
locale,
offline,
permissions,
proxy,
storageState,
timezoneId,
userAgent,
viewport,
baseURL,
contextOptions,
actionTimeout,
navigationTimeout,
serviceWorkers,
testIdAttribute,
browserName,
defaultBrowserType,
headless,
channel,
launchOptions,
connectOptions,
screenshot,
trace,
video,
},
testInfo
)
}
testInfo.tronApp =
tronApp instanceof AuthenticatedTronApp ? tronApp : undefined
}
)
}
type ZooTest = typeof test
test.describe = pwTestFnWithFixtures.describe
test.beforeEach = pwTestFnWithFixtures.beforeEach
test.afterEach = pwTestFnWithFixtures.afterEach
test.step = pwTestFnWithFixtures.step
test.skip = pwTestFnWithFixtures.skip
test.setTimeout = pwTestFnWithFixtures.setTimeout
test.fixme = pwTestFnWithFixtures.fixme as unknown as ZooTest
test.only = pwTestFnWithFixtures.only
test.fail = pwTestFnWithFixtures.fail
test.slow = pwTestFnWithFixtures.slow
test.beforeAll = pwTestFnWithFixtures.beforeAll
test.afterAll = pwTestFnWithFixtures.afterAll
test.use = pwTestFnWithFixtures.use
test.expect = pwTestFnWithFixtures.expect
test.extend = pwTestFnWithFixtures.extend
test.info = pwTestFnWithFixtures.info
export { test }

View File

@ -106,7 +106,7 @@
"files:flip-to-nightly:windows": "./scripts/flip-files-to-nightly.ps1",
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
"postinstall": "./node_modules/.bin/electron-rebuild",
"postinstall": "yarn --cwd ./rust/kcl-language-server --modules-folder node_modules install && ./node_modules/.bin/electron-rebuild",
"make:dev": "make dev",
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",

31
rust/Cargo.lock generated
View File

@ -1783,7 +1783,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.48"
version = "0.1.49"
dependencies = [
"anyhow",
"clap",
@ -1794,7 +1794,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.48"
version = "0.1.49"
dependencies = [
"Inflector",
"anyhow",
@ -1813,7 +1813,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.48"
version = "0.1.49"
dependencies = [
"proc-macro2",
"quote",
@ -1822,7 +1822,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.48"
version = "0.2.49"
dependencies = [
"anyhow",
"clap",
@ -1843,7 +1843,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.48"
version = "0.1.49"
dependencies = [
"anyhow",
"clap",
@ -1863,7 +1863,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.48"
version = "0.2.49"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1931,7 +1931,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.48"
version = "0.3.49"
dependencies = [
"anyhow",
"kcl-lib",
@ -1946,7 +1946,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.48"
version = "0.1.49"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -1959,7 +1959,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.48"
version = "0.1.49"
dependencies = [
"anyhow",
"async-trait",
@ -1973,7 +1973,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.48"
version = "0.1.49"
dependencies = [
"bson",
"console_error_panic_hook",
@ -3097,15 +3097,14 @@ checksum = "e6cd655523701785087f69900df39892fb7b9b0721aa67682f571c38c32ac58a"
[[package]]
name = "ring"
version = "0.17.8"
version = "0.17.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
checksum = "70ac5d832aa16abd7d1def883a8545280c20a60f523a370aa3a9617c2b8550ee"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.15",
"libc",
"spin",
"untrusted",
"windows-sys 0.52.0",
]
@ -3560,12 +3559,6 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-bumper"
version = "0.1.48"
version = "0.1.49"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-api"
rust-version = "1.76"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.48"
version = "0.1.49"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-directory-test-macro"
description = "A tool for generating tests from a directory of kcl files"
version = "0.1.48"
version = "0.1.49"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-language-server-release"
version = "0.1.48"
version = "0.1.49"
edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false

View File

@ -72,7 +72,7 @@ impl Build {
}
fn build_client(sh: &Shell, version: &str, release_tag: &str, target: &Target) -> anyhow::Result<()> {
let bundle_path = Path::new("server");
let bundle_path = Path::new("kcl-language-server/server");
sh.create_dir(bundle_path)?;
sh.copy_file(&target.server_path, bundle_path)?;
if let Some(symbols_path) = &target.symbols_path {

View File

@ -2,7 +2,7 @@
name = "kcl-language-server"
description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.48"
version = "0.2.49"
edition = "2021"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -97,7 +97,7 @@ async function getServer(
'You need to manually clone the kcl-lsp repository and ' +
'run `cargo install` to build the language server from sources. ' +
'If you feel that your platform should be supported, please create an issue ' +
'about that [here](https://github.com/kittycad/kcl-lsp/issues) and we ' +
'about that [here](https://github.com/kittycad/modeling-app/issues) and we ' +
'will consider it.'
)
return undefined

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.48"
version = "0.2.49"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,8 +1,15 @@
const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line(end = [0, 10], tag = $thing)
|> line(end = [10, 0])
|> line(end = [0, -10], tag = $thing2)
|> close()
startProfileAt([0, 0], startSketchOn("XY"))
|> xLine(length = 10, tag = $line000)
|> yLine(length = 10, tag = $line001)
|> xLine(endAbsolute = profileStartX(%), tag = $line002)
|> close(tag = $line003)
|> extrude(length = 10)
|> fillet(radius = 0.5, tags = [thing, thing])
|> fillet(
radius = 1,
tags = [
line003,
getNextAdjacentEdge(line000),
getPreviousAdjacentEdge(line001)
],
)

View File

@ -27,11 +27,14 @@ async fn kcl_test_fillet_duplicate_tags() {
let code = kcl_input!("fillet_duplicate_tags");
let result = execute_and_snapshot(code, UnitLength::Mm, None).await;
assert!(result.is_err());
let err = result.expect_err("Code should have failed due to the duplicate edges being filletted");
let err = err.as_kcl_error().unwrap();
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([229, 272, 0])], message: "Duplicate tags are not allowed." }"#,
err.message(),
"The same edge ID is being referenced multiple times, which is not allowed. Please select a different edge"
);
assert_eq!(err.source_ranges().len(), 2);
}
#[tokio::test(flavor = "multi_thread")]
@ -857,7 +860,7 @@ part = rectShape([0, 0], 20, 20)
};
assert_eq!(
err.error.message(),
"This function expected this argument to be of type SketchOrSurface but it's actually of type string (text)"
"This function expected the input argument to be of type SketchOrSurface but it's actually of type string (text)"
);
}
@ -2103,7 +2106,7 @@ async fn kcl_test_better_type_names() {
},
None => todo!(),
};
assert_eq!(err, "This function expected this argument to be of type SolidSet but it's actually of type Sketch. You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`");
assert_eq!(err, "This function expected the input argument to be of type SolidSet but it's actually of type Sketch. You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`");
}
#[tokio::test(flavor = "multi_thread")]

View File

@ -48,6 +48,15 @@ impl ExecErrorWithState {
}
}
impl ExecError {
pub fn as_kcl_error(&self) -> Option<&crate::KclError> {
let ExecError::Kcl(k) = &self else {
return None;
};
Some(&k.error)
}
}
impl From<ExecError> for ExecErrorWithState {
fn from(error: ExecError) -> Self {
Self {

View File

@ -596,12 +596,11 @@ impl ExecutorContext {
self.exec_module_for_result(module_id, exec_state, ExecutionKind::Normal, metadata.source_range)
.await?
.unwrap_or_else(|| {
// The module didn't have a return value. Currently,
// the only way to have a return value is with the final
// statement being an expression statement.
//
// TODO: Make a warning when we support them in the
// execution phase.
exec_state.warn(CompilationError::err(
metadata.source_range,
"Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
));
let mut new_meta = vec![metadata.to_owned()];
new_meta.extend(meta);
KclValue::KclNone {
@ -1187,7 +1186,7 @@ impl Node<CallExpressionKw> {
},
self.into(),
ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
exec_state.mod_local.pipe_value.clone().map(|v| Arg::new(v, callsite)),
);
match ctx.stdlib.get_either(fn_name) {
FunctionKind::Core(func) => {
@ -1349,7 +1348,7 @@ impl Node<CallExpression> {
fn_args,
self.into(),
ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
exec_state.mod_local.pipe_value.clone().map(|v| Arg::new(v, callsite)),
);
let mut return_value = {
// Don't early-return in this block.
@ -2000,7 +1999,11 @@ impl FunctionSource {
args,
source_range,
ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
exec_state
.mod_local
.pipe_value
.clone()
.map(|v| Arg::new(v, source_range)),
);
func(exec_state, args).await.map(Some)

View File

@ -659,7 +659,11 @@ impl KclValue {
args,
source_range,
ctx.clone(),
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
exec_state
.mod_local
.pipe_value
.clone()
.map(|v| Arg::new(v, source_range)),
);
let result = func(exec_state, args).await.map(Some);
exec_state.mut_stack().pop_env();

View File

@ -844,11 +844,23 @@ fn object_property(i: &mut TokenSlice) -> PResult<Node<ObjectProperty>> {
))
.parse_next(i)?;
ignore_whitespace(i);
let expr = expression
let expr = match expression
.context(expected(
"the value which you're setting the property to, e.g. in 'height: 4', the value is 4",
))
.parse_next(i)?;
.parse_next(i)
{
Ok(expr) => expr,
Err(_) => {
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::from(sep),
"This property has a label, but no value. Put some value after the equals sign",
)
.into(),
));
}
};
let result = Node {
start: key.start,
@ -2810,7 +2822,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
ignore_whitespace(i);
#[allow(clippy::large_enum_variant)]
pub enum ArgPlace {
enum ArgPlace {
NonCode(Node<NonCodeNode>),
LabeledArg(LabeledArg),
UnlabeledArg(Expr),
@ -2827,22 +2839,34 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
.parse_next(i)?;
let (args, non_code_nodes): (Vec<_>, BTreeMap<usize, _>) = args.into_iter().enumerate().try_fold(
(Vec::new(), BTreeMap::new()),
|(mut args, mut non_code_nodes), (i, e)| {
|(mut args, mut non_code_nodes), (index, e)| {
match e {
ArgPlace::NonCode(x) => {
non_code_nodes.insert(i, vec![x]);
non_code_nodes.insert(index, vec![x]);
}
ArgPlace::LabeledArg(x) => {
args.push(x);
}
ArgPlace::UnlabeledArg(arg) => {
return Err(ErrMode::Cut(
let followed_by_equals = peek((opt(whitespace), equals)).parse_next(i).is_ok();
let err = if followed_by_equals {
ErrMode::Cut(
CompilationError::fatal(
SourceRange::from(arg),
"This argument has a label, but no value. Put some value after the equals sign",
)
.into(),
)
} else {
ErrMode::Cut(
CompilationError::fatal(
SourceRange::from(arg),
"This argument needs a label, but it doesn't have one",
)
.into(),
));
)
};
return Err(err);
}
}
Ok((args, non_code_nodes))
@ -4678,6 +4702,42 @@ baz = 2
);
}
}
#[test]
fn test_sensible_error_when_missing_rhs_of_kw_arg() {
for (i, program) in ["f(x, y=)"].into_iter().enumerate() {
let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
let err = fn_call_kw.parse(tokens.as_slice()).unwrap_err();
let cause = err.inner().cause.as_ref().unwrap();
assert_eq!(
cause.message, "This argument has a label, but no value. Put some value after the equals sign",
"failed test {i}: {program}"
);
assert_eq!(
cause.source_range.start(),
program.find("y").unwrap(),
"failed test {i}: {program}"
);
}
}
#[test]
fn test_sensible_error_when_missing_rhs_of_obj_property() {
for (i, program) in ["{x = 1, y =}"].into_iter().enumerate() {
let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
let err = object.parse(tokens.as_slice()).unwrap_err();
let cause = err.inner().cause.as_ref().unwrap();
assert_eq!(
cause.message, "This property has a label, but no value. Put some value after the equals sign",
"failed test {i}: {program}"
);
assert_eq!(
cause.source_range.start(),
program.rfind('=').unwrap(),
"failed test {i}: {program}"
);
}
}
}
#[cfg(test)]

View File

@ -159,6 +159,49 @@ impl Args {
})
}
/// Get a labelled keyword arg, check it's an array, and return all items in the array
/// plus their source range.
pub(crate) fn kw_arg_array_and_source<'a, T>(&'a self, label: &str) -> Result<Vec<(T, SourceRange)>, KclError>
where
T: FromKclValue<'a>,
{
let Some(arg) = self.kw_args.labeled.get(label) else {
let err = KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a keyword argument '{label}'"),
});
return Err(err);
};
let Some(array) = arg.value.as_array() else {
let err = KclError::Semantic(KclErrorDetails {
source_ranges: vec![arg.source_range],
message: format!(
"Expected an array of {} but found {}",
type_name::<T>(),
arg.value.human_friendly_type()
),
});
return Err(err);
};
array
.iter()
.map(|item| {
let source = SourceRange::from(item);
let val = FromKclValue::from_kcl_val(item).ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message: format!(
"Expected a {} but found {}",
type_name::<T>(),
arg.value.human_friendly_type()
),
})
})?;
Ok((val, source))
})
.collect::<Result<Vec<_>, _>>()
}
/// Get the unlabeled keyword argument. If not set, returns None.
pub(crate) fn unlabeled_kw_arg_unconverted(&self) -> Option<&Arg> {
self.kw_args
@ -184,7 +227,7 @@ impl Args {
T::from_kcl_val(&arg.value).ok_or_else(|| {
let expected_type_name = tynm::type_name::<T>();
let actual_type_name = arg.value.human_friendly_type();
let msg_base = format!("This function expected this argument to be of type {expected_type_name} but it's actually of type {actual_type_name}");
let msg_base = format!("This function expected the input argument to be of type {expected_type_name} but it's actually of type {actual_type_name}");
let suggestion = match (expected_type_name.as_str(), actual_type_name) {
("SolidSet", "Sketch") => Some(
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`",

View File

@ -5,7 +5,6 @@ use kcl_derive_docs::stdlib;
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::CutType, ModelingCmd};
use kittycad_modeling_cmds as kcmc;
use super::utils::unique_count;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{ChamferSurface, EdgeCut, ExecState, ExtrudeSurface, GeoMeta, KclValue, Solid},
@ -19,9 +18,11 @@ pub(crate) const DEFAULT_TOLERANCE: f64 = 0.0000001;
pub async fn chamfer(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let solid = args.get_unlabeled_kw_arg("solid")?;
let length = args.get_kw_arg("length")?;
let tags = args.get_kw_arg("tags")?;
let tags = args.kw_arg_array_and_source::<EdgeReference>("tags")?;
let tag = args.get_kw_arg_opt("tag")?;
super::fillet::validate_unique(&tags)?;
let tags: Vec<EdgeReference> = tags.into_iter().map(|item| item.0).collect();
let value = inner_chamfer(solid, length, tags, tag, exec_state, args).await?;
Ok(KclValue::Solid { value })
}
@ -109,15 +110,6 @@ async fn inner_chamfer(
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<Solid>, KclError> {
// Check if tags contains any duplicate values.
let unique_tags = unique_count(tags.clone());
if unique_tags != tags.len() {
return Err(KclError::Type(KclErrorDetails {
message: "Duplicate tags are not allowed.".to_string(),
source_ranges: vec![args.source_range],
}));
}
// If you try and tag multiple edges with a tagged chamfer, we want to return an
// error to the user that they can only tag one edge at a time.
if tag.is_some() && tags.len() > 1 {

View File

@ -1,6 +1,7 @@
//! Standard library fillets.
use anyhow::Result;
use indexmap::IndexMap;
use kcl_derive_docs::stdlib;
use kcmc::{
each_cmd as mcmd, length_unit::LengthUnit, ok_response::OkModelingCmdResponse, shared::CutType,
@ -11,13 +12,13 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::utils::unique_count;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{EdgeCut, ExecState, ExtrudeSurface, FilletSurface, GeoMeta, KclValue, Solid, TagIdentifier},
parsing::ast::types::TagNode,
settings::types::UnitLength,
std::Args,
SourceRange,
};
/// A tag or a uuid of an edge.
@ -40,13 +41,39 @@ impl EdgeReference {
}
}
pub(super) fn validate_unique<T: Eq + std::hash::Hash>(tags: &[(T, SourceRange)]) -> Result<(), KclError> {
// Check if tags contains any duplicate values.
let mut tag_counts: IndexMap<&T, Vec<SourceRange>> = Default::default();
for tag in tags {
tag_counts.entry(&tag.0).or_insert(Vec::new()).push(tag.1);
}
let mut duplicate_tags_source = Vec::new();
for (_tag, count) in tag_counts {
if count.len() > 1 {
duplicate_tags_source.extend(count)
}
}
if !duplicate_tags_source.is_empty() {
return Err(KclError::Type(KclErrorDetails {
message: "The same edge ID is being referenced multiple times, which is not allowed. Please select a different edge".to_string(),
source_ranges: duplicate_tags_source,
}));
}
Ok(())
}
/// Create fillets on tagged paths.
pub async fn fillet(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
// Get all args:
let solid = args.get_unlabeled_kw_arg("solid")?;
let radius = args.get_kw_arg("radius")?;
let tolerance = args.get_kw_arg_opt("tolerance")?;
let tags = args.get_kw_arg("tags")?;
let tags = args.kw_arg_array_and_source::<EdgeReference>("tags")?;
let tag = args.get_kw_arg_opt("tag")?;
// Run the function.
validate_unique(&tags)?;
let tags: Vec<EdgeReference> = tags.into_iter().map(|item| item.0).collect();
let value = inner_fillet(solid, radius, tags, tolerance, tag, exec_state, args).await?;
Ok(KclValue::Solid { value })
}
@ -129,15 +156,6 @@ async fn inner_fillet(
exec_state: &mut ExecState,
args: Args,
) -> Result<Box<Solid>, KclError> {
// Check if tags contains any duplicate values.
let unique_tags = unique_count(tags.clone());
if unique_tags != tags.len() {
return Err(KclError::Type(KclErrorDetails {
message: "Duplicate tags are not allowed.".to_string(),
source_ranges: vec![args.source_range],
}));
}
let mut solid = solid.clone();
for edge_tag in tags {
let edge_id = edge_tag.get_engine_id(exec_state, &args)?;
@ -432,3 +450,22 @@ pub(crate) fn default_tolerance(units: &UnitLength) -> f64 {
UnitLength::M => 0.001,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_unique() {
let dup_a = SourceRange::from([1, 3, 0]);
let dup_b = SourceRange::from([10, 30, 0]);
// Two entries are duplicates (abc) with different source ranges.
let tags = vec![("abc", dup_a), ("abc", dup_b), ("def", SourceRange::from([2, 4, 0]))];
let actual = validate_unique(&tags);
// Both the duplicates should show up as errors, with both of the
// source ranges they correspond to.
// But the unique source range 'def' should not.
let expected = vec![dup_a, dup_b];
assert_eq!(actual.err().unwrap().source_ranges(), expected);
}
}

View File

@ -1,4 +1,4 @@
use std::{collections::HashSet, f64::consts::PI};
use std::f64::consts::PI;
use kittycad_modeling_cmds::shared::Angle;
@ -8,16 +8,6 @@ use crate::{
source_range::SourceRange,
};
/// Count the number of unique items in a `Vec` in O(n) time.
pub(crate) fn unique_count<T: Eq + std::hash::Hash>(vec: Vec<T>) -> usize {
// Add to a set.
let mut set = HashSet::with_capacity(vec.len());
for item in vec {
set.insert(item);
}
set.len()
}
/// Get the distance between two points.
pub fn distance(a: Point2d, b: Point2d) -> f64 {
((b.x - a.x).powi(2) + (b.y - a.y).powi(2)).sqrt()
@ -686,11 +676,6 @@ mod get_tangential_arc_to_info_tests {
(num * 1000.0).round() / 1000.0
}
#[test]
fn test_unique_count() {
assert_eq!(unique_count(vec![1, 2, 2, 3, 2]), 3);
}
#[test]
fn test_basic_case() {
let result = get_tangential_arc_to_info(TangentialArcInfoInput {

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed angled_line.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed angled_line.kcl
}
},
"sourceRange": [
0,
0,
270,
289,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed artifact_graph_example_code1.kcl
---
[
@ -125,8 +125,8 @@ description: Operations executed artifact_graph_example_code1.kcl
}
},
"sourceRange": [
0,
0,
298,
332,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed basic_fillet_cube_close_opposite.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed basic_fillet_cube_close_opposite.kcl
}
},
"sourceRange": [
0,
0,
197,
217,
0
]
}
@ -129,8 +129,8 @@ description: Operations executed basic_fillet_cube_close_opposite.kcl
}
},
"sourceRange": [
0,
0,
223,
283,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed basic_fillet_cube_end.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed basic_fillet_cube_end.kcl
}
},
"sourceRange": [
0,
0,
185,
205,
0
]
}
@ -129,8 +129,8 @@ description: Operations executed basic_fillet_cube_end.kcl
}
},
"sourceRange": [
0,
0,
211,
269,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed basic_fillet_cube_next_adjacent.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed basic_fillet_cube_next_adjacent.kcl
}
},
"sourceRange": [
0,
0,
212,
232,
0
]
}
@ -124,8 +124,8 @@ description: Operations executed basic_fillet_cube_next_adjacent.kcl
}
},
"sourceRange": [
0,
0,
238,
294,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed basic_fillet_cube_previous_adjacent.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed basic_fillet_cube_previous_adjacent.kcl
}
},
"sourceRange": [
0,
0,
212,
232,
0
]
}
@ -124,8 +124,8 @@ description: Operations executed basic_fillet_cube_previous_adjacent.kcl
}
},
"sourceRange": [
0,
0,
238,
298,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed basic_fillet_cube_start.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed basic_fillet_cube_start.kcl
}
},
"sourceRange": [
0,
0,
185,
205,
0
]
}
@ -130,8 +130,8 @@ description: Operations executed basic_fillet_cube_start.kcl
}
},
"sourceRange": [
0,
0,
211,
253,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed big_number_angle_to_match_length_x.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed big_number_angle_to_match_length_x.kcl
}
},
"sourceRange": [
0,
0,
183,
203,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed big_number_angle_to_match_length_y.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed big_number_angle_to_match_length_y.kcl
}
},
"sourceRange": [
0,
0,
183,
203,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed circle_three_point.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed circle_three_point.kcl
}
},
"sourceRange": [
0,
0,
104,
124,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed circular_pattern3d_a_pattern.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed circular_pattern3d_a_pattern.kcl
}
},
"sourceRange": [
0,
0,
159,
178,
0
]
}

View File

@ -139,8 +139,8 @@ description: Operations executed cube.kcl
}
},
"sourceRange": [
0,
0,
374,
402,
0
]
}

View File

@ -80,8 +80,8 @@ description: Operations executed cube_with_error.kcl
}
},
"sourceRange": [
0,
0,
366,
390,
0
]
}

View File

@ -87,8 +87,8 @@ description: Operations executed fillet-and-shell.kcl
}
},
"sourceRange": [
0,
0,
1057,
1085,
0
]
}
@ -159,8 +159,8 @@ description: Operations executed fillet-and-shell.kcl
}
},
"sourceRange": [
0,
0,
1091,
1297,
0
]
}
@ -280,8 +280,8 @@ description: Operations executed fillet-and-shell.kcl
}
},
"sourceRange": [
0,
0,
1497,
1521,
0
]
}
@ -404,8 +404,8 @@ description: Operations executed fillet-and-shell.kcl
}
},
"sourceRange": [
0,
0,
1497,
1521,
0
]
}
@ -528,8 +528,8 @@ description: Operations executed fillet-and-shell.kcl
}
},
"sourceRange": [
0,
0,
1497,
1521,
0
]
}
@ -652,8 +652,8 @@ description: Operations executed fillet-and-shell.kcl
}
},
"sourceRange": [
0,
0,
1497,
1521,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed function_sketch.kcl
---
[
@ -80,8 +80,8 @@ description: Operations executed function_sketch.kcl
}
},
"sourceRange": [
0,
0,
183,
202,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed function_sketch_with_position.kcl
---
[
@ -80,8 +80,8 @@ description: Operations executed function_sketch_with_position.kcl
}
},
"sourceRange": [
0,
0,
181,
200,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed helix_ccw.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed helix_ccw.kcl
}
},
"sourceRange": [
0,
0,
77,
97,
0
]
}

View File

@ -125,8 +125,8 @@ description: Operations executed i_shape.kcl
}
},
"sourceRange": [
0,
0,
2510,
2531,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed import_whole.kcl
---
[
@ -64,9 +64,9 @@ description: Operations executed import_whole.kcl
}
},
"sourceRange": [
0,
0,
0
103,
123,
3
]
}
},
@ -124,8 +124,8 @@ description: Operations executed import_whole.kcl
}
},
"sourceRange": [
0,
0,
83,
123,
0
]
}

View File

@ -93,9 +93,9 @@ description: Operations executed 3d-boaty.kcl
}
},
"sourceRange": [
0,
0,
0
1379,
1417,
3
]
}
},
@ -173,9 +173,9 @@ description: Operations executed 3d-boaty.kcl
}
},
"sourceRange": [
0,
0,
0
1455,
1494,
3
]
}
},
@ -428,9 +428,9 @@ description: Operations executed 3d-boaty.kcl
}
},
"sourceRange": [
0,
0,
0
1379,
1417,
3
]
}
},
@ -508,9 +508,9 @@ description: Operations executed 3d-boaty.kcl
}
},
"sourceRange": [
0,
0,
0
1455,
1494,
3
]
}
},
@ -763,9 +763,9 @@ description: Operations executed 3d-boaty.kcl
}
},
"sourceRange": [
0,
0,
0
1379,
1417,
3
]
}
},
@ -843,9 +843,9 @@ description: Operations executed 3d-boaty.kcl
}
},
"sourceRange": [
0,
0,
0
1455,
1494,
3
]
}
},
@ -1104,9 +1104,9 @@ description: Operations executed 3d-boaty.kcl
}
},
"sourceRange": [
0,
0,
0
1949,
1973,
3
]
}
},
@ -1190,9 +1190,9 @@ description: Operations executed 3d-boaty.kcl
}
},
"sourceRange": [
0,
0,
0
2015,
2039,
3
]
}
},
@ -1339,9 +1339,9 @@ description: Operations executed 3d-boaty.kcl
]
},
"sourceRange": [
0,
0,
0
2523,
2547,
3
]
}
},
@ -1485,9 +1485,9 @@ description: Operations executed 3d-boaty.kcl
]
},
"sourceRange": [
0,
0,
0
3047,
3071,
3
]
}
},

View File

@ -118,8 +118,8 @@ description: Operations executed 80-20-rail.kcl
}
},
"sourceRange": [
0,
0,
6006,
6034,
0
]
}
@ -238,8 +238,8 @@ description: Operations executed 80-20-rail.kcl
}
},
"sourceRange": [
0,
0,
6042,
6746,
0
]
}
@ -358,8 +358,8 @@ description: Operations executed 80-20-rail.kcl
}
},
"sourceRange": [
0,
0,
6754,
7457,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed a-parametric-bearing-pillow-block.kcl
---
[
@ -254,8 +254,8 @@ description: Operations executed a-parametric-bearing-pillow-block.kcl
}
},
"sourceRange": [
0,
0,
1902,
1936,
0
]
}
@ -693,8 +693,8 @@ description: Operations executed a-parametric-bearing-pillow-block.kcl
}
},
"sourceRange": [
0,
0,
3383,
3408,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed ball-bearing.kcl
---
[
@ -519,8 +519,8 @@ description: Operations executed ball-bearing.kcl
}
},
"sourceRange": [
0,
0,
1561,
1721,
0
]
}
@ -779,8 +779,8 @@ description: Operations executed ball-bearing.kcl
}
},
"sourceRange": [
0,
0,
2214,
2374,
0
]
}
@ -1027,8 +1027,8 @@ description: Operations executed ball-bearing.kcl
}
},
"sourceRange": [
0,
0,
2718,
2878,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed bracket.kcl
---
[
@ -274,8 +274,8 @@ description: Operations executed bracket.kcl
}
},
"sourceRange": [
0,
0,
1903,
2052,
0
]
}
@ -724,8 +724,8 @@ description: Operations executed bracket.kcl
}
},
"sourceRange": [
0,
0,
3306,
3455,
0
]
}

View File

@ -1510,9 +1510,9 @@ description: Operations executed car-wheel-assembly.kcl
}
},
"sourceRange": [
0,
0,
0
529,
562,
3
]
}
},
@ -1622,9 +1622,9 @@ description: Operations executed car-wheel-assembly.kcl
}
},
"sourceRange": [
0,
0,
0
859,
892,
3
]
}
},
@ -1710,9 +1710,9 @@ description: Operations executed car-wheel-assembly.kcl
]
},
"sourceRange": [
0,
0,
0
1214,
1248,
3
]
}
},
@ -1798,9 +1798,9 @@ description: Operations executed car-wheel-assembly.kcl
]
},
"sourceRange": [
0,
0,
0
1572,
1606,
3
]
}
},
@ -2362,9 +2362,9 @@ description: Operations executed car-wheel-assembly.kcl
}
},
"sourceRange": [
0,
0,
0
4067,
4247,
3
]
}
},
@ -2801,9 +2801,9 @@ description: Operations executed car-wheel-assembly.kcl
}
},
"sourceRange": [
0,
0,
0
4067,
4247,
3
]
}
},
@ -3298,8 +3298,8 @@ description: Operations executed car-wheel-assembly.kcl
}
},
"sourceRange": [
0,
0,
373,
524,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed enclosure.kcl
---
[
@ -130,8 +130,8 @@ description: Operations executed enclosure.kcl
}
},
"sourceRange": [
0,
0,
740,
1021,
0
]
}
@ -190,8 +190,8 @@ description: Operations executed enclosure.kcl
}
},
"sourceRange": [
0,
0,
1100,
1170,
0
]
}
@ -1699,8 +1699,8 @@ description: Operations executed enclosure.kcl
}
},
"sourceRange": [
0,
0,
3601,
3882,
0
]
}
@ -1997,8 +1997,8 @@ description: Operations executed enclosure.kcl
}
},
"sourceRange": [
0,
0,
5327,
5608,
0
]
}

View File

@ -338,8 +338,8 @@ description: Operations executed exhaust-manifold.kcl
}
},
"sourceRange": [
0,
0,
1547,
1570,
0
]
}
@ -682,8 +682,8 @@ description: Operations executed exhaust-manifold.kcl
}
},
"sourceRange": [
0,
0,
1547,
1570,
0
]
}
@ -1026,8 +1026,8 @@ description: Operations executed exhaust-manifold.kcl
}
},
"sourceRange": [
0,
0,
1547,
1570,
0
]
}
@ -1370,8 +1370,8 @@ description: Operations executed exhaust-manifold.kcl
}
},
"sourceRange": [
0,
0,
1547,
1570,
0
]
}
@ -1744,8 +1744,8 @@ description: Operations executed exhaust-manifold.kcl
}
},
"sourceRange": [
0,
0,
3933,
3962,
0
]
}
@ -1808,8 +1808,8 @@ description: Operations executed exhaust-manifold.kcl
}
},
"sourceRange": [
0,
0,
3968,
4101,
0
]
}
@ -1872,8 +1872,8 @@ description: Operations executed exhaust-manifold.kcl
}
},
"sourceRange": [
0,
0,
4107,
4240,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed flange-with-patterns.kcl
---
[
@ -174,8 +174,8 @@ description: Operations executed flange-with-patterns.kcl
}
},
"sourceRange": [
0,
0,
1413,
1444,
0
]
}
@ -461,8 +461,8 @@ description: Operations executed flange-with-patterns.kcl
}
},
"sourceRange": [
0,
0,
1928,
1963,
0
]
}
@ -566,8 +566,8 @@ description: Operations executed flange-with-patterns.kcl
}
},
"sourceRange": [
0,
0,
2230,
2264,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed flange-xy.kcl
---
[
@ -254,8 +254,8 @@ description: Operations executed flange-xy.kcl
}
},
"sourceRange": [
0,
0,
1555,
1586,
0
]
}
@ -552,8 +552,8 @@ description: Operations executed flange-xy.kcl
}
},
"sourceRange": [
0,
0,
2077,
2112,
0
]
}
@ -657,8 +657,8 @@ description: Operations executed flange-xy.kcl
}
},
"sourceRange": [
0,
0,
2379,
2413,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed focusrite-scarlett-mounting-bracket.kcl
---
[
@ -253,8 +253,8 @@ description: Operations executed focusrite-scarlett-mounting-bracket.kcl
}
},
"sourceRange": [
0,
0,
1831,
1865,
0
]
}
@ -325,8 +325,8 @@ description: Operations executed focusrite-scarlett-mounting-bracket.kcl
}
},
"sourceRange": [
0,
0,
1871,
2129,
0
]
}
@ -612,8 +612,8 @@ description: Operations executed focusrite-scarlett-mounting-bracket.kcl
}
},
"sourceRange": [
0,
0,
2875,
2900,
0
]
}
@ -670,8 +670,8 @@ description: Operations executed focusrite-scarlett-mounting-bracket.kcl
}
},
"sourceRange": [
0,
0,
2906,
3050,
0
]
}
@ -779,8 +779,8 @@ description: Operations executed focusrite-scarlett-mounting-bracket.kcl
}
},
"sourceRange": [
0,
0,
3056,
3184,
0
]
}
@ -1066,8 +1066,8 @@ description: Operations executed focusrite-scarlett-mounting-bracket.kcl
}
},
"sourceRange": [
0,
0,
3719,
3744,
0
]
}
@ -1124,8 +1124,8 @@ description: Operations executed focusrite-scarlett-mounting-bracket.kcl
}
},
"sourceRange": [
0,
0,
3750,
3894,
0
]
}
@ -1233,8 +1233,8 @@ description: Operations executed focusrite-scarlett-mounting-bracket.kcl
}
},
"sourceRange": [
0,
0,
3900,
4028,
0
]
}
@ -1476,8 +1476,8 @@ description: Operations executed focusrite-scarlett-mounting-bracket.kcl
}
},
"sourceRange": [
0,
0,
4458,
4486,
0
]
}
@ -1719,8 +1719,8 @@ description: Operations executed focusrite-scarlett-mounting-bracket.kcl
}
},
"sourceRange": [
0,
0,
4706,
4734,
0
]
}

View File

@ -323,8 +323,8 @@ description: Operations executed french-press.kcl
}
},
"sourceRange": [
0,
0,
2157,
2179,
0
]
}
@ -500,8 +500,8 @@ description: Operations executed french-press.kcl
}
},
"sourceRange": [
0,
0,
2185,
2340,
0
]
}
@ -1293,8 +1293,8 @@ description: Operations executed french-press.kcl
}
},
"sourceRange": [
0,
0,
5053,
5092,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed gear-rack.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed gear-rack.kcl
}
},
"sourceRange": [
0,
0,
731,
754,
0
]
}
@ -147,8 +147,8 @@ description: Operations executed gear-rack.kcl
}
},
"sourceRange": [
0,
0,
1279,
1302,
0
]
}
@ -265,8 +265,8 @@ description: Operations executed gear-rack.kcl
}
},
"sourceRange": [
0,
0,
1409,
1508,
0
]
}
@ -332,8 +332,8 @@ description: Operations executed gear-rack.kcl
}
},
"sourceRange": [
0,
0,
1820,
1843,
0
]
}
@ -399,8 +399,8 @@ description: Operations executed gear-rack.kcl
}
},
"sourceRange": [
0,
0,
2157,
2180,
0
]
}

View File

@ -5897,8 +5897,8 @@ description: Operations executed gear.kcl
}
},
"sourceRange": [
0,
0,
1405,
1433,
0
]
}
@ -9802,8 +9802,8 @@ description: Operations executed gear.kcl
}
},
"sourceRange": [
0,
0,
2128,
2156,
0
]
}
@ -9979,8 +9979,8 @@ description: Operations executed gear.kcl
}
},
"sourceRange": [
0,
0,
2162,
2322,
0
]
}
@ -10097,8 +10097,8 @@ description: Operations executed gear.kcl
}
},
"sourceRange": [
0,
0,
3058,
3087,
0
]
}

View File

@ -919,8 +919,8 @@ description: Operations executed gridfinity-baseplate-magnets.kcl
]
},
"sourceRange": [
0,
0,
2243,
2360,
0
]
}
@ -1183,8 +1183,8 @@ description: Operations executed gridfinity-baseplate-magnets.kcl
]
},
"sourceRange": [
0,
0,
2584,
2701,
0
]
}
@ -2008,8 +2008,8 @@ description: Operations executed gridfinity-baseplate-magnets.kcl
]
},
"sourceRange": [
0,
0,
6613,
6730,
0
]
}
@ -2243,8 +2243,8 @@ description: Operations executed gridfinity-baseplate-magnets.kcl
]
},
"sourceRange": [
0,
0,
6933,
7050,
0
]
}

View File

@ -919,8 +919,8 @@ description: Operations executed gridfinity-baseplate.kcl
]
},
"sourceRange": [
0,
0,
2118,
2235,
0
]
}
@ -1183,8 +1183,8 @@ description: Operations executed gridfinity-baseplate.kcl
]
},
"sourceRange": [
0,
0,
2459,
2576,
0
]
}

View File

@ -722,8 +722,8 @@ description: Operations executed gridfinity-bins-stacking-lip.kcl
}
},
"sourceRange": [
0,
0,
2875,
2899,
0
]
}
@ -794,8 +794,8 @@ description: Operations executed gridfinity-bins-stacking-lip.kcl
}
},
"sourceRange": [
0,
0,
2905,
3134,
0
]
}
@ -885,8 +885,8 @@ description: Operations executed gridfinity-bins-stacking-lip.kcl
]
},
"sourceRange": [
0,
0,
3580,
3607,
0
]
}
@ -1137,8 +1137,8 @@ description: Operations executed gridfinity-bins-stacking-lip.kcl
]
},
"sourceRange": [
0,
0,
3813,
3943,
0
]
}
@ -1389,8 +1389,8 @@ description: Operations executed gridfinity-bins-stacking-lip.kcl
]
},
"sourceRange": [
0,
0,
4174,
4304,
0
]
}
@ -1612,8 +1612,8 @@ description: Operations executed gridfinity-bins-stacking-lip.kcl
]
},
"sourceRange": [
0,
0,
4529,
4659,
0
]
}
@ -1715,8 +1715,8 @@ description: Operations executed gridfinity-bins-stacking-lip.kcl
}
},
"sourceRange": [
0,
0,
5002,
5046,
0
]
}
@ -1787,8 +1787,8 @@ description: Operations executed gridfinity-bins-stacking-lip.kcl
}
},
"sourceRange": [
0,
0,
5052,
5284,
0
]
}
@ -1847,8 +1847,8 @@ description: Operations executed gridfinity-bins-stacking-lip.kcl
}
},
"sourceRange": [
0,
0,
5290,
5332,
0
]
}

View File

@ -722,8 +722,8 @@ description: Operations executed gridfinity-bins.kcl
}
},
"sourceRange": [
0,
0,
2618,
2642,
0
]
}
@ -794,8 +794,8 @@ description: Operations executed gridfinity-bins.kcl
}
},
"sourceRange": [
0,
0,
2648,
2877,
0
]
}
@ -885,8 +885,8 @@ description: Operations executed gridfinity-bins.kcl
]
},
"sourceRange": [
0,
0,
3323,
3350,
0
]
}
@ -1137,8 +1137,8 @@ description: Operations executed gridfinity-bins.kcl
]
},
"sourceRange": [
0,
0,
3556,
3686,
0
]
}
@ -1389,8 +1389,8 @@ description: Operations executed gridfinity-bins.kcl
]
},
"sourceRange": [
0,
0,
3917,
4047,
0
]
}
@ -1612,8 +1612,8 @@ description: Operations executed gridfinity-bins.kcl
]
},
"sourceRange": [
0,
0,
4272,
4402,
0
]
}
@ -1715,8 +1715,8 @@ description: Operations executed gridfinity-bins.kcl
}
},
"sourceRange": [
0,
0,
4771,
4815,
0
]
}
@ -1787,8 +1787,8 @@ description: Operations executed gridfinity-bins.kcl
}
},
"sourceRange": [
0,
0,
4821,
5053,
0
]
}
@ -1847,8 +1847,8 @@ description: Operations executed gridfinity-bins.kcl
}
},
"sourceRange": [
0,
0,
5059,
5101,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed hex-nut.kcl
---
[
@ -118,8 +118,8 @@ description: Operations executed hex-nut.kcl
}
},
"sourceRange": [
0,
0,
1038,
1059,
0
]
}

View File

@ -66,8 +66,8 @@ description: Operations executed i-beam.kcl
]
},
"sourceRange": [
0,
0,
652,
680,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed kitt.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
900,
930,
0
]
}
@ -160,8 +160,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -243,8 +243,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
2204,
2235,
0
]
}
@ -339,8 +339,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -438,8 +438,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -537,8 +537,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -636,8 +636,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -719,8 +719,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
3485,
3514,
0
]
}
@ -815,8 +815,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -914,8 +914,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -1013,8 +1013,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -1112,8 +1112,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -1211,8 +1211,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -1310,8 +1310,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -1409,8 +1409,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -1508,8 +1508,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -1607,8 +1607,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -1706,8 +1706,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -1805,8 +1805,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -1904,8 +1904,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -2003,8 +2003,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -2185,8 +2185,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -2370,8 +2370,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -2489,8 +2489,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -2588,8 +2588,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -2687,8 +2687,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -2786,8 +2786,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -2905,8 +2905,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -3004,8 +3004,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -3103,8 +3103,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -3202,8 +3202,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -3305,8 +3305,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -3405,8 +3405,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -3505,8 +3505,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -3605,8 +3605,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -3705,8 +3705,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -3805,8 +3805,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -3905,8 +3905,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -4005,8 +4005,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -4105,8 +4105,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}
@ -4205,8 +4205,8 @@ description: Operations executed kitt.kcl
}
},
"sourceRange": [
0,
0,
449,
472,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed lego.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed lego.kcl
}
},
"sourceRange": [
0,
0,
1780,
1804,
0
]
}
@ -138,8 +138,8 @@ description: Operations executed lego.kcl
}
},
"sourceRange": [
0,
0,
2221,
2252,
0
]
}
@ -262,8 +262,8 @@ description: Operations executed lego.kcl
]
},
"sourceRange": [
0,
0,
2687,
2715,
0
]
}
@ -528,8 +528,8 @@ description: Operations executed lego.kcl
]
},
"sourceRange": [
0,
0,
3197,
3226,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed mounting-plate.kcl
---
[
@ -273,8 +273,8 @@ description: Operations executed mounting-plate.kcl
}
},
"sourceRange": [
0,
0,
1825,
1857,
0
]
}
@ -345,8 +345,8 @@ description: Operations executed mounting-plate.kcl
}
},
"sourceRange": [
0,
0,
1863,
2127,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed multi-axis-robot.kcl
---
[
@ -212,9 +212,9 @@ description: Operations executed multi-axis-robot.kcl
}
},
"sourceRange": [
0,
0,
0
769,
1045,
3
]
}
},
@ -352,9 +352,9 @@ description: Operations executed multi-axis-robot.kcl
}
},
"sourceRange": [
0,
0,
0
1280,
1362,
3
]
}
},
@ -923,9 +923,9 @@ description: Operations executed multi-axis-robot.kcl
}
},
"sourceRange": [
0,
0,
0
323,
406,
4
]
}
},
@ -1340,9 +1340,9 @@ description: Operations executed multi-axis-robot.kcl
}
},
"sourceRange": [
0,
0,
0
1143,
1226,
4
]
}
},
@ -1685,9 +1685,9 @@ description: Operations executed multi-axis-robot.kcl
}
},
"sourceRange": [
0,
0,
0
1996,
2079,
4
]
}
},
@ -2341,9 +2341,9 @@ description: Operations executed multi-axis-robot.kcl
}
},
"sourceRange": [
0,
0,
0
1033,
1116,
5
]
}
},
@ -2519,9 +2519,9 @@ description: Operations executed multi-axis-robot.kcl
}
},
"sourceRange": [
0,
0,
0
1415,
1498,
5
]
}
},
@ -3400,9 +3400,9 @@ description: Operations executed multi-axis-robot.kcl
}
},
"sourceRange": [
0,
0,
0
1299,
1382,
6
]
}
},

View File

@ -247,8 +247,8 @@ description: Operations executed pipe-flange-assembly.kcl
}
},
"sourceRange": [
0,
0,
5504,
5535,
0
]
}
@ -410,8 +410,8 @@ description: Operations executed pipe-flange-assembly.kcl
}
},
"sourceRange": [
0,
0,
5838,
5872,
0
]
}
@ -713,8 +713,8 @@ description: Operations executed pipe-flange-assembly.kcl
}
},
"sourceRange": [
0,
0,
3177,
3198,
0
]
}
@ -893,8 +893,8 @@ description: Operations executed pipe-flange-assembly.kcl
}
},
"sourceRange": [
0,
0,
6049,
6204,
0
]
}
@ -1431,8 +1431,8 @@ description: Operations executed pipe-flange-assembly.kcl
}
},
"sourceRange": [
0,
0,
6407,
6562,
0
]
}
@ -1857,8 +1857,8 @@ description: Operations executed pipe-flange-assembly.kcl
}
},
"sourceRange": [
0,
0,
6781,
6936,
0
]
}
@ -2160,8 +2160,8 @@ description: Operations executed pipe-flange-assembly.kcl
}
},
"sourceRange": [
0,
0,
3177,
3198,
0
]
}
@ -2340,8 +2340,8 @@ description: Operations executed pipe-flange-assembly.kcl
}
},
"sourceRange": [
0,
0,
7426,
7581,
0
]
}
@ -2643,8 +2643,8 @@ description: Operations executed pipe-flange-assembly.kcl
}
},
"sourceRange": [
0,
0,
4236,
4258,
0
]
}
@ -2823,8 +2823,8 @@ description: Operations executed pipe-flange-assembly.kcl
}
},
"sourceRange": [
0,
0,
7778,
7933,
0
]
}

View File

@ -209,8 +209,8 @@ description: Operations executed poopy-shoe.kcl
}
},
"sourceRange": [
0,
0,
1706,
1743,
0
]
}
@ -452,8 +452,8 @@ description: Operations executed poopy-shoe.kcl
}
},
"sourceRange": [
0,
0,
2178,
2209,
0
]
}
@ -532,8 +532,8 @@ description: Operations executed poopy-shoe.kcl
}
},
"sourceRange": [
0,
0,
2907,
2938,
0
]
}
@ -775,8 +775,8 @@ description: Operations executed poopy-shoe.kcl
}
},
"sourceRange": [
0,
0,
3566,
3597,
0
]
}
@ -856,8 +856,8 @@ description: Operations executed poopy-shoe.kcl
}
},
"sourceRange": [
0,
0,
3816,
3847,
0
]
}
@ -930,8 +930,8 @@ description: Operations executed poopy-shoe.kcl
}
},
"sourceRange": [
0,
0,
4043,
4093,
0
]
}
@ -1173,8 +1173,8 @@ description: Operations executed poopy-shoe.kcl
}
},
"sourceRange": [
0,
0,
4597,
4629,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed sheet-metal-bracket.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
1299,
1325,
0
]
}
@ -124,8 +124,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
1331,
1410,
0
]
}
@ -184,8 +184,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
1416,
1502,
0
]
}
@ -244,8 +244,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
1508,
1594,
0
]
}
@ -304,8 +304,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
1600,
1679,
0
]
}
@ -364,8 +364,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
1685,
1771,
0
]
}
@ -424,8 +424,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
1777,
1856,
0
]
}
@ -484,8 +484,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
1862,
1942,
0
]
}
@ -544,8 +544,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
1948,
2035,
0
]
}
@ -687,8 +687,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
2446,
2473,
0
]
}
@ -751,8 +751,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
2479,
2608,
0
]
}
@ -894,8 +894,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
2976,
3003,
0
]
}
@ -958,8 +958,8 @@ description: Operations executed sheet-metal-bracket.kcl
}
},
"sourceRange": [
0,
0,
3009,
3139,
0
]
}

View File

@ -136,9 +136,9 @@ description: Operations executed walkie-talkie.kcl
}
},
"sourceRange": [
0,
0,
0
564,
794,
3
]
}
},
@ -2035,9 +2035,9 @@ description: Operations executed walkie-talkie.kcl
}
},
"sourceRange": [
0,
0,
0
745,
890,
8
]
}
},
@ -2227,9 +2227,9 @@ description: Operations executed walkie-talkie.kcl
}
},
"sourceRange": [
0,
0,
0
745,
890,
8
]
}
},
@ -2419,9 +2419,9 @@ description: Operations executed walkie-talkie.kcl
}
},
"sourceRange": [
0,
0,
0
745,
890,
8
]
}
},
@ -2611,9 +2611,9 @@ description: Operations executed walkie-talkie.kcl
}
},
"sourceRange": [
0,
0,
0
745,
890,
8
]
}
},
@ -2923,9 +2923,9 @@ description: Operations executed walkie-talkie.kcl
}
},
"sourceRange": [
0,
0,
0
891,
1096,
6
]
}
},

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed kittycad_svg.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed kittycad_svg.kcl
}
},
"sourceRange": [
0,
0,
18347,
18366,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed linear_pattern3d_a_pattern.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed linear_pattern3d_a_pattern.kcl
}
},
"sourceRange": [
0,
0,
159,
178,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed mike_stress_test.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed mike_stress_test.kcl
}
},
"sourceRange": [
0,
0,
77102,
77121,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed neg_xz_plane.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed neg_xz_plane.kcl
}
},
"sourceRange": [
0,
0,
151,
174,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed parametric.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed parametric.kcl
}
},
"sourceRange": [
0,
0,
465,
488,
0
]
}

View File

@ -64,8 +64,8 @@ description: Operations executed parametric_with_tan_arc.kcl
}
},
"sourceRange": [
0,
0,
622,
645,
0
]
}

View File

@ -1,5 +1,5 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Operations executed pentagon_fillet_sugar.kcl
---
[
@ -64,8 +64,8 @@ description: Operations executed pentagon_fillet_sugar.kcl
}
},
"sourceRange": [
0,
0,
379,
411,
0
]
}
@ -164,8 +164,8 @@ description: Operations executed pentagon_fillet_sugar.kcl
}
},
"sourceRange": [
0,
0,
612,
640,
0
]
}
@ -229,8 +229,8 @@ description: Operations executed pentagon_fillet_sugar.kcl
}
},
"sourceRange": [
0,
0,
646,
773,
0
]
}
@ -329,8 +329,8 @@ description: Operations executed pentagon_fillet_sugar.kcl
}
},
"sourceRange": [
0,
0,
812,
840,
0
]
}
@ -394,8 +394,8 @@ description: Operations executed pentagon_fillet_sugar.kcl
}
},
"sourceRange": [
0,
0,
846,
973,
0
]
}

View File

@ -93,8 +93,8 @@ description: Operations executed pipe_as_arg.kcl
}
},
"sourceRange": [
0,
0,
367,
391,
0
]
}

View File

@ -209,8 +209,8 @@ description: Operations executed poop_chute.kcl
}
},
"sourceRange": [
0,
0,
1719,
1757,
0
]
}

Some files were not shown because too many files have changed in this diff Show More