Compare commits

...

17 Commits

Author SHA1 Message Date
86ae7a989f initial go
Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

better

Signed-off-by: Jess Frazelle <github@jessfraz.com>

typo

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-14 19:09:38 -07:00
334145f0be fix sketch on face of union (#6949)
* fix sketch on face of union

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* rotate the model

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-14 23:25:12 +00:00
c24073b6ae Sensible parser error when using keyword as arg label (#6948)
Closes <https://github.com/KittyCAD/modeling-app/issues/6924>
2025-05-14 22:29:31 +00:00
078b7f3bf7 fix errors from the wasm side (#6939)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* playwright tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* change order of operations

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* lint

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* Revert "fixups"

This reverts commit c54fd71074.

* fixups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-14 13:20:46 -07:00
3d65676ccb Permit concurrent unit tests (#6938)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-15 07:43:03 +12:00
ce566fb6e5 Accept idents as KW args (#6644)
Support kw arg/local variable shorthand

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-15 07:42:48 +12:00
b23fc9f623 Make the Reset View button do the same view_isometric behavior as load (#6934)
* Make the Reset View button do the same view_isometric behavior as load

Just copying some logic from the EngineStream code to make that button
behave the same way: old initial camera position while in Playwright,
isometric view for normal users.

* Move duplicate code into shared `resetCameraPosition` function

* Fix lints
2025-05-14 19:01:45 +00:00
5c2dfb8e40 Support new sweep flag (#6932)
Closes https://github.com/KittyCAD/engine/issues/3115
2025-05-14 13:54:10 -05:00
0e341d7863 #6202 Save input value before closing settings dialogue (#6931)
* call blur on current input before closing settings dialogue to save value

* separate esc handling is not needed

* lint
2025-05-14 14:16:23 -04:00
6a03ff9596 Stop checking for intermediate export toasts (#6935) 2025-05-14 17:53:44 +00:00
d7bd0c937d Keep test toast messages around for longer (#6930)
* Keep test toast messages around for longer

* Check for at least two locators

I wasn't able to reproduce, but it's possible one stuck around from a previous test.
2025-05-14 12:24:38 -04:00
d3b2483f4f Clear errors when leaving file to avoid seeing previous files errors (#6928)
Clear errors when leaving file to avoid seeing previous files errors when opening new project

Co-authored-by: Jace Browning <jacebrowning@gmail.com>
2025-05-14 17:20:04 +02:00
7838b7c9fd Fix "include settings" setting to have an effect (#6917)
* Fix "include settings" setting to have an effect

I'm not sold on if we should have this setting, but this fixes it for
now. The issue is was that the new callback actor approach was using a
stale version of the settings every time it received an "update" event:
JS closure problems. Now it receives the new settings as an event
payload.

* Update src/machines/settingsMachine.ts

---------

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-05-14 15:14:33 +00:00
130ecf1f88 Shard macOS and Windows e2e tests (#6926) 2025-05-14 14:33:53 +00:00
550d8b3753 #6300 Fix wrong Feature Tree when switching to kcl file with errors (#6922)
* reset lastSuccessfulOperation, variables when switching to new kcl file, add test

* use scene.settled instead of random delay in test
2025-05-14 14:17:22 +00:00
696222a070 Change KCL error messages to display principal type of values (#6906) 2025-05-14 10:04:51 -04:00
edb424988d Restore the native file menu tests (#6279)
* Restore the native file menu tests

* fix: saving off progress

* chore: making progress cleaning up these verbose tests and improving app logic for e2e

* chore: rewriting tests

* fix: reworking application logic for file menu in the scene and e2e scene file menu test

* chore: updating more e2e tests

* fix: updated all the tests, auto fixers

* fix: trying to improve tests within E2E, they aren't failing locally even with --repeat-each=10

* fix: application logic has a bug that you can navigate instantly but the scroll to view code will not trigger which breaks end to end tests

* fix: improving E2E tests

* fix: fixing clipboard typo

* fix: porting test() for each native file menu to a test.step to speed it up

* fix: auto fixes and console log helper function for playwright runtimes

* fix: more cleanup

* fix: trying to fix these...

* fix: got the tests working

* fix: addressing PR comments

* fix: trying to stablize the tests

* fix: auto fixes

* fix: trying to make it the command name and not arg? could be a source of race condition if the input is not written fast enough?

* fix: maybe because this close locator was running too quickly?

* fix: panic timeout, classic

* fix: these are gone

* fix: shorter waits

---------

Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-05-14 09:06:29 -04:00
100 changed files with 12801 additions and 50219 deletions

View File

@ -234,10 +234,16 @@ jobs:
shardTotal: 8
- os: namespace-profile-macos-8-cores
shardIndex: 1
shardTotal: 1
shardTotal: 2
- os: namespace-profile-macos-8-cores
shardIndex: 2
shardTotal: 2
- os: windows-latest-8-cores
shardIndex: 1
shardTotal: 1
shardTotal: 2
- os: windows-latest-8-cores
shardIndex: 2
shardTotal: 2
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,7 @@ sweep(
path: Sketch | Helix,
sectional?: bool,
tolerance?: number,
relativeTo?: string,
tagStart?: TagDeclarator,
tagEnd?: TagDeclarator,
): [Solid]
@ -30,6 +31,7 @@ You can provide more than one sketch to sweep, and they will all be swept along
| `path` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) or [`Helix`](/docs/kcl-std/types/std-types-Helix) | The path to sweep the sketch along | Yes |
| `sectional` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
| `tolerance` | [`number`](/docs/kcl-std/types/std-types-number) | Tolerance for this operation | No |
| `relativeTo` | [`string`](/docs/kcl-std/types/std-types-string) | What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. Defaults to sketchPlane. | No |
| `tagStart` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the start of the sweep, i.e. the original sketch | No |
| `tagEnd` | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | A named tag for the face at the end of the sweep | No |

View File

@ -158,10 +158,14 @@ extrude001 = extrude(sketch001, length = 5)`
await expect(
page
.getByText(
'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]'
'Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis'
)
.first()
).toBeVisible()
// Make sure ApiError is not on the page.
// This ensures we didn't nest the json
await expect(page.getByText('ApiError')).not.toBeVisible()
})
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({

View File

@ -58,12 +58,6 @@ test(
await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter')
// Look out for the toast message
const exportingToastMessage = page.getByText(`Exporting...`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
await expect(exportingToastMessage).toBeVisible()
await expect(alreadyExportingToastMessage).not.toBeVisible()
// Expect it to succeed
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
@ -72,7 +66,6 @@ test(
const successToastMessage = page.getByText(`Exported successfully`)
await expect(successToastMessage).toBeVisible()
await expect(exportingToastMessage).not.toBeVisible()
// Check for the exported file
const firstFileFullPath = path.resolve(

View File

@ -155,6 +155,12 @@ export class CmdBarFixture {
}
}
closeCmdBar = async () => {
const cmdBarCloseBtn = this.page.getByTestId('command-bar-close-button')
await cmdBarCloseBtn.click()
await expect(this.cmdBarElement).not.toBeVisible()
}
get cmdSearchInput() {
return this.page.getByTestId('cmd-bar-search')
}
@ -298,4 +304,27 @@ export class CmdBarFixture {
`Monitoring text-to-cad API requests. Output will be saved to: ${outputPath}`
)
}
async toBeOpened() {
// Check that the command bar is opened
await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 })
}
async expectArgValue(value: string) {
// Check the placeholder project name exists
const actualArgument = await this.cmdBarElement
.getByTestId('cmd-bar-arg-value')
.inputValue()
const expectedArgument = value
expect(actualArgument).toBe(expectedArgument)
}
async expectCommandName(value: string) {
// Check the placeholder project name exists
const actual = await this.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = value
expect(actual).toBe(expected)
}
}

View File

@ -24,6 +24,7 @@ export class HomePageFixture {
projectTextName!: Locator
sortByDateBtn!: Locator
sortByNameBtn!: Locator
appHeader!: Locator
tutorialBtn!: Locator
constructor(page: Page) {
@ -44,6 +45,7 @@ export class HomePageFixture {
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
this.appHeader = this.page.getByTestId('app-header')
this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
}
@ -125,4 +127,11 @@ export class HomePageFixture {
await this.createAndGoToProject(name)
}
isNativeFileMenuCreated = async () => {
await expect(this.appHeader).toHaveAttribute(
'data-native-file-menu',
'true'
)
}
}

View File

@ -46,6 +46,7 @@ export class SceneFixture {
public networkToggleConnected!: Locator
public engineConnectionsSpinner!: Locator
public startEditSketchBtn!: Locator
public appHeader!: Locator
constructor(page: Page) {
this.page = page
@ -57,6 +58,7 @@ export class SceneFixture {
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))
this.appHeader = this.page.getByTestId('app-header')
}
private _serialiseScene = async (): Promise<SceneSerialised> => {
const camera = await this.getCameraInfo()
@ -280,6 +282,13 @@ export class SceneFixture {
await expect(buttonToTest).toBeVisible()
await buttonToTest.click()
}
isNativeFileMenuCreated = async () => {
await expect(this.appHeader).toHaveAttribute(
'data-native-file-menu',
'true'
)
}
}
function isColourArray(

File diff suppressed because it is too large Load Diff

View File

@ -51,8 +51,11 @@ test.describe('Regression tests', () => {
// the close doesn't work
// when https://github.com/KittyCAD/modeling-app/issues/3268 is closed
// this test will need updating
const crypticErrorText = `ApiError`
const crypticErrorText = `Cannot close a path that is non-planar or with duplicate vertices.
Internal engine error on request`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// Ensure we didn't nest the json.
await expect(page.getByText('ApiError')).not.toBeVisible()
})
test('user should not have to press down twice in cmdbar', async ({
page,
@ -545,7 +548,8 @@ extrude002 = extrude(profile002, length = 150)
expect(alreadyExportingToastMessage).not.toBeVisible(),
])
await expect(successToastMessage).toHaveCount(2)
const count = await successToastMessage.count()
await expect(count).toBeGreaterThanOrEqual(2)
})
})

View File

@ -3496,6 +3496,73 @@ profile001 = startProfile(sketch001, at = [-102.72, 237.44])
).toBeVisible()
})
// Ensure feature tree is not showing previous file's content when switching to a file with KCL errors.
test('Feature tree shows correct sketch count per file', async ({
context,
homePage,
scene,
toolbar,
cmdBar,
page,
}) => {
const u = await getUtils(page)
// Setup project with files.
const GOOD_KCL = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [220.81, 253.8])
|> line(end = [132.84, -151.31])
|> line(end = [25.51, 167.15])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch002 = startSketchOn(XZ)
profile002 = startProfile(sketch002, at = [158.35, -70.82])
|> line(end = [73.9, -152.19])
|> line(end = [85.33, 135.48])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`
const ERROR_KCL = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [127.56, 179.02])
|> line(end = [132.84, -112.6])
|> line(end = [85.33, 234.01])
|> line(enfd = [-137.23, -54.55])`
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, 'multi-file-sketch-test')
await fs.mkdir(projectDir, { recursive: true })
await Promise.all([
fs.writeFile(path.join(projectDir, 'good.kcl'), GOOD_KCL, 'utf-8'),
fs.writeFile(path.join(projectDir, 'error.kcl'), ERROR_KCL, 'utf-8'),
])
})
await page.setBodyDimensions({ width: 1000, height: 800 })
await homePage.openProject('multi-file-sketch-test')
await scene.connectionEstablished()
await scene.settled(cmdBar)
await u.closeDebugPanel()
await toolbar.openFeatureTreePane()
await toolbar.openPane('files')
await toolbar.openFile('good.kcl')
await expect(
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
).toHaveCount(2)
await toolbar.openFile('error.kcl')
// Ensure filetree is populated
await scene.settled(cmdBar)
await expect(
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
).toHaveCount(0)
})
test('adding a syntax error, recovers after fixing', async ({
page,
homePage,

View File

@ -834,7 +834,13 @@ test('theme persists', async ({ page, context }) => {
})
test.describe('code color goober', { tag: '@snapshot' }, () => {
test('code color goober', async ({ page, context, scene, cmdBar }) => {
test('code color goober', async ({
page,
context,
scene,
cmdBar,
editor,
}) => {
const u = await getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
@ -879,6 +885,7 @@ sweepSketch = startSketchOn(XY)
context,
scene,
cmdBar,
editor,
}) => {
const u = await getUtils(page)
await context.addInitScript(async () => {

View File

@ -21,6 +21,7 @@ export const token = process.env.token || ''
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup'
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
import { test } from '@e2e/playwright/zoo-test'
@ -1149,3 +1150,77 @@ export function perProjectSettingsToToml(
// eslint-disable-next-line no-restricted-syntax
return TOML.stringify(settings as any)
}
export async function clickElectronNativeMenuById(
tronApp: ElectronZoo,
menuId: string
) {
const clickWasTriggered = await tronApp.electron.evaluate(
async ({ app }, menuId) => {
if (!app || !app.applicationMenu) {
return false
}
const menu = app.applicationMenu.getMenuItemById(menuId)
if (!menu) return false
menu.click()
return true
},
menuId
)
expect(clickWasTriggered).toBe(true)
}
export async function findElectronNativeMenuById(
tronApp: ElectronZoo,
menuId: string
) {
const found = await tronApp.electron.evaluate(async ({ app }, menuId) => {
if (!app || !app.applicationMenu) {
return false
}
const menu = app.applicationMenu.getMenuItemById(menuId)
if (!menu) return false
return true
}, menuId)
expect(found).toBe(true)
}
export async function openSettingsExpectText(page: Page, text: string) {
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the user tab
const actualText = settings.getByText(text)
await expect(actualText).toBeVisible()
}
export async function openSettingsExpectLocator(page: Page, selector: string) {
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the keybindings tab
const settingsLocator = settings.locator(selector)
await expect(settingsLocator).toBeVisible()
}
/**
* A developer helper function to make playwright send all the console logs to stdout
* Call this within your E2E test and pass in the page or the tronApp to get as many
* logs piped to stdout for debugging
*/
export async function enableConsoleLogEverything({
page,
tronApp,
}: { page?: Page; tronApp?: ElectronZoo }) {
page?.on('console', (msg) => {
console.log(`[Page-log]: ${msg.text()}`)
})
tronApp?.electron.on('window', async (electronPage) => {
electronPage.on('console', (msg) => {
console.log(`[Renderer] ${msg.type()}: ${msg.text()}`)
})
})
tronApp?.electron.on('console', (msg) => {
console.log(`[Main] ${msg.type()}: ${msg.text()}`)
})
}

View File

@ -41,6 +41,14 @@ interface LSPRequestMap {
LSP.DefinitionParams,
LSP.Definition | LSP.DefinitionLink[] | null,
]
'textDocument/documentColor': [
LSP.DocumentColorParams,
LSP.ColorInformation[] | null,
]
'textDocument/colorPresentation': [
LSP.ColorPresentationParams,
LSP.ColorPresentation[] | null,
]
}
// Client to server
@ -229,6 +237,22 @@ export class LanguageServerClient {
return await this.request('textDocument/definition', params)
}
async textDocumentDocumentColor(params: LSP.DocumentColorParams) {
const serverCapabilities = this.getServerCapabilities()
if (!serverCapabilities.colorProvider) {
return null
}
return await this.request('textDocument/documentColor', params)
}
async textDocumentColorPresentation(params: LSP.ColorPresentationParams) {
const serverCapabilities = this.getServerCapabilities()
if (!serverCapabilities.colorProvider) {
return null
}
return await this.request('textDocument/colorPresentation', params)
}
attachPlugin(plugin: LanguageServerPlugin) {
this.plugins.push(plugin)
}

View File

@ -21,6 +21,7 @@ export {
lspRenameEvent,
lspSemanticTokensEvent,
lspCodeActionEvent,
lspColorUpdateEvent,
} from './plugin/annotation'
export {
LanguageServerPlugin,

View File

@ -6,6 +6,7 @@ export enum LspAnnotation {
Diagnostics = 'diagnostics',
Rename = 'rename',
CodeAction = 'code-action',
ColorUpdate = 'color-update',
}
const lspEvent = Annotation.define<LspAnnotation>()
@ -14,3 +15,4 @@ export const lspFormatCodeEvent = lspEvent.of(LspAnnotation.FormatCode)
export const lspDiagnosticsEvent = lspEvent.of(LspAnnotation.Diagnostics)
export const lspRenameEvent = lspEvent.of(LspAnnotation.Rename)
export const lspCodeActionEvent = lspEvent.of(LspAnnotation.CodeAction)
export const lspColorUpdateEvent = lspEvent.of(LspAnnotation.ColorUpdate)

View File

@ -0,0 +1,278 @@
import {
StateEffect,
StateField,
type Extension,
type Range,
} from '@codemirror/state'
import {
Decoration,
type DecorationSet,
EditorView,
ViewPlugin,
WidgetType,
type ViewUpdate,
} from '@codemirror/view'
import type { LanguageServerPlugin } from './lsp'
import { lspColorUpdateEvent } from './annotation'
import { isArray } from '../lib/utils'
import { offsetToPos, posToOffset, posToOffsetOrZero } from './util'
import type * as LSP from 'vscode-languageserver-protocol'
/* ------------------------------------------------------------------ */
/* ---------- original helpers / widget / color utilities ---------- */
/* ------------------------------------------------------------------ */
interface PickerState {
from: number
to: number
red: number
green: number
blue: number
alpha: number
}
export interface WidgetOptions extends PickerState {
color: string
}
export type ColorData = Omit<WidgetOptions, 'from' | 'to'>
const pickerState = new WeakMap<HTMLInputElement, PickerState>()
function rgbaToHex(color: LSP.Color): string {
return (
'#' +
[color.red, color.green, color.blue]
.map((c) =>
Math.round(c * 255)
.toString(16)
.padStart(2, '0')
)
.join('')
)
}
function hexToRGBComponents(hex: string): number[] {
const r = hex.slice(1, 3)
const g = hex.slice(3, 5)
const b = hex.slice(5, 7)
return [parseInt(r, 16) / 255, parseInt(g, 16) / 255, parseInt(b, 16) / 255]
}
async function discoverColorsViaLsp(
view: EditorView,
plugin: LanguageServerPlugin
): Promise<WidgetOptions | Array<WidgetOptions> | null> {
const responses = await plugin.requestDocumentColors()
if (!responses) return null
const colors: Array<WidgetOptions> = []
for (const color of responses) {
if (!color.range || !color.color) continue
const { start, end } = color.range
const from = posToOffset(view.state.doc, start)
const to = posToOffset(view.state.doc, end)
if (from == null || to == null) continue
colors.push({
color: rgbaToHex(color.color),
...color.color,
from,
to,
})
}
return colors
}
async function colorPickersDecorations(
view: EditorView,
plugin: LanguageServerPlugin
): Promise<DecorationSet> {
const widgets: Array<Range<Decoration>> = []
const maybe = await discoverColorsViaLsp(view, plugin)
if (!maybe) return Decoration.none
const optionsList = isArray(maybe) ? maybe : [maybe]
for (const wo of optionsList) {
widgets.push(
Decoration.widget({
widget: new ColorPickerWidget(wo),
side: 1,
}).range(wo.from)
)
}
return Decoration.set(widgets)
}
export const wrapperClassName = 'cm-css-color-picker-wrapper'
class ColorPickerWidget extends WidgetType {
private readonly state: PickerState
private readonly color: string
constructor({ color, ...state }: WidgetOptions) {
super()
this.state = state
this.color = color
}
eq(other: ColorPickerWidget) {
return (
other.color === this.color &&
other.state.from === this.state.from &&
other.state.to === this.state.to &&
other.state.alpha === this.state.alpha
)
}
toDOM() {
const picker = document.createElement('input')
pickerState.set(picker, this.state)
picker.type = 'color'
picker.value = this.color
const wrapper = document.createElement('span')
wrapper.appendChild(picker)
wrapper.className = wrapperClassName
return wrapper
}
ignoreEvent() {
return false
}
}
export const colorPickerTheme = EditorView.baseTheme({
[`.${wrapperClassName}`]: {
display: 'inline-block',
outline: '1px solid #eee',
marginRight: '0.6ch',
height: '1em',
width: '1em',
transform: 'translateY(1px)',
},
[`.${wrapperClassName} input[type="color"]`]: {
cursor: 'pointer',
height: '100%',
width: '100%',
padding: 0,
border: 'none',
'&::-webkit-color-swatch-wrapper': { padding: 0 },
'&::-webkit-color-swatch': { border: 'none' },
'&::-moz-color-swatch': { border: 'none' },
},
})
/* ------------------------------------------------------------------ */
/* ------------------- ✅ new state machinery -------------------- */
/* ------------------------------------------------------------------ */
// Effect that carries a fresh DecorationSet
const setColorDecorations = StateEffect.define<DecorationSet>()
// Field that stores the current DecorationSet
const colorDecorationsField = StateField.define<DecorationSet>({
create: () => Decoration.none,
update(value, tr) {
value = value.map(tr.changes)
for (const e of tr.effects) if (e.is(setColorDecorations)) value = e.value
return value
},
provide: (f) => EditorView.decorations.from(f),
})
/* ------------------------------------------------------------------ */
/* ------------------ original ViewPlugin, patched ---------------- */
/* ------------------------------------------------------------------ */
export const makeColorPicker = (plugin: ViewPlugin<LanguageServerPlugin>) =>
ViewPlugin.fromClass(
class ColorPickerViewPlugin {
plugin: LanguageServerPlugin | null
constructor(view: EditorView) {
this.plugin = view.plugin(plugin)
if (!this.plugin) return
// initial async load → dispatch decorations
// eslint-disable-next-line @typescript-eslint/no-floating-promises
colorPickersDecorations(view, this.plugin).then((deco) => {
view.dispatch({ effects: setColorDecorations.of(deco) })
})
}
async update(update: ViewUpdate) {
if (!this.plugin) return
if (!(update.docChanged || update.viewportChanged)) return
const deco = await colorPickersDecorations(update.view, this.plugin)
update.view.dispatch({ effects: setColorDecorations.of(deco) })
}
},
{
eventHandlers: {
change: (e: Event, view: EditorView) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
colorPickerChange(e, view, plugin)
},
},
}
)
/* ------------------------------------------------------------------ */
/* -------------------- unchanged event handler ------------------- */
/* ------------------------------------------------------------------ */
async function colorPickerChange(
e: Event,
view: EditorView,
plugin: ViewPlugin<LanguageServerPlugin>
): Promise<boolean> {
const value = view.plugin(plugin)
if (!value) return false
const target = e.target as HTMLInputElement
if (
target.nodeName !== 'INPUT' ||
!target.parentElement?.classList.contains(wrapperClassName)
)
return false
const data = pickerState.get(target)!
const converted = target.value + data.alpha
const [red, green, blue] = hexToRGBComponents(converted)
const responses = await value.requestColorPresentation(
{ red, green, blue, alpha: data.alpha },
{
start: offsetToPos(view.state.doc, data.from),
end: offsetToPos(view.state.doc, data.to),
}
)
if (!responses?.length) return false
for (const resp of responses) {
const changes = resp.textEdit
? {
from: posToOffsetOrZero(view.state.doc, resp.textEdit.range.start),
to: posToOffsetOrZero(view.state.doc, resp.textEdit.range.end),
insert: resp.textEdit.newText,
}
: { from: data.from, to: data.to, insert: resp.label }
view.dispatch({ changes, annotations: [lspColorUpdateEvent] })
}
return true
}
/* ------------------------------------------------------------------ */
/* ------------------------- public API --------------------------- */
/* ------------------------------------------------------------------ */
export default function lspColorsExt(
plugin: ViewPlugin<LanguageServerPlugin>
): Extension {
return [colorDecorationsField, makeColorPicker(plugin), colorPickerTheme]
}

View File

@ -48,6 +48,7 @@ import { isArray } from '../lib/utils'
import lspGoToDefinitionExt from './go-to-definition'
import lspRenameExt from './rename'
import lspSignatureHelpExt from './signature-help'
import lspColorsExt from './colors'
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '')
export const docPathFacet = Facet.define<string, string>({
@ -534,6 +535,37 @@ export class LanguageServerPlugin implements PluginValue {
})
}
async requestDocumentColors() {
if (
!(this.client.getServerCapabilities().colorProvider && this.client.ready)
) {
return
}
const result = await this.client.textDocumentDocumentColor({
textDocument: { uri: this.getDocUri() },
})
if (!result) return
return result
}
async requestColorPresentation(color: LSP.Color, range: LSP.Range) {
if (
!(this.client.getServerCapabilities().colorProvider && this.client.ready)
) {
return
}
const result = await this.client.textDocumentColorPresentation({
textDocument: { uri: this.getDocUri() },
color,
range,
})
if (!result) return
return result
}
async requestRename(
view: EditorView,
{ line, character }: { line: number; character: number }
@ -1318,6 +1350,7 @@ export class LanguageServerPluginSpec
return [
linter(null),
lspAutocompleteExt(plugin),
lspColorsExt(plugin),
lspFormatExt(plugin),
lspGoToDefinitionExt(plugin),
lspHoverExt(plugin),

View File

@ -57,9 +57,9 @@ fn connectorSketch(@plane, start) {
export fn connector(@plane, length) {
connectorSketch(plane, start = [-12, 8])
|> extrude(length = length)
|> extrude(length)
connectorSketch(plane, start = [16, 8])
|> extrude(length = length)
|> extrude(length)
return 0
}
@ -79,7 +79,7 @@ fn seatSlatSketch(@plane) {
export fn seatSlats(@plane, length) {
seatSlatSketch(plane)
|> extrude(length = length)
|> extrude(length)
return 0
}
@ -99,7 +99,7 @@ fn backSlatsSketch(@plane) {
export fn backSlats(@plane, length) {
b = backSlatsSketch(plane)
|> extrude(length = length)
|> extrude(length)
return b
}

View File

@ -15,7 +15,7 @@ holeDia = 4
sketch001 = startSketchOn(XY)
|> startProfile(at = [0, 0])
|> angledLine(angle = 0, length = width, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = length, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|> close()
@ -74,7 +74,7 @@ function001([
sketch003 = startSketchOn(XY)
|> startProfile(at = [width * 1.2, 0])
|> angledLine(angle = 0, length = width, tag = $rectangleSegmentA002)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = length, tag = $rectangleSegmentB002)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length, tag = $rectangleSegmentB002)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC002)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD002)
|> close()

View File

@ -1,12 +1,12 @@
[test-groups]
# If a test uses the engine, we want to limit the number that can run in parallel.
# This way we don't start and stop too many engine instances, putting pressure on our cloud.
uses-engine = { max-threads = 4 }
uses-engine = { max-threads = 32 }
# If a test must run after the engine tests, we want to make sure the engine tests are done first.
after-engine = { max-threads = 12 }
after-engine = { max-threads = 32 }
[profile.default]
slow-timeout = { period = "180s", terminate-after = 1 }
slow-timeout = { period = "280s", terminate-after = 1 }
[profile.ci]
slow-timeout = { period = "280s", terminate-after = 5 }

24
rust/Cargo.lock generated
View File

@ -1815,7 +1815,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.69"
version = "0.1.70"
dependencies = [
"anyhow",
"clap",
@ -1826,7 +1826,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.69"
version = "0.1.70"
dependencies = [
"Inflector",
"anyhow",
@ -1845,7 +1845,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.69"
version = "0.1.70"
dependencies = [
"proc-macro2",
"quote",
@ -1854,7 +1854,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.69"
version = "0.2.70"
dependencies = [
"anyhow",
"clap",
@ -1875,7 +1875,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.69"
version = "0.1.70"
dependencies = [
"anyhow",
"clap",
@ -1895,7 +1895,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.69"
version = "0.2.70"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1971,7 +1971,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.69"
version = "0.3.70"
dependencies = [
"anyhow",
"kcl-lib",
@ -1986,7 +1986,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.69"
version = "0.1.70"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -1999,7 +1999,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.69"
version = "0.1.70"
dependencies = [
"anyhow",
"async-trait",
@ -2013,7 +2013,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.69"
version = "0.1.70"
dependencies = [
"anyhow",
"bson",
@ -2080,9 +2080,9 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds"
version = "0.2.120"
version = "0.2.121"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48b71e06ee5d711d0085864a756fb6a304531246689ea00c6ef5d740670c3701"
checksum = "94ba95c22493d79ec8a1faab963d8903f6de0e373efedf2bc3bb76a0ddbab036"
dependencies = [
"anyhow",
"chrono",

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-bumper"
version = "0.1.69"
version = "0.1.70"
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.69"
version = "0.1.70"
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.69"
version = "0.1.70"
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.69"
version = "0.1.70"
edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false

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.69"
version = "0.2.70"
edition = "2021"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

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

View File

@ -953,36 +953,6 @@ sketch001 = startSketchOn(box, face = END)
assert_out("revolve_on_edge", &result);
}
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_revolve_on_edge_get_edge() {
let code = r#"box = startSketchOn(XY)
|> startProfile(at = [0, 0])
|> line(end = [0, 10])
|> line(end = [10, 0])
|> line(end = [0, -10], tag = $revolveAxis)
|> close()
|> extrude(length = 10)
sketch001 = startSketchOn(box, face = revolveAxis)
|> startProfile(at = [5, 10])
|> line(end = [0, -10])
|> line(end = [2, 0])
|> line(end = [0, 10])
|> close()
|> revolve(axis = revolveAxis, angle = 90)
"#;
let result = execute_and_snapshot(code, None).await;
result.unwrap_err();
//this fails right now, but slightly differently, lets just say its enough for it to fail - mike
//assert_eq!(
// result.err().unwrap().to_string(),
// r#"engine: KclErrorDetails { source_ranges: [SourceRange([346, 390, 0])], message: "Modeling command failed: [ApiError { error_code: InternalEngine, message: \"Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis\" }]" }"#
//);
}
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_revolve_on_face_circle_edge() {
let code = r#"box = startSketchOn(XY)
@ -1249,7 +1219,7 @@ secondSketch = startSketchOn(part001, face = '')
let err = err.as_kcl_error().unwrap();
assert_eq!(
err.message(),
"The arg face was given, but it was the wrong type. It should be type FaceTag but it was string (text)"
"The arg face was given, but it was the wrong type. It should be type FaceTag but it was string"
);
}
@ -1882,7 +1852,7 @@ someFunction('INVALID')
assert!(result.is_err());
assert_eq!(
result.err().unwrap().to_string(),
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([46, 55, 0]), SourceRange([60, 83, 0])], message: "This function expected the input argument to be Solid or Plane but it's actually of type string (text)" }"#
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([46, 55, 0]), SourceRange([60, 83, 0])], message: "This function expected the input argument to be Solid or Plane but it's actually of type string" }"#
);
}

View File

@ -223,6 +223,26 @@ impl EngineConnection {
message: errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
source_ranges: vec![source_range],
})
} else if let Ok(data) =
serde_json::from_str::<Vec<kittycad_modeling_cmds::websocket::FailureWebSocketResponse>>(&err_str)
{
if let Some(data) = data.first() {
// It could also be an array of responses.
KclError::Engine(KclErrorDetails {
message: data
.errors
.iter()
.map(|e| e.message.clone())
.collect::<Vec<_>>()
.join("\n"),
source_ranges: vec![source_range],
})
} else {
KclError::Engine(KclErrorDetails {
message: "Received empty response from engine".into(),
source_ranges: vec![source_range],
})
}
} else {
KclError::Engine(KclErrorDetails {
message: format!("Failed to wait for promise from send modeling command: {:?}", e),

View File

@ -764,7 +764,12 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
WebSocketResponse::Failure(fail) => {
let _request_id = fail.request_id;
Err(KclError::Engine(KclErrorDetails {
message: format!("Modeling command failed: {:?}", fail.errors),
message: fail
.errors
.iter()
.map(|e| e.message.clone())
.collect::<Vec<_>>()
.join("\n"),
source_ranges: vec![source_range],
}))
}
@ -807,7 +812,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
})
})?;
return Err(KclError::Engine(KclErrorDetails {
message: format!("Modeling command failed: {:?}", errors),
message: errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
source_ranges: vec![source_range],
}));
}

View File

@ -913,11 +913,9 @@ impl Node<MemberExpression> {
}),
(being_indexed, _, _) => {
let t = being_indexed.human_friendly_type();
let article = article_for(t);
let article = article_for(&t);
Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Only arrays and objects can be indexed, but you're trying to index {article} {t}"
),
message: format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
source_ranges: vec![self.clone().into()],
}))
}
@ -1313,10 +1311,15 @@ impl Node<CallExpressionKw> {
Some(l) => {
fn_args.insert(l.name.clone(), arg);
}
None => errors.push(arg),
None => {
if let Some(id) = arg_expr.arg.ident_name() {
fn_args.insert(id.to_owned(), arg);
} else {
errors.push(arg);
}
}
}
}
let fn_args = fn_args; // remove mutability
// Evaluate the unlabeled first param, if any exists.
let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
@ -1325,12 +1328,15 @@ impl Node<CallExpressionKw> {
let value = ctx
.execute_expr(arg_expr, exec_state, &metadata, &[], StatementKind::Expression)
.await?;
Some(Arg::new(value, source_range))
let label = arg_expr.ident_name().map(str::to_owned);
Some((label, Arg::new(value, source_range)))
} else {
None
};
let args = Args::new_kw(
let mut args = Args::new_kw(
KwArgs {
unlabeled,
labeled: fn_args,
@ -1349,6 +1355,20 @@ impl Node<CallExpressionKw> {
));
}
let formals = func.args(false);
// If it's possible the input arg was meant to be labelled and we probably don't want to use
// it as the input arg, then treat it as labelled.
if let Some((Some(label), _)) = &args.kw_args.unlabeled {
if (formals.iter().all(|a| a.label_required) || exec_state.pipe_value().is_some())
&& formals.iter().any(|a| &a.name == label && a.label_required)
&& !args.kw_args.labeled.contains_key(label)
{
let (label, arg) = args.kw_args.unlabeled.take().unwrap();
args.kw_args.labeled.insert(label.unwrap(), arg);
}
}
#[cfg(feature = "artifact-graph")]
let op = if func.feature_tree_operation() {
let op_labeled_args = args
@ -1370,7 +1390,6 @@ impl Node<CallExpressionKw> {
None
};
let formals = func.args(false);
for (label, arg) in &args.kw_args.labeled {
match formals.iter().find(|p| &p.name == label) {
Some(p) => {
@ -1698,8 +1717,9 @@ impl Node<ObjectExpression> {
}
}
fn article_for(s: &str) -> &'static str {
if s.starts_with(['a', 'e', 'i', 'o', 'u']) {
fn article_for<S: AsRef<str>>(s: S) -> &'static str {
// '[' is included since it's an array.
if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
"an"
} else {
"a"
@ -1709,10 +1729,9 @@ fn article_for(s: &str) -> &'static str {
fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
v.as_ty_f64().ok_or_else(|| {
let actual_type = v.human_friendly_type();
let article = article_for(actual_type);
KclError::Semantic(KclErrorDetails {
source_ranges: vec![source_range],
message: format!("Expected a number, but found {article} {actual_type}",),
message: format!("Expected a number, but found {actual_type}",),
})
})
}
@ -1867,6 +1886,21 @@ fn type_check_params_kw(
args: &mut KwArgs,
exec_state: &mut ExecState,
) -> Result<(), KclError> {
// If it's possible the input arg was meant to be labelled and we probably don't want to use
// it as the input arg, then treat it as labelled.
if let Some((Some(label), _)) = &args.unlabeled {
if (function_expression.params.iter().all(|p| p.labeled) || exec_state.pipe_value().is_some())
&& function_expression
.params
.iter()
.any(|p| &p.identifier.name == label && p.labeled)
&& !args.labeled.contains_key(label)
{
let (label, arg) = args.unlabeled.take().unwrap();
args.labeled.insert(label.unwrap(), arg);
}
}
for (label, arg) in &mut args.labeled {
match function_expression.params.iter().find(|p| &p.identifier.name == label) {
Some(p) => {
@ -1961,10 +1995,11 @@ fn type_check_params_kw(
if let Some(arg) = &mut args.unlabeled {
if let Some(p) = function_expression.params.iter().find(|p| !p.labeled) {
if let Some(ty) = &p.type_ {
arg.value = arg
arg.1.value = arg
.1
.value
.coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range)
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.1.source_range)
.map_err(|e| KclError::Semantic(e.into()))?,
exec_state,
)
@ -1976,9 +2011,9 @@ fn type_check_params_kw(
.map(|n| format!("`{}`", n))
.unwrap_or_else(|| "this function".to_owned()),
ty.inner,
arg.value.human_friendly_type()
arg.1.value.human_friendly_type()
),
source_ranges: vec![arg.source_range],
source_ranges: vec![arg.1.source_range],
})
})?;
}
@ -2141,10 +2176,11 @@ impl FunctionSource {
if let Some(arg) = &mut args.kw_args.unlabeled {
if let Some(p) = ast.params.iter().find(|p| !p.labeled) {
if let Some(ty) = &p.type_ {
arg.value = arg
arg.1.value = arg
.1
.value
.coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range)
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.1.source_range)
.map_err(|e| KclError::Semantic(e.into()))?,
exec_state,
)
@ -2154,7 +2190,7 @@ impl FunctionSource {
"The input argument of {} requires a value with type `{}`, but found {}",
props.name,
ty.inner,
arg.value.human_friendly_type(),
arg.1.value.human_friendly_type(),
),
source_ranges: vec![callsite],
})
@ -2226,7 +2262,7 @@ impl FunctionSource {
.kw_args
.unlabeled
.as_ref()
.map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
.map(|arg| OpArg::new(OpKclValue::from(&arg.1.value), arg.1.source_range)),
labeled_args: op_labeled_args,
},
source_range: callsite,
@ -2446,19 +2482,23 @@ arr1 = [42]: [number(cm)]
a = 42: string
"#;
let result = parse_execute(program).await;
assert!(result
.unwrap_err()
.to_string()
.contains("could not coerce number value to type string"));
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce number(default units) value to type string"),
"Expected error but found {err:?}"
);
let program = r#"
a = 42: Plane
"#;
let result = parse_execute(program).await;
assert!(result
.unwrap_err()
.to_string()
.contains("could not coerce number value to type Plane"));
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce number(default units) value to type Plane"),
"Expected error but found {err:?}"
);
let program = r#"
arr = [0]: [string]
@ -2467,7 +2507,7 @@ arr = [0]: [string]
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce array (list) value to type [string]"),
.contains("could not coerce [any; 1] value to type [string]"),
"Expected error but found {err:?}"
);
@ -2478,7 +2518,7 @@ mixedArr = [0, "a"]: [number(mm)]
let err = result.unwrap_err();
assert!(
err.to_string()
.contains("could not coerce array (list) value to type [number(mm)]"),
.contains("could not coerce [any; 2] value to type [number(mm)]"),
"Expected error but found {err:?}"
);
}
@ -2663,13 +2703,12 @@ a = foo()
#[tokio::test(flavor = "multi_thread")]
async fn test_sensible_error_when_missing_equals_in_kwarg() {
for (i, call) in ["f(x=1,y)", "f(x=1,3,z)", "f(x=1,y,z=1)", "f(x=1, 3 + 4, z=1)"]
for (i, call) in ["f(x=1,3,0)", "f(x=1,3,z)", "f(x=1,0,z=1)", "f(x=1, 3 + 4, z)"]
.into_iter()
.enumerate()
{
let program = format!(
"fn foo() {{ return 0 }}
y = 42
z = 0
fn f(x, y, z) {{ return 0 }}
{call}"
@ -2689,9 +2728,10 @@ fn f(x, y, z) {{ return 0 }}
#[tokio::test(flavor = "multi_thread")]
async fn default_param_for_unlabeled() {
// Tests that the input param for myExtrude is taken from the pipeline value.
// Tests that the input param for myExtrude is taken from the pipeline value and same-name
// keyword args.
let ast = r#"fn myExtrude(@sk, length) {
return extrude(sk, length = length)
return extrude(sk, length)
}
sketch001 = startSketchOn(XY)
|> circle(center = [0, 0], radius = 93.75)
@ -2701,6 +2741,18 @@ sketch001 = startSketchOn(XY)
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn dont_use_unlabelled_as_input() {
// `length` should be used as the `length` argument to extrude, not the unlabelled input
let ast = r#"length = 10
startSketchOn(XY)
|> circle(center = [0, 0], radius = 93.75)
|> extrude(length)
"#;
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn ascription_in_binop() {
let ast = r#"foo = tan(0): number(rad) - 4deg"#;

View File

@ -280,7 +280,10 @@ impl KclValue {
/// Human readable type name used in error messages. Should not be relied
/// on for program logic.
pub(crate) fn human_friendly_type(&self) -> &'static str {
pub(crate) fn human_friendly_type(&self) -> String {
if let Some(t) = self.principal_type() {
return t.to_string();
}
match self {
KclValue::Uuid { .. } => "Unique ID (uuid)",
KclValue::TagDeclarator(_) => "TagDeclarator",
@ -314,6 +317,7 @@ impl KclValue {
KclValue::Type { .. } => "type",
KclValue::KclNone { .. } => "None",
}
.to_owned()
}
pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {

View File

@ -1910,13 +1910,13 @@ notNull = !myNull
"#;
assert_eq!(
parse_execute(code1).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: number",
"Cannot apply unary operator ! to non-boolean value: number(default units)",
);
let code2 = "notZero = !0";
assert_eq!(
parse_execute(code2).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: number",
"Cannot apply unary operator ! to non-boolean value: number(default units)",
);
let code3 = r#"
@ -1924,7 +1924,7 @@ notEmptyString = !""
"#;
assert_eq!(
parse_execute(code3).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: string (text)",
"Cannot apply unary operator ! to non-boolean value: string",
);
let code4 = r#"
@ -1933,7 +1933,7 @@ notMember = !obj.a
"#;
assert_eq!(
parse_execute(code4).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: number",
"Cannot apply unary operator ! to non-boolean value: number(default units)",
);
let code5 = "
@ -1941,7 +1941,7 @@ a = []
notArray = !a";
assert_eq!(
parse_execute(code5).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: array (list)",
"Cannot apply unary operator ! to non-boolean value: [any; 0]",
);
let code6 = "
@ -1949,7 +1949,7 @@ x = {}
notObject = !x";
assert_eq!(
parse_execute(code6).await.unwrap_err().message(),
"Cannot apply unary operator ! to non-boolean value: object",
"Cannot apply unary operator ! to non-boolean value: { }",
);
let code7 = "
@ -1975,7 +1975,7 @@ notTagDeclarator = !myTagDeclarator";
assert!(
tag_declarator_err
.message()
.starts_with("Cannot apply unary operator ! to non-boolean value: TagDeclarator"),
.starts_with("Cannot apply unary operator ! to non-boolean value: tag"),
"Actual error: {:?}",
tag_declarator_err
);
@ -1989,7 +1989,7 @@ notTagIdentifier = !myTag";
assert!(
tag_identifier_err
.message()
.starts_with("Cannot apply unary operator ! to non-boolean value: TagIdentifier"),
.starts_with("Cannot apply unary operator ! to non-boolean value: tag"),
"Actual error: {:?}",
tag_identifier_err
);

View File

@ -4220,8 +4220,8 @@ sketch001 = startSketchOn(XY)
result,
vec![tower_lsp::lsp_types::ColorInformation {
range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
start: tower_lsp::lsp_types::Position { line: 4, character: 25 },
end: tower_lsp::lsp_types::Position { line: 4, character: 32 },
},
color: tower_lsp::lsp_types::Color {
red: 1.0,
@ -4272,8 +4272,8 @@ sketch001 = startSketchOn(XY)
result,
vec![tower_lsp::lsp_types::ColorInformation {
range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
start: tower_lsp::lsp_types::Position { line: 4, character: 25 },
end: tower_lsp::lsp_types::Position { line: 4, character: 32 },
},
color: tower_lsp::lsp_types::Color {
red: 1.0,
@ -4291,8 +4291,8 @@ sketch001 = startSketchOn(XY)
uri: "file:///test.kcl".try_into().unwrap(),
},
range: tower_lsp::lsp_types::Range {
start: tower_lsp::lsp_types::Position { line: 4, character: 24 },
end: tower_lsp::lsp_types::Position { line: 4, character: 33 },
start: tower_lsp::lsp_types::Position { line: 4, character: 25 },
end: tower_lsp::lsp_types::Position { line: 4, character: 32 },
},
color: tower_lsp::lsp_types::Color {
red: 1.0,

View File

@ -186,7 +186,7 @@ impl<T> Node<T> {
self.comment_start = start;
}
pub fn map_ref<'a, U: 'a>(&'a self, f: fn(&'a T) -> U) -> Node<U> {
pub fn map_ref<'a, U: 'a>(&'a self, f: impl Fn(&'a T) -> U) -> Node<U> {
Node {
inner: f(&self.inner),
start: self.start,
@ -438,8 +438,15 @@ impl Node<Program> {
let add_color = |literal: &Node<Literal>| {
// Check if the string is a color.
if let Some(c) = literal.value.is_color() {
let source_range = literal.as_source_range();
// We subtract 1 from either side because of the "'s in the literal.
let fixed_source_range = SourceRange::new(
source_range.start() + 1,
source_range.end() - 1,
source_range.module_id(),
);
let color = ColorInformation {
range: literal.as_source_range().to_lsp_range(code),
range: fixed_source_range.to_lsp_range(code),
color: tower_lsp::lsp_types::Color {
red: c.r,
green: c.g,
@ -498,7 +505,11 @@ impl Node<Program> {
crate::walk::walk(self, |node: crate::walk::Node<'a>| {
match node {
crate::walk::Node::Literal(literal) => {
if literal.start == pos_start && literal.end == pos_end && literal.value.is_color().is_some() {
// Account for the quotes in the literal.
if (literal.start + 1) == pos_start
&& (literal.end - 1) == pos_end
&& literal.value.is_color().is_some()
{
found.replace(true);
return Ok(true);
}
@ -1187,7 +1198,7 @@ impl Expr {
pub fn ident_name(&self) -> Option<&str> {
match self {
Expr::Name(ident) => Some(&ident.name.name),
Expr::Name(name) => name.local_ident().map(|n| n.inner),
_ => None,
}
}
@ -2371,7 +2382,7 @@ impl Name {
pub fn local_ident(&self) -> Option<Node<&str>> {
if self.path.is_empty() && !self.abs_path {
Some(self.name.map_ref(|n| &n.name))
Some(self.name.map_ref(|n| &*n.name))
} else {
None
}

View File

@ -2729,6 +2729,17 @@ fn ty(i: &mut TokenSlice) -> PResult<Token> {
keyword(i, "type")
}
fn any_keyword(i: &mut TokenSlice) -> PResult<Token> {
any.try_map(|token: Token| match token.token_type {
TokenType::Keyword => Ok(token),
_ => Err(CompilationError::fatal(
token.as_source_range(),
"expected some reserved keyword".to_owned(),
)),
})
.parse_next(i)
}
fn keyword(i: &mut TokenSlice, expected: &str) -> PResult<Token> {
any.try_map(|token: Token| match token.token_type {
TokenType::Keyword if token.value == expected => Ok(token),
@ -3143,12 +3154,14 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
NonCode(Node<NonCodeNode>),
LabeledArg(LabeledArg),
UnlabeledArg(Expr),
Keyword(Token),
}
let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?;
let args: Vec<_> = repeat(
0..,
alt((
terminated(non_code_node.map(ArgPlace::NonCode), whitespace),
terminated(any_keyword.map(ArgPlace::Keyword), whitespace),
terminated(labeled_argument, labeled_arg_separator).map(ArgPlace::LabeledArg),
expression.map(ArgPlace::UnlabeledArg),
)),
@ -3164,6 +3177,18 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult<Node<CallExpressionKw>> {
ArgPlace::LabeledArg(x) => {
args.push(x);
}
ArgPlace::Keyword(kw) => {
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::from(kw.clone()),
format!(
"`{}` is not the name of an argument (it's a reserved keyword)",
kw.value
),
)
.into(),
));
}
ArgPlace::UnlabeledArg(arg) => {
let followed_by_equals = peek((opt(whitespace), equals)).parse_next(i).is_ok();
if followed_by_equals {
@ -5055,6 +5080,30 @@ bar = 1
}
}
#[test]
fn test_sensible_error_when_using_keyword_as_arg_label() {
for (i, program) in ["pow(2, fn = 8)"].into_iter().enumerate() {
let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
let err = match fn_call_kw.parse(tokens.as_slice()) {
Err(e) => e,
Ok(ast) => {
eprintln!("{ast:#?}");
panic!("Expected this to error but it didn't");
}
};
let cause = err.inner().cause.as_ref().unwrap();
assert_eq!(
cause.message, "`fn` is not the name of an argument (it's a reserved keyword)",
"failed test {i}: {program}"
);
assert_eq!(
cause.source_range.start(),
program.find("fn").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() {

View File

@ -1669,7 +1669,6 @@ mod mike_stress_test {
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
#[ignore = "when kurt made the artifact graph lots of commands, this became super slow and sometimes the engine will just die, turn this back on when we can parallelize the simulation tests with snapshots deterministically"]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
@ -2772,6 +2771,7 @@ mod clone_w_fillets {
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
#[ignore] // turn on when https://github.com/KittyCAD/engine/pull/3380 is merged
// Theres also a test in clone.rs you need to turn too
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
@ -3071,3 +3071,45 @@ mod error_inside_fn_also_has_source_range_of_call_site_recursive {
super::execute(TEST_NAME, true).await
}
}
mod error_revolve_on_edge_get_edge {
const TEST_NAME: &str = "error_revolve_on_edge_get_edge";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}
mod sketch_on_face_union {
const TEST_NAME: &str = "sketch_on_face_union";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}

View File

@ -59,7 +59,9 @@ impl Arg {
#[derive(Debug, Clone, Default)]
pub struct KwArgs {
/// Unlabeled keyword args. Currently only the first arg can be unlabeled.
pub unlabeled: Option<Arg>,
/// If the argument was a local variable, then the first element of the tuple is its name
/// which may be used to treat this arg as a labelled arg.
pub unlabeled: Option<(Option<String>, Arg)>,
/// Labeled args.
pub labeled: IndexMap<String, Arg>,
pub errors: Vec<Arg>,
@ -257,14 +259,22 @@ impl Args {
};
let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
let actual_type_name = arg.value.human_friendly_type();
let actual_type = arg.value.principal_type();
let actual_type_name = actual_type
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| arg.value.human_friendly_type().to_owned());
let msg_base = format!(
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
ty.human_friendly_type(),
);
let suggestion = match (ty, actual_type_name) {
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER),
(RuntimeType::Array(t, _), "Sketch") if **t == RuntimeType::Primitive(PrimitiveType::Solid) => {
let suggestion = match (ty, actual_type) {
(RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
(RuntimeType::Array(t, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
if **t == RuntimeType::Primitive(PrimitiveType::Solid) =>
{
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
_ => None,
@ -334,6 +344,7 @@ impl Args {
self.kw_args
.unlabeled
.as_ref()
.map(|(_, a)| a)
.or(self.args.first())
.or(self.pipe_value.as_ref())
}
@ -381,14 +392,22 @@ impl Args {
}))?;
let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
let actual_type_name = arg.value.human_friendly_type();
let actual_type = arg.value.principal_type();
let actual_type_name = actual_type
.as_ref()
.map(|t| t.to_string())
.unwrap_or_else(|| arg.value.human_friendly_type().to_owned());
let msg_base = format!(
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
ty.human_friendly_type(),
);
let suggestion = match (ty, actual_type_name) {
(RuntimeType::Primitive(PrimitiveType::Solid), "Sketch") => Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER),
(RuntimeType::Array(ty, _), "Sketch") if **ty == RuntimeType::Primitive(PrimitiveType::Solid) => {
let suggestion = match (ty, actual_type) {
(RuntimeType::Primitive(PrimitiveType::Solid), Some(RuntimeType::Primitive(PrimitiveType::Sketch))) => {
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
(RuntimeType::Array(ty, _), Some(RuntimeType::Primitive(PrimitiveType::Sketch)))
if **ty == RuntimeType::Primitive(PrimitiveType::Solid) =>
{
Some(ERROR_STRING_SKETCH_TO_SOLID_HELPER)
}
_ => None,

View File

@ -48,7 +48,7 @@ async fn call_map_closure(
ctxt: &ExecutorContext,
) -> Result<KclValue, KclError> {
let kw_args = KwArgs {
unlabeled: Some(Arg::new(input, source_range)),
unlabeled: Some((None, Arg::new(input, source_range))),
labeled: Default::default(),
errors: Vec::new(),
};
@ -104,7 +104,7 @@ async fn call_reduce_closure(
let mut labeled = IndexMap::with_capacity(1);
labeled.insert("accum".to_string(), Arg::new(accum, source_range));
let kw_args = KwArgs {
unlabeled: Some(Arg::new(elem, source_range)),
unlabeled: Some((None, Arg::new(elem, source_range))),
labeled,
errors: Vec::new(),
};

View File

@ -177,25 +177,6 @@ async fn get_old_new_child_map(
exec_state: &mut ExecState,
args: &Args,
) -> Result<HashMap<uuid::Uuid, uuid::Uuid>> {
// Get the new geometries entity ids.
let response = args
.send_modeling_cmd(
exec_state.next_uuid(),
ModelingCmd::from(mcmd::EntityGetAllChildUuids {
entity_id: new_geometry_id,
}),
)
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response:
OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids {
entity_ids: new_entity_ids,
}),
} = response
else {
anyhow::bail!("Expected EntityGetAllChildUuids response, got: {:?}", response);
};
// Get the old geometries entity ids.
let response = args
.send_modeling_cmd(
@ -215,6 +196,25 @@ async fn get_old_new_child_map(
anyhow::bail!("Expected EntityGetAllChildUuids response, got: {:?}", response);
};
// Get the new geometries entity ids.
let response = args
.send_modeling_cmd(
exec_state.next_uuid(),
ModelingCmd::from(mcmd::EntityGetAllChildUuids {
entity_id: new_geometry_id,
}),
)
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response:
OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids {
entity_ids: new_entity_ids,
}),
} = response
else {
anyhow::bail!("Expected EntityGetAllChildUuids response, got: {:?}", response);
};
// Create a map of old entity ids to new entity ids.
Ok(HashMap::from_iter(
old_entity_ids

View File

@ -14,7 +14,7 @@ use super::{args::TyF64, DEFAULT_TOLERANCE};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{types::RuntimeType, ExecState, KclValue, Solid},
std::Args,
std::{patterns::GeometryTrait, Args},
};
/// Union two or more solids into a single solid.
@ -123,7 +123,7 @@ pub(crate) async fn inner_union(
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
solid.set_id(solid_out_id);
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
@ -155,7 +155,7 @@ pub(crate) async fn inner_union(
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
solid.set_id(extra_solid_ids[0]);
new_solids.push(solid.clone());
}
@ -249,7 +249,7 @@ pub(crate) async fn inner_intersect(
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
solid.set_id(solid_out_id);
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
@ -281,7 +281,7 @@ pub(crate) async fn inner_intersect(
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
solid.set_id(extra_solid_ids[0]);
new_solids.push(solid.clone());
}
@ -385,7 +385,7 @@ pub(crate) async fn inner_subtract(
let solid_out_id = exec_state.next_uuid();
let mut solid = solids[0].clone();
solid.id = solid_out_id;
solid.set_id(solid_out_id);
let mut new_solids = vec![solid.clone()];
if args.ctx.no_engine_commands().await {
@ -419,7 +419,7 @@ pub(crate) async fn inner_subtract(
// If we have more solids, set those as well.
if !extra_solid_ids.is_empty() {
solid.id = extra_solid_ids[0];
solid.set_id(extra_solid_ids[0]);
new_solids.push(solid.clone());
}

View File

@ -424,7 +424,7 @@ async fn make_transform<T: GeometryTrait>(
meta: vec![source_range.into()],
};
let kw_args = KwArgs {
unlabeled: Some(Arg::new(repetition_num, source_range)),
unlabeled: Some((None, Arg::new(repetition_num, source_range))),
labeled: Default::default(),
errors: Vec::new(),
};
@ -598,7 +598,7 @@ fn array_to_point2d(
.map(|val| val.as_point2d().unwrap())
}
trait GeometryTrait: Clone {
pub trait GeometryTrait: Clone {
type Set: Into<Vec<Self>> + Clone;
fn id(&self) -> Uuid;
fn original_id(&self) -> Uuid;
@ -608,6 +608,7 @@ trait GeometryTrait: Clone {
source_ranges: Vec<SourceRange>,
exec_state: &mut ExecState,
) -> Result<[TyF64; 3], KclError>;
#[allow(async_fn_in_trait)]
async fn flush_batch(args: &Args, exec_state: &mut ExecState, set: &Self::Set) -> Result<(), KclError>;
}
@ -641,6 +642,8 @@ impl GeometryTrait for Solid {
type Set = Vec<Solid>;
fn set_id(&mut self, id: Uuid) {
self.id = id;
// We need this for in extrude.rs when you sketch on face.
self.sketch.id = id;
}
fn id(&self) -> Uuid {

View File

@ -3,7 +3,7 @@
use anyhow::Result;
use kcl_derive_docs::stdlib;
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd};
use kittycad_modeling_cmds::{self as kcmc};
use kittycad_modeling_cmds::{self as kcmc, shared::RelativeTo};
use schemars::JsonSchema;
use serde::Serialize;
@ -37,11 +37,20 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
)?;
let sectional = args.get_kw_arg_opt("sectional")?;
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
let relative_to: Option<String> = args.get_kw_arg_opt_typed("relativeTo", &RuntimeType::string(), exec_state)?;
let tag_start = args.get_kw_arg_opt("tagStart")?;
let tag_end = args.get_kw_arg_opt("tagEnd")?;
let value = inner_sweep(
sketches, path, sectional, tolerance, tag_start, tag_end, exec_state, args,
sketches,
path,
sectional,
tolerance,
relative_to,
tag_start,
tag_end,
exec_state,
args,
)
.await?;
Ok(value.into())
@ -158,6 +167,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
path = { docs = "The path to sweep the sketch along" },
sectional = { docs = "If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components." },
tolerance = { docs = "Tolerance for this operation" },
relative_to = { docs = "What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. Defaults to sketchPlane."},
tag_start = { docs = "A named tag for the face at the start of the sweep, i.e. the original sketch" },
tag_end = { docs = "A named tag for the face at the end of the sweep" },
},
@ -169,6 +179,7 @@ async fn inner_sweep(
path: SweepPath,
sectional: Option<bool>,
tolerance: Option<TyF64>,
relative_to: Option<String>,
tag_start: Option<TagNode>,
tag_end: Option<TagNode>,
exec_state: &mut ExecState,
@ -178,6 +189,17 @@ async fn inner_sweep(
SweepPath::Sketch(sketch) => sketch.id.into(),
SweepPath::Helix(helix) => helix.value.into(),
};
let relative_to = match relative_to.as_deref() {
Some("sketchPlane") => RelativeTo::SketchPlane,
Some("trajectoryCurve") => RelativeTo::TrajectoryCurve,
Some(_) => {
return Err(KclError::Syntax(crate::errors::KclErrorDetails {
source_ranges: vec![args.source_range],
message: "If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(),
}))
}
None => RelativeTo::default(),
};
let mut solids = Vec::new();
for sketch in &sketches {
@ -189,6 +211,7 @@ async fn inner_sweep(
trajectory,
sectional: sectional.unwrap_or(false),
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
relative_to,
}),
)
.await?;

View File

@ -4,8 +4,7 @@ description: Error from executing argument_error.kcl
---
KCL Semantic error
× semantic: f requires a value with type `fn(any): any`, but found array
│ (list)
× semantic: f requires a value with type `fn(any): any`, but found [any; 2]
╭─[5:1]
4 │
5 │ map(f, f = [0, 1])
@ -16,7 +15,7 @@ KCL Semantic error
╰─▶ KCL Semantic error
× semantic: f requires a value with type `fn(any): any`, but found
array (list)
[any; 2]
╭─[5:12]
4 │
5 │ map(f, f = [0, 1])

View File

@ -5,7 +5,7 @@ description: Error from executing array_elem_pop_empty_fail.kcl
KCL Semantic error
× semantic: The input argument of `std::array::pop` requires a value with
│ type `[any; 1+]`, but found array (list)
│ type `[any; 1+]`, but found [any; 0]
╭─[2:8]
1 │ arr = []
2 │ fail = pop(arr)
@ -16,7 +16,7 @@ KCL Semantic error
╰─▶ KCL Semantic error
× semantic: The input argument of `std::array::pop` requires a value
│ with type `[any; 1+]`, but found array (list)
│ with type `[any; 1+]`, but found [any; 0]
╭─[2:12]
1 │ arr = []
2 │ fail = pop(arr)

View File

@ -4,7 +4,7 @@ description: Error from executing comparisons_multiple.kcl
---
KCL Semantic error
× semantic: Expected a number, but found a boolean (true/false value)
× semantic: Expected a number, but found bool
╭────
1 │ assert(3 == 3 == 3, error = "this should not compile")
· ───┬──

View File

@ -5,7 +5,7 @@ description: Error from executing error_inside_fn_also_has_source_range_of_call_
KCL Semantic error
× semantic: This function expected the input argument to be Solid or Plane
│ but it's actually of type string (text)
│ but it's actually of type string
╭─[3:23]
2 │ fn someNestedFunction(@something2) {
3 │ startSketchOn(something2)
@ -25,7 +25,7 @@ KCL Semantic error
├─▶ KCL Semantic error
× semantic: This function expected the input argument to be Solid or
│ │ Plane but it's actually of type string (text)
│ │ Plane but it's actually of type string
│ ╭─[3:23]
│ 2 │ fn someNestedFunction(@something2) {
│ 3 │ startSketchOn(something2)
@ -37,7 +37,7 @@ KCL Semantic error
╰─▶ KCL Semantic error
× semantic: This function expected the input argument to be Solid or
│ Plane but it's actually of type string (text)
│ Plane but it's actually of type string
╭─[6:5]
5 │
6 │ someNestedFunction(something)

View File

@ -0,0 +1,349 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact commands error_revolve_on_edge_get_edge.kcl
---
[
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "edge_lines_visible",
"hidden": false
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"size": 60.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "enable_sketch_mode",
"entity_id": "[uuid]",
"ortho": false,
"animated": false,
"adjust_camera": false,
"planar_normal": {
"x": 0.0,
"y": 0.0,
"z": 1.0
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": 0.0,
"y": 0.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "start_path"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 0.0,
"y": 10.0,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 10.0,
"y": 0.0,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 0.0,
"y": -10.0,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "close_path",
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "enable_sketch_mode",
"entity_id": "[uuid]",
"ortho": false,
"animated": false,
"adjust_camera": false,
"planar_normal": {
"x": 0.0,
"y": 0.0,
"z": 1.0
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extrude",
"target": "[uuid]",
"distance": 10.0,
"faces": null,
"opposite": "None"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_bring_to_front",
"object_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_adjacency_info",
"object_id": "[uuid]",
"edge_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_extrusion_face_info",
"object_id": "[uuid]",
"edge_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "enable_sketch_mode",
"entity_id": "[uuid]",
"ortho": false,
"animated": false,
"adjust_camera": false,
"planar_normal": null
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": 5.0,
"y": 10.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "start_path"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 0.0,
"y": -10.0,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 2.0,
"y": 0.0,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 0.0,
"y": 10.0,
"z": 0.0
},
"relative": true
}
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "close_path",
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_bring_to_front",
"object_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "revolve_about_edge",
"target": "[uuid]",
"edge_id": "[uuid]",
"angle": {
"unit": "degrees",
"value": 90.0
},
"tolerance": 0.0000001,
"opposite": "None"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_extrusion_face_info",
"object_id": "[uuid]",
"edge_id": "[uuid]"
}
}
]

View File

@ -0,0 +1,937 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of parsing error_revolve_on_edge_get_edge.kcl
---
{
"Ok": {
"body": [
{
"commentStart": 0,
"declaration": {
"commentStart": 0,
"end": 0,
"id": {
"commentStart": 0,
"end": 0,
"name": "box",
"start": 0,
"type": "Identifier"
},
"init": {
"body": [
{
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "startSketchOn",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "XY",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
}
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "at",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
}
],
"end": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "startProfile",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "end",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "10",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
}
],
"end": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "line",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "end",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"raw": "10",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
}
],
"end": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "line",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "end",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
{
"argument": {
"commentStart": 0,
"end": 0,
"raw": "10",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
},
"commentStart": 0,
"end": 0,
"operator": "-",
"start": 0,
"type": "UnaryExpression",
"type": "UnaryExpression"
}
],
"end": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "tag",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"start": 0,
"type": "TagDeclarator",
"type": "TagDeclarator",
"value": "revolveAxis"
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "line",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "close",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "length",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "10",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "extrude",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
}
],
"commentStart": 0,
"end": 0,
"start": 0,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 0,
"type": "VariableDeclarator"
},
"end": 0,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"commentStart": 0,
"declaration": {
"commentStart": 0,
"end": 0,
"id": {
"commentStart": 0,
"end": 0,
"name": "sketch001",
"start": 0,
"type": "Identifier"
},
"init": {
"body": [
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "face",
"start": 0,
"type": "Identifier"
},
"arg": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "revolveAxis",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "startSketchOn",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "box",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
}
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "at",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"raw": "5",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 5.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "10",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
}
],
"end": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "startProfile",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "end",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
{
"argument": {
"commentStart": 0,
"end": 0,
"raw": "10",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
},
"commentStart": 0,
"end": 0,
"operator": "-",
"start": 0,
"type": "UnaryExpression",
"type": "UnaryExpression"
}
],
"end": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "line",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "end",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"raw": "2",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 2.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
}
],
"end": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "line",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "end",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"elements": [
{
"commentStart": 0,
"end": 0,
"raw": "0",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
{
"commentStart": 0,
"end": 0,
"raw": "10",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
}
],
"end": 0,
"start": 0,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "line",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "close",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "axis",
"start": 0,
"type": "Identifier"
},
"arg": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "revolveAxis",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
}
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "angle",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "90",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 90.0,
"suffix": "None"
}
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "revolve",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
}
],
"commentStart": 0,
"end": 0,
"start": 0,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 0,
"type": "VariableDeclarator"
},
"end": 0,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"commentStart": 0,
"end": 0,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"commentStart": 0,
"end": 0,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -0,0 +1,26 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Error from executing error_revolve_on_edge_get_edge.kcl
---
KCL Engine error
× engine: Solid3D revolve failed: sketch profile must lie entirely on one
│ side of the revolution axis
╭─[15:6]
14 │ |> close()
15 │ |> revolve(axis = revolveAxis, angle = 90)
· ───────────────────┬───────────────────┬
· ╰── tests/error_revolve_on_edge_get_edge/input.kcl
· ╰── tests/error_revolve_on_edge_get_edge/input.kcl
╰────
╰─▶ KCL Engine error
× engine: Solid3D revolve failed: sketch profile must lie entirely on
│ one side of the revolution axis
╭─[15:6]
14 │ |> close()
15 │ |> revolve(axis = revolveAxis, angle = 90)
· ───────────────────┬───────────────────
· ╰── tests/error_revolve_on_edge_get_edge/
input.kcl
╰────

View File

@ -0,0 +1,15 @@
box = startSketchOn(XY)
|> startProfile(at = [0, 0])
|> line(end = [0, 10])
|> line(end = [10, 0])
|> line(end = [0, -10], tag = $revolveAxis)
|> close()
|> extrude(length = 10)
sketch001 = startSketchOn(box, face = revolveAxis)
|> startProfile(at = [5, 10])
|> line(end = [0, -10])
|> line(end = [2, 0])
|> line(end = [0, 10])
|> close()
|> revolve(axis = revolveAxis, angle = 90)

View File

@ -0,0 +1,116 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Operations executed error_revolve_on_edge_get_edge.kcl
---
[
{
"labeledArgs": {},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Plane",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
{
"labeledArgs": {
"length": {
"value": {
"type": "Number",
"value": 10.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "extrude",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"face": {
"value": {
"type": "TagIdentifier",
"value": "revolveAxis",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Solid",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"type": "KclStdLibCall",
"name": "revolve",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
},
"labeledArgs": {
"angle": {
"value": {
"type": "Number",
"value": 90.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
},
"axis": {
"value": {
"type": "TagIdentifier",
"value": "revolveAxis",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
"sourceRange": [],
"isError": true
}
]

View File

@ -0,0 +1,19 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing error_revolve_on_edge_get_edge.kcl
---
box = startSketchOn(XY)
|> startProfile(at = [0, 0])
|> line(end = [0, 10])
|> line(end = [10, 0])
|> line(end = [0, -10], tag = $revolveAxis)
|> close()
|> extrude(length = 10)
sketch001 = startSketchOn(box, face = revolveAxis)
|> startProfile(at = [5, 10])
|> line(end = [0, -10])
|> line(end = [2, 0])
|> line(end = [0, 10])
|> close()
|> revolve(axis = revolveAxis, angle = 90)

View File

@ -4,9 +4,8 @@ description: Error from executing execute_engine_error_return.kcl
---
KCL Engine error
× engine: Modeling command failed: [ApiError { error_code: BadRequest,
message: "The path is not closed. Solid2D construction requires a closed
│ path!" }]
× engine: The path is not closed. Solid2D construction requires a closed
path!
╭─[7:6]
6 │ |> line(end = [-11.53311, 2.81559])
7 │ |> extrude(length = 4)

View File

@ -1012,8 +1012,41 @@ description: Artifact commands import_mesh_clone.kcl
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "entity_get_all_child_uuids",
"entity_id": "[uuid]"
"type": "set_object_transform",
"object_id": "[uuid]",
"transforms": [
{
"translate": {
"property": {
"x": 1020.0,
"y": 0.0,
"z": 0.0
},
"set": false,
"is_local": true
},
"rotate_rpy": null,
"rotate_angle_axis": null,
"scale": null
}
]
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_set_material_params_pbr",
"object_id": "[uuid]",
"color": {
"r": 1.0,
"g": 0.0,
"b": 0.0,
"a": 100.0
},
"metalness": 0.5,
"roughness": 0.5,
"ambient_occlusion": 0.0
}
}
]

View File

@ -2,18 +2,9 @@
source: kcl-lib/src/simulation_tests.rs
description: Error from executing import_mesh_clone.kcl
---
KCL Internal error
KCL Engine error
× internal: failed to fix tags and references: engine: KclErrorDetails
│ { source_ranges: [SourceRange([60, 72, 0])], message: "Modeling command
failed: [ApiError { error_code: BadRequest, message: \"Entity type does
│ not currently support transform patterns.\" }, ApiError { error_code:
│ InternalEngine, message: \"Failed to clone entity.\" }, ApiError
│ { error_code: InternalEngine, message: \"Failed to clone entity\" }]" }
╭─[5:10]
4 │
5 │ model2 = clone(model)
· ──────┬─────
· ╰── tests/import_mesh_clone/input.kcl
6 │ |> translate(
╰────
× engine: Modeling command failed: websocket closed early
╭────
13 )
╰────

View File

@ -7,28 +7,25 @@ description: Operations executed import_mesh_clone.kcl
"type": "GroupBegin",
"group": {
"type": "ModuleInstance",
"name": "cube",
"moduleId": 6
"name": "cube.obj",
"moduleId": 0
},
"sourceRange": []
},
{
"type": "GroupEnd"
"type": "KclStdLibCall",
"name": "clone",
"unlabeledArg": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
},
"labeledArgs": {},
"sourceRange": []
},
{
"isError": true,
"labeledArgs": {
"geometry": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
"name": "clone",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": null
"type": "GroupEnd"
}
]

View File

@ -1,11 +1,11 @@
---
source: kcl/src/simulation_tests.rs
source: kcl-lib/src/simulation_tests.rs
description: Error from executing invalid_member_object.kcl
---
KCL Semantic error
× semantic: Only arrays and objects can be indexed, but you're trying to
index a number
× semantic: Only arrays can be indexed, but you're trying to index a
number(default units)
╭─[2:5]
1 │ num = 999
2 │ x = num[3]

View File

@ -5121,7 +5121,8 @@ description: Artifact commands bench.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{
@ -5132,7 +5133,8 @@ description: Artifact commands bench.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
}
]

View File

@ -905,7 +905,8 @@ description: Artifact commands cold-plate.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{

View File

@ -5575,7 +5575,8 @@ description: Artifact commands cpu-cooler.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{
@ -6109,7 +6110,8 @@ description: Artifact commands cpu-cooler.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{
@ -9466,7 +9468,8 @@ description: Artifact commands cpu-cooler.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{
@ -9597,7 +9600,8 @@ description: Artifact commands cpu-cooler.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{
@ -10115,7 +10119,8 @@ description: Artifact commands cpu-cooler.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{
@ -10246,7 +10251,8 @@ description: Artifact commands cpu-cooler.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{

View File

@ -456,13 +456,7 @@ description: Result of parsing enclosure.kcl
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "length",
"start": 0,
"type": "Identifier"
},
"label": null,
"arg": {
"abs_path": false,
"commentStart": 0,
@ -3178,13 +3172,7 @@ description: Result of parsing enclosure.kcl
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "length",
"start": 0,
"type": "Identifier"
},
"label": null,
"arg": {
"abs_path": false,
"commentStart": 0,

View File

@ -28,9 +28,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 485,
"end": 506,
"start": 485,
"commentStart": 476,
"end": 497,
"start": 476,
"type": "TagDeclarator",
"value": "rectangleSegmentB001"
},
@ -41,9 +41,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 608,
"end": 629,
"start": 608,
"commentStart": 599,
"end": 620,
"start": 599,
"type": "TagDeclarator",
"value": "rectangleSegmentC001"
},
@ -54,9 +54,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 699,
"end": 720,
"start": 699,
"commentStart": 690,
"end": 711,
"start": 690,
"type": "TagDeclarator",
"value": "rectangleSegmentD001"
},
@ -102,9 +102,9 @@ description: Variables in memory after executing enclosure.kcl
0.0
],
"tag": {
"commentStart": 485,
"end": 506,
"start": 485,
"commentStart": 476,
"end": 497,
"start": 476,
"type": "TagDeclarator",
"value": "rectangleSegmentB001"
},
@ -127,9 +127,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 608,
"end": 629,
"start": 608,
"commentStart": 599,
"end": 620,
"start": 599,
"type": "TagDeclarator",
"value": "rectangleSegmentC001"
},
@ -152,9 +152,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 699,
"end": 720,
"start": 699,
"commentStart": 690,
"end": 711,
"start": 690,
"type": "TagDeclarator",
"value": "rectangleSegmentD001"
},
@ -354,9 +354,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2445,
"end": 2466,
"start": 2445,
"commentStart": 2436,
"end": 2457,
"start": 2436,
"type": "TagDeclarator",
"value": "rectangleSegmentA002"
},
@ -367,9 +367,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2550,
"end": 2571,
"start": 2550,
"commentStart": 2532,
"end": 2553,
"start": 2532,
"type": "TagDeclarator",
"value": "rectangleSegmentB002"
},
@ -380,9 +380,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2673,
"end": 2694,
"start": 2673,
"commentStart": 2655,
"end": 2676,
"start": 2655,
"type": "TagDeclarator",
"value": "rectangleSegmentC002"
},
@ -393,9 +393,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2764,
"end": 2785,
"start": 2764,
"commentStart": 2746,
"end": 2767,
"start": 2746,
"type": "TagDeclarator",
"value": "rectangleSegmentD002"
},
@ -416,9 +416,9 @@ description: Variables in memory after executing enclosure.kcl
0.0
],
"tag": {
"commentStart": 2445,
"end": 2466,
"start": 2445,
"commentStart": 2436,
"end": 2457,
"start": 2436,
"type": "TagDeclarator",
"value": "rectangleSegmentA002"
},
@ -441,9 +441,9 @@ description: Variables in memory after executing enclosure.kcl
0.0
],
"tag": {
"commentStart": 2550,
"end": 2571,
"start": 2550,
"commentStart": 2532,
"end": 2553,
"start": 2532,
"type": "TagDeclarator",
"value": "rectangleSegmentB002"
},
@ -466,9 +466,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 2673,
"end": 2694,
"start": 2673,
"commentStart": 2655,
"end": 2676,
"start": 2655,
"type": "TagDeclarator",
"value": "rectangleSegmentC002"
},
@ -491,9 +491,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 2764,
"end": 2785,
"start": 2764,
"commentStart": 2746,
"end": 2767,
"start": 2746,
"type": "TagDeclarator",
"value": "rectangleSegmentD002"
},
@ -693,9 +693,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 4147,
"end": 4168,
"start": 4147,
"commentStart": 4129,
"end": 4150,
"start": 4129,
"type": "TagDeclarator",
"value": "rectangleSegmentA003"
},
@ -706,9 +706,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 4274,
"end": 4295,
"start": 4274,
"commentStart": 4256,
"end": 4277,
"start": 4256,
"type": "TagDeclarator",
"value": "rectangleSegmentB003"
},
@ -719,9 +719,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 4397,
"end": 4418,
"start": 4397,
"commentStart": 4379,
"end": 4400,
"start": 4379,
"type": "TagDeclarator",
"value": "rectangleSegmentC003"
},
@ -732,9 +732,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 4488,
"end": 4509,
"start": 4488,
"commentStart": 4470,
"end": 4491,
"start": 4470,
"type": "TagDeclarator",
"value": "rectangleSegmentD003"
},
@ -755,9 +755,9 @@ description: Variables in memory after executing enclosure.kcl
3.0
],
"tag": {
"commentStart": 4147,
"end": 4168,
"start": 4147,
"commentStart": 4129,
"end": 4150,
"start": 4129,
"type": "TagDeclarator",
"value": "rectangleSegmentA003"
},
@ -780,9 +780,9 @@ description: Variables in memory after executing enclosure.kcl
3.0
],
"tag": {
"commentStart": 4274,
"end": 4295,
"start": 4274,
"commentStart": 4256,
"end": 4277,
"start": 4256,
"type": "TagDeclarator",
"value": "rectangleSegmentB003"
},
@ -805,9 +805,9 @@ description: Variables in memory after executing enclosure.kcl
172.0
],
"tag": {
"commentStart": 4397,
"end": 4418,
"start": 4397,
"commentStart": 4379,
"end": 4400,
"start": 4379,
"type": "TagDeclarator",
"value": "rectangleSegmentC003"
},
@ -830,9 +830,9 @@ description: Variables in memory after executing enclosure.kcl
172.0
],
"tag": {
"commentStart": 4488,
"end": 4509,
"start": 4488,
"commentStart": 4470,
"end": 4491,
"start": 4470,
"type": "TagDeclarator",
"value": "rectangleSegmentD003"
},
@ -896,9 +896,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2445,
"end": 2466,
"start": 2445,
"commentStart": 2436,
"end": 2457,
"start": 2436,
"type": "TagDeclarator",
"value": "rectangleSegmentA002"
},
@ -909,9 +909,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2550,
"end": 2571,
"start": 2550,
"commentStart": 2532,
"end": 2553,
"start": 2532,
"type": "TagDeclarator",
"value": "rectangleSegmentB002"
},
@ -922,9 +922,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2673,
"end": 2694,
"start": 2673,
"commentStart": 2655,
"end": 2676,
"start": 2655,
"type": "TagDeclarator",
"value": "rectangleSegmentC002"
},
@ -935,9 +935,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2764,
"end": 2785,
"start": 2764,
"commentStart": 2746,
"end": 2767,
"start": 2746,
"type": "TagDeclarator",
"value": "rectangleSegmentD002"
},
@ -958,9 +958,9 @@ description: Variables in memory after executing enclosure.kcl
0.0
],
"tag": {
"commentStart": 2445,
"end": 2466,
"start": 2445,
"commentStart": 2436,
"end": 2457,
"start": 2436,
"type": "TagDeclarator",
"value": "rectangleSegmentA002"
},
@ -983,9 +983,9 @@ description: Variables in memory after executing enclosure.kcl
0.0
],
"tag": {
"commentStart": 2550,
"end": 2571,
"start": 2550,
"commentStart": 2532,
"end": 2553,
"start": 2532,
"type": "TagDeclarator",
"value": "rectangleSegmentB002"
},
@ -1008,9 +1008,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 2673,
"end": 2694,
"start": 2673,
"commentStart": 2655,
"end": 2676,
"start": 2655,
"type": "TagDeclarator",
"value": "rectangleSegmentC002"
},
@ -1033,9 +1033,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 2764,
"end": 2785,
"start": 2764,
"commentStart": 2746,
"end": 2767,
"start": 2746,
"type": "TagDeclarator",
"value": "rectangleSegmentD002"
},
@ -1495,9 +1495,9 @@ description: Variables in memory after executing enclosure.kcl
0.0
],
"tag": {
"commentStart": 485,
"end": 506,
"start": 485,
"commentStart": 476,
"end": 497,
"start": 476,
"type": "TagDeclarator",
"value": "rectangleSegmentB001"
},
@ -1520,9 +1520,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 608,
"end": 629,
"start": 608,
"commentStart": 599,
"end": 620,
"start": 599,
"type": "TagDeclarator",
"value": "rectangleSegmentC001"
},
@ -1545,9 +1545,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 699,
"end": 720,
"start": 699,
"commentStart": 690,
"end": 711,
"start": 690,
"type": "TagDeclarator",
"value": "rectangleSegmentD001"
},
@ -1669,9 +1669,9 @@ description: Variables in memory after executing enclosure.kcl
0.0
],
"tag": {
"commentStart": 2445,
"end": 2466,
"start": 2445,
"commentStart": 2436,
"end": 2457,
"start": 2436,
"type": "TagDeclarator",
"value": "rectangleSegmentA002"
},
@ -1694,9 +1694,9 @@ description: Variables in memory after executing enclosure.kcl
0.0
],
"tag": {
"commentStart": 2550,
"end": 2571,
"start": 2550,
"commentStart": 2532,
"end": 2553,
"start": 2532,
"type": "TagDeclarator",
"value": "rectangleSegmentB002"
},
@ -1719,9 +1719,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 2673,
"end": 2694,
"start": 2673,
"commentStart": 2655,
"end": 2676,
"start": 2655,
"type": "TagDeclarator",
"value": "rectangleSegmentC002"
},
@ -1744,9 +1744,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 2764,
"end": 2785,
"start": 2764,
"commentStart": 2746,
"end": 2767,
"start": 2746,
"type": "TagDeclarator",
"value": "rectangleSegmentD002"
},
@ -1868,9 +1868,9 @@ description: Variables in memory after executing enclosure.kcl
3.0
],
"tag": {
"commentStart": 4147,
"end": 4168,
"start": 4147,
"commentStart": 4129,
"end": 4150,
"start": 4129,
"type": "TagDeclarator",
"value": "rectangleSegmentA003"
},
@ -1893,9 +1893,9 @@ description: Variables in memory after executing enclosure.kcl
3.0
],
"tag": {
"commentStart": 4274,
"end": 4295,
"start": 4274,
"commentStart": 4256,
"end": 4277,
"start": 4256,
"type": "TagDeclarator",
"value": "rectangleSegmentB003"
},
@ -1918,9 +1918,9 @@ description: Variables in memory after executing enclosure.kcl
172.0
],
"tag": {
"commentStart": 4397,
"end": 4418,
"start": 4397,
"commentStart": 4379,
"end": 4400,
"start": 4379,
"type": "TagDeclarator",
"value": "rectangleSegmentC003"
},
@ -1943,9 +1943,9 @@ description: Variables in memory after executing enclosure.kcl
172.0
],
"tag": {
"commentStart": 4488,
"end": 4509,
"start": 4488,
"commentStart": 4470,
"end": 4491,
"start": 4470,
"type": "TagDeclarator",
"value": "rectangleSegmentD003"
},
@ -2009,9 +2009,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2445,
"end": 2466,
"start": 2445,
"commentStart": 2436,
"end": 2457,
"start": 2436,
"type": "TagDeclarator",
"value": "rectangleSegmentA002"
},
@ -2022,9 +2022,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2550,
"end": 2571,
"start": 2550,
"commentStart": 2532,
"end": 2553,
"start": 2532,
"type": "TagDeclarator",
"value": "rectangleSegmentB002"
},
@ -2035,9 +2035,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2673,
"end": 2694,
"start": 2673,
"commentStart": 2655,
"end": 2676,
"start": 2655,
"type": "TagDeclarator",
"value": "rectangleSegmentC002"
},
@ -2048,9 +2048,9 @@ description: Variables in memory after executing enclosure.kcl
"id": "[uuid]",
"sourceRange": [],
"tag": {
"commentStart": 2764,
"end": 2785,
"start": 2764,
"commentStart": 2746,
"end": 2767,
"start": 2746,
"type": "TagDeclarator",
"value": "rectangleSegmentD002"
},
@ -2071,9 +2071,9 @@ description: Variables in memory after executing enclosure.kcl
0.0
],
"tag": {
"commentStart": 2445,
"end": 2466,
"start": 2445,
"commentStart": 2436,
"end": 2457,
"start": 2436,
"type": "TagDeclarator",
"value": "rectangleSegmentA002"
},
@ -2096,9 +2096,9 @@ description: Variables in memory after executing enclosure.kcl
0.0
],
"tag": {
"commentStart": 2550,
"end": 2571,
"start": 2550,
"commentStart": 2532,
"end": 2553,
"start": 2532,
"type": "TagDeclarator",
"value": "rectangleSegmentB002"
},
@ -2121,9 +2121,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 2673,
"end": 2694,
"start": 2673,
"commentStart": 2655,
"end": 2676,
"start": 2655,
"type": "TagDeclarator",
"value": "rectangleSegmentC002"
},
@ -2146,9 +2146,9 @@ description: Variables in memory after executing enclosure.kcl
175.0
],
"tag": {
"commentStart": 2764,
"end": 2785,
"start": 2764,
"commentStart": 2746,
"end": 2767,
"start": 2746,
"type": "TagDeclarator",
"value": "rectangleSegmentD002"
},

View File

@ -1597,7 +1597,8 @@ description: Artifact commands exhaust-manifold.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{
@ -1608,7 +1609,8 @@ description: Artifact commands exhaust-manifold.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{
@ -1619,7 +1621,8 @@ description: Artifact commands exhaust-manifold.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{
@ -1630,7 +1633,8 @@ description: Artifact commands exhaust-manifold.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{

View File

@ -4490,7 +4490,8 @@ description: Artifact commands utility-sink.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{

File diff suppressed because it is too large Load Diff

View File

@ -4,19 +4,17 @@ description: Operations executed mike_stress_test.kcl
---
[
{
"labeledArgs": {
"planeOrSolid": {
"value": {
"type": "String",
"value": "XY"
},
"sourceRange": []
}
},
"labeledArgs": {},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": null
"unlabeledArg": {
"value": {
"type": "Plane",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
{
"labeledArgs": {

View File

@ -26043,10 +26043,8 @@ description: Variables in memory after executing mike_stress_test.kcl
}
],
"on": {
"type": "plane",
"id": "[uuid]",
"artifactId": "[uuid]",
"value": "XY",
"id": "[uuid]",
"origin": {
"x": 0.0,
"y": 0.0,
@ -26055,12 +26053,14 @@ description: Variables in memory after executing mike_stress_test.kcl
"type": "Mm"
}
},
"type": "plane",
"value": "XY",
"xAxis": {
"x": 1.0,
"y": 0.0,
"z": 0.0,
"units": {
"type": "Mm"
"type": "Unknown"
}
},
"yAxis": {
@ -26068,7 +26068,7 @@ description: Variables in memory after executing mike_stress_test.kcl
"y": 1.0,
"z": 0.0,
"units": {
"type": "Mm"
"type": "Unknown"
}
}
},

View File

@ -5,7 +5,7 @@ description: Error from executing panic_repro_cube.kcl
KCL Semantic error
× semantic: This function expected the input argument to be tag identifier
│ but it's actually of type Unique ID (uuid)
│ but it's actually of type tag
╭─[43:25]
42 │ // these double wrapped functions are the point of this test
43 │ getNextAdjacentEdge(getNextAdjacentEdge(seg01)),

View File

@ -4,8 +4,7 @@ description: Error from executing pattern_into_union.kcl
---
KCL Engine error
× engine: Modeling command failed: [ApiError { error_code: InternalEngine,
│ message: "More than 2 solids were passed to the low-level CSG method" }]
× engine: More than 2 solids were passed to the low-level CSG method
╭─[67:1]
66 │
67 │ union([base,endTabs])

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
@settings(defaultLengthUnit = in)
// Define parameters
trussSupportAngle = 15
height = 120
thickness = 4
sketch001 = startSketchOn(YZ)
profile001 = startProfile(sketch001, at = [60, 0])
|> xLine(length = -120, tag = $bottomFace)
|> yLine(length = 12)
|> angledLine(angle = 25, endAbsoluteX = 0, tag = $tag001)
|> angledLine(angle = -25, endAbsoluteX = 60)
|> close()
profile002 = startProfile(sketch001, at = [60-thickness, thickness])
|> xLine(endAbsolute = thickness/2)
|> yLine(endAbsolute = segEndY(tag001)-thickness) // update
|> angledLine(endAbsoluteX = profileStartX(%), angle = -25)
|> close(%)
profile003 = startProfile(sketch001, at = [-60+thickness, thickness])
|> xLine(endAbsolute = -thickness/2)
|> yLine(endAbsolute = segEndY(tag001)-thickness) // update
|> angledLine(endAbsoluteX = profileStartX(%), angle = 205)
|> close(%)
profile004 = subtract2d(profile001, tool = profile002)
subtract2d(profile001, tool = profile003)
body001 = extrude(profile001, length = 2)
sketch002 = startSketchOn(offsetPlane(YZ, offset = .1))
profile006 = startProfile(sketch002, at = [thickness/2-1, 14])
|> angledLine(angle = 30, length = 25)
|> angledLine(angle = -25, length = 5)
|> angledLine(angle = 210, endAbsoluteX = profileStartX(%))
|> close(%)
|> extrude(%, length = 1.8)
profile007 = startProfile(sketch002, at = [-thickness/2+1, 14])
|> angledLine(angle = 150, length = 25)
|> angledLine(angle = 205, length = 5)
|> angledLine(angle = -30, endAbsoluteX = profileStartX(%))
|> close(%)
|> extrude(%, length = 1.8)
newSketch = body001 + profile006 + profile007
leg001Sketch = startSketchOn(newSketch, face = bottomFace)
legProfile001 = startProfile(leg001Sketch, at = [-60, 0])
|> xLine(%, length = 4)
|> yLine(%, length = 2)
|> xLine(%, endAbsolute = profileStartX(%))
|> close(%)
leg001 = extrude(legProfile001, length = 48)
|> rotate(axis = [0, 0, 1.0], angle = -90)

View File

@ -0,0 +1,264 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Operations executed sketch_on_face_union.kcl
---
[
{
"labeledArgs": {},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Plane",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
{
"labeledArgs": {
"tool": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
"name": "subtract2d",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"tool": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
"name": "subtract2d",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"length": {
"value": {
"type": "Number",
"value": 2.0,
"ty": {
"type": "Default",
"len": {
"type": "Inches"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "extrude",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Plane",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
{
"type": "KclStdLibCall",
"name": "offsetPlane",
"unlabeledArg": {
"value": {
"type": "Plane",
"artifact_id": "[uuid]"
},
"sourceRange": []
},
"labeledArgs": {
"offset": {
"value": {
"type": "Number",
"value": 0.1,
"ty": {
"type": "Default",
"len": {
"type": "Inches"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"sourceRange": []
},
{
"labeledArgs": {
"length": {
"value": {
"type": "Number",
"value": 1.8,
"ty": {
"type": "Default",
"len": {
"type": "Inches"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "extrude",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"length": {
"value": {
"type": "Number",
"value": 1.8,
"ty": {
"type": "Default",
"len": {
"type": "Inches"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "extrude",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"face": {
"value": {
"type": "TagIdentifier",
"value": "bottomFace",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
"name": "startSketchOn",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Solid",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
},
{
"labeledArgs": {
"length": {
"value": {
"type": "Number",
"value": 48.0,
"ty": {
"type": "Default",
"len": {
"type": "Inches"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"name": "extrude",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": {
"value": {
"type": "Sketch",
"value": {
"artifactId": "[uuid]"
}
},
"sourceRange": []
}
}
]

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -0,0 +1,62 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing sketch_on_face_union.kcl
---
@settings(defaultLengthUnit = in)
// Define parameters
trussSupportAngle = 15
height = 120
thickness = 4
sketch001 = startSketchOn(YZ)
profile001 = startProfile(sketch001, at = [60, 0])
|> xLine(length = -120, tag = $bottomFace)
|> yLine(length = 12)
|> angledLine(angle = 25, endAbsoluteX = 0, tag = $tag001)
|> angledLine(angle = -25, endAbsoluteX = 60)
|> close()
profile002 = startProfile(sketch001, at = [60 - thickness, thickness])
|> xLine(endAbsolute = thickness / 2)
|> yLine(endAbsolute = segEndY(tag001) - thickness) // update
|> angledLine(endAbsoluteX = profileStartX(%), angle = -25)
|> close(%)
profile003 = startProfile(sketch001, at = [-60 + thickness, thickness])
|> xLine(endAbsolute = -thickness / 2)
|> yLine(endAbsolute = segEndY(tag001) - thickness) // update
|> angledLine(endAbsoluteX = profileStartX(%), angle = 205)
|> close(%)
profile004 = subtract2d(profile001, tool = profile002)
subtract2d(profile001, tool = profile003)
body001 = extrude(profile001, length = 2)
sketch002 = startSketchOn(offsetPlane(YZ, offset = .1))
profile006 = startProfile(sketch002, at = [thickness / 2 - 1, 14])
|> angledLine(angle = 30, length = 25)
|> angledLine(angle = -25, length = 5)
|> angledLine(angle = 210, endAbsoluteX = profileStartX(%))
|> close(%)
|> extrude(%, length = 1.8)
profile007 = startProfile(sketch002, at = [-thickness / 2 + 1, 14])
|> angledLine(angle = 150, length = 25)
|> angledLine(angle = 205, length = 5)
|> angledLine(angle = -30, endAbsoluteX = profileStartX(%))
|> close(%)
|> extrude(%, length = 1.8)
newSketch = body001 + profile006 + profile007
leg001Sketch = startSketchOn(newSketch, face = bottomFace)
legProfile001 = startProfile(leg001Sketch, at = [-60, 0])
|> xLine(%, length = 4)
|> yLine(%, length = 2)
|> xLine(%, endAbsolute = profileStartX(%))
|> close(%)
leg001 = extrude(legProfile001, length = 48)
|> rotate(axis = [0, 0, 1.0], angle = -90)

View File

@ -417,7 +417,8 @@ description: Artifact commands subtract_regression03.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{

View File

@ -394,7 +394,8 @@ description: Artifact commands subtract_regression05.kcl
"target": "[uuid]",
"trajectory": "[uuid]",
"sectional": false,
"tolerance": 0.0000001
"tolerance": 0.0000001,
"relative_to": "sketch_plane"
}
},
{

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-python-bindings"
version = "0.3.69"
version = "0.3.70"
edition = "2021"
repository = "https://github.com/kittycad/modeling-app"
exclude = ["tests/*", "files/*", "venv/*"]

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-test-server"
description = "A test server for KCL"
version = "0.1.69"
version = "0.1.70"
edition = "2021"
license = "MIT"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-to-core"
description = "Utility methods to convert kcl to engine core executable tests"
version = "0.1.69"
version = "0.1.70"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-wasm-lib"
version = "0.1.69"
version = "0.1.70"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-app"
rust-version = "1.83"

View File

@ -1,4 +1,4 @@
import { useEffect, useRef } from 'react'
import { useEffect, useRef, useState } from 'react'
import toast from 'react-hot-toast'
import { useHotkeys } from 'react-hotkeys-hook'
import ModalContainer from 'react-modal-promise'
@ -42,6 +42,7 @@ import {
ONBOARDING_TOAST_ID,
TutorialRequestToast,
} from '@src/routes/Onboarding/utils'
import { reportRejection } from '@src/lib/trap'
// CYCLIC REF
sceneInfra.camControls.engineStreamActor = engineStreamActor
@ -52,6 +53,7 @@ maybeWriteToDisk()
export function App() {
const { project, file } = useLoaderData() as IndexLoaderData
const [nativeFileMenuCreated, setNativeFileMenuCreated] = useState(false)
// Keep a lookout for a URL query string that invokes the 'import file from URL' command
useCreateFileLinkQuery((argDefaultValues) => {
@ -145,12 +147,25 @@ export function App() {
}
}, [location, settings.app.onboardingStatus, navigate])
// Only create the native file menus on desktop
useEffect(() => {
if (isDesktop()) {
window.electron
.createModelingPageMenu()
.then(() => {
setNativeFileMenuCreated(true)
})
.catch(reportRejection)
}
}, [])
return (
<div className="relative h-full flex flex-col" ref={ref}>
<AppHeader
className="transition-opacity transition-duration-75"
project={{ project, file }}
enableMenu={true}
nativeFileMenuCreated={nativeFileMenuCreated}
>
<CommandBarOpenButton />
<ShareButton />

View File

@ -14,6 +14,7 @@ interface AppHeaderProps extends React.PropsWithChildren {
className?: string
enableMenu?: boolean
style?: React.CSSProperties
nativeFileMenuCreated: boolean
}
export const AppHeader = ({
@ -23,12 +24,14 @@ export const AppHeader = ({
className = '',
style,
enableMenu = false,
nativeFileMenuCreated,
}: AppHeaderProps) => {
const user = useUser()
return (
<header
id="app-header"
data-testid="app-header"
className={
'w-full grid ' +
styles.header +
@ -37,6 +40,7 @@ export const AppHeader = ({
}overlaid-panes sticky top-0 z-20 px-2 items-start ` +
className
}
data-native-file-menu={nativeFileMenuCreated}
style={style}
>
<ProjectSidebarMenu

View File

@ -169,6 +169,7 @@ export const CommandBar = () => {
)}
<div className="flex flex-col gap-2 !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent">
<button
data-testid="command-bar-close-button"
onClick={() => commandBarActor.send({ type: 'Close' })}
className="group m-0 p-0 border-none bg-transparent hover:bg-transparent"
>

View File

@ -29,15 +29,9 @@ import { useSelector } from '@xstate/react'
import type { MouseEventHandler } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useRouteLoaderData } from 'react-router-dom'
import { isPlaywright } from '@src/lib/isPlaywright'
import {
engineStreamZoomToFit,
engineViewIsometricWithGeometryPresent,
engineViewIsometricWithoutGeometryPresent,
} from '@src/lib/utils'
import { DEFAULT_DEFAULT_LENGTH_UNIT } from '@src/lib/constants'
import { createThumbnailPNGOnDesktop } from '@src/lib/screenshot'
import type { SettingsViaQueryString } from '@src/lib/settings/settingsTypes'
import { resetCameraPosition } from '@src/lib/resetCameraPosition'
export const EngineStream = (props: {
pool: string | null
@ -104,31 +98,7 @@ export const EngineStream = (props: {
kmp
.then(async () => {
// Gotcha: Playwright E2E tests will be zoom_to_fit, when you try to recreate the e2e test manually
// your localhost will do view_isometric. Turn this boolean on to have the same experience when manually
// debugging e2e tests
// We need a padding of 0.1 for zoom_to_fit for all E2E tests since they were originally
// written with zoom_to_fit with padding 0.1
const padding = 0.1
if (isPlaywright()) {
await engineStreamZoomToFit({ engineCommandManager, padding })
} else {
// If the scene is empty you cannot use view_isometric, it will not move the camera
if (kclManager.isAstBodyEmpty(kclManager.ast)) {
await engineViewIsometricWithoutGeometryPresent({
engineCommandManager,
unit:
kclManager.fileSettings.defaultLengthUnit ||
DEFAULT_DEFAULT_LENGTH_UNIT,
})
} else {
await engineViewIsometricWithGeometryPresent({
engineCommandManager,
padding,
})
}
}
await resetCameraPosition()
if (project && project.path) {
createThumbnailPNGOnDesktop({

View File

@ -29,7 +29,7 @@ import { kclCommands } from '@src/lib/kclCommands'
import { BROWSER_PATH, PATHS } from '@src/lib/paths'
import { markOnce } from '@src/lib/performance'
import { codeManager, kclManager } from '@src/lib/singletons'
import { err, reportRejection } from '@src/lib/trap'
import { err } from '@src/lib/trap'
import { type IndexLoaderData } from '@src/lib/types'
import { useSettings, useToken } from '@src/lib/singletons'
import { commandBarActor } from '@src/lib/singletons'
@ -59,12 +59,6 @@ export const FileMachineProvider = ({
const { project, file } = projectData
const filePath = useAbsoluteFilePath()
// Only create the native file menus on desktop
useEffect(() => {
if (isDesktop()) {
window.electron.createModelingPageMenu().catch(reportRejection)
}
}, [])
useEffect(() => {
const {

View File

@ -13,6 +13,7 @@ import { VIEW_NAMES_SEMANTIC } from '@src/lib/constants'
import { sceneInfra } from '@src/lib/singletons'
import { reportRejection } from '@src/lib/trap'
import { useSettings } from '@src/lib/singletons'
import { resetCameraPosition } from '@src/lib/resetCameraPosition'
export function useViewControlMenuItems() {
const { state: modelingState, send: modelingSend } = useModelingContext()
@ -38,7 +39,7 @@ export function useViewControlMenuItems() {
<ContextMenuDivider />,
<ContextMenuItem
onClick={() => {
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
resetCameraPosition().catch(reportRejection)
}}
disabled={shouldLockView}
>

View File

@ -1,328 +0,0 @@
import { language, syntaxTree } from '@codemirror/language'
import type { Extension, Range, Text } from '@codemirror/state'
import type { DecorationSet, ViewUpdate } from '@codemirror/view'
import {
Decoration,
EditorView,
ViewPlugin,
WidgetType,
} from '@codemirror/view'
import type { Tree } from '@lezer/common'
import { NodeProp } from '@lezer/common'
import { isArray } from '@src/lib/utils'
interface PickerState {
from: number
to: number
alpha: string
colorType: ColorType
}
export interface WidgetOptions extends PickerState {
color: string
}
export type ColorData = Omit<WidgetOptions, 'from' | 'to'>
const pickerState = new WeakMap<HTMLInputElement, PickerState>()
export enum ColorType {
hex = 'HEX',
}
const hexRegex = /(^|\b)(#[0-9a-f]{3,9})(\b|$)/i
function discoverColorsInKCL(
syntaxTree: Tree,
from: number,
to: number,
typeName: string,
doc: Text,
language?: string
): WidgetOptions | Array<WidgetOptions> | null {
switch (typeName) {
case 'Program':
case 'VariableDeclaration':
case 'CallExpressionKw':
case 'ObjectExpression':
case 'ObjectProperty':
case 'ArgumentList':
case 'PipeExpression': {
let innerTree = syntaxTree.resolveInner(from, 0).tree
if (!innerTree) {
innerTree = syntaxTree.resolveInner(from, 1).tree
if (!innerTree) {
return null
}
}
const overlayTree = innerTree.prop(NodeProp.mounted)?.tree
if (overlayTree?.type.name !== 'Styles') {
return null
}
const ret: Array<WidgetOptions> = []
overlayTree.iterate({
from: 0,
to: overlayTree.length,
enter: ({ type, from: overlayFrom, to: overlayTo }) => {
const maybeWidgetOptions = discoverColorsInKCL(
syntaxTree,
// We add one because the tree doesn't include the
// quotation mark from the style tag
from + 1 + overlayFrom,
from + 1 + overlayTo,
type.name,
doc,
language
)
if (maybeWidgetOptions) {
if (isArray(maybeWidgetOptions)) {
console.error('Unexpected nested overlays')
ret.push(...maybeWidgetOptions)
} else {
ret.push(maybeWidgetOptions)
}
}
},
})
return ret
}
case 'String': {
const result = parseColorLiteral(doc.sliceString(from, to))
if (!result) {
return null
}
return {
...result,
from,
to,
}
}
default:
return null
}
}
export function parseColorLiteral(colorLiteral: string): ColorData | null {
const literal = colorLiteral.replace(/"/g, '')
const match = hexRegex.exec(literal)
if (!match) {
return null
}
const [color, alpha] = toFullHex(literal)
return {
colorType: ColorType.hex,
color,
alpha,
}
}
function colorPickersDecorations(
view: EditorView,
discoverColors: typeof discoverColorsInKCL
) {
const widgets: Array<Range<Decoration>> = []
const st = syntaxTree(view.state)
for (const range of view.visibleRanges) {
st.iterate({
from: range.from,
to: range.to,
enter: ({ type, from, to }) => {
const maybeWidgetOptions = discoverColors(
st,
from,
to,
type.name,
view.state.doc,
view.state.facet(language)?.name
)
if (!maybeWidgetOptions) {
return
}
if (!isArray(maybeWidgetOptions)) {
widgets.push(
Decoration.widget({
widget: new ColorPickerWidget(maybeWidgetOptions),
side: 1,
}).range(maybeWidgetOptions.from)
)
return
}
for (const wo of maybeWidgetOptions) {
widgets.push(
Decoration.widget({
widget: new ColorPickerWidget(wo),
side: 1,
}).range(wo.from)
)
}
},
})
}
return Decoration.set(widgets)
}
function toFullHex(color: string): string[] {
if (color.length === 4) {
// 3-char hex
return [
`#${color[1].repeat(2)}${color[2].repeat(2)}${color[3].repeat(2)}`,
'',
]
}
if (color.length === 5) {
// 4-char hex (alpha)
return [
`#${color[1].repeat(2)}${color[2].repeat(2)}${color[3].repeat(2)}`,
color[4].repeat(2),
]
}
if (color.length === 9) {
// 8-char hex (alpha)
return [`#${color.slice(1, -2)}`, color.slice(-2)]
}
return [color, '']
}
export const wrapperClassName = 'cm-css-color-picker-wrapper'
class ColorPickerWidget extends WidgetType {
private readonly state: PickerState
private readonly color: string
constructor({ color, ...state }: WidgetOptions) {
super()
this.state = state
this.color = color
}
eq(other: ColorPickerWidget) {
return (
other.state.colorType === this.state.colorType &&
other.color === this.color &&
other.state.from === this.state.from &&
other.state.to === this.state.to &&
other.state.alpha === this.state.alpha
)
}
toDOM() {
const picker = document.createElement('input')
pickerState.set(picker, this.state)
picker.type = 'color'
picker.value = this.color
const wrapper = document.createElement('span')
wrapper.appendChild(picker)
wrapper.className = wrapperClassName
return wrapper
}
ignoreEvent() {
return false
}
}
export const colorPickerTheme = EditorView.baseTheme({
[`.${wrapperClassName}`]: {
display: 'inline-block',
outline: '1px solid #eee',
marginRight: '0.6ch',
height: '1em',
width: '1em',
transform: 'translateY(1px)',
},
[`.${wrapperClassName} input[type="color"]`]: {
cursor: 'pointer',
height: '100%',
width: '100%',
padding: 0,
border: 'none',
'&::-webkit-color-swatch-wrapper': {
padding: 0,
},
'&::-webkit-color-swatch': {
border: 'none',
},
'&::-moz-color-swatch': {
border: 'none',
},
},
})
interface IFactoryOptions {
discoverColors: typeof discoverColorsInKCL
}
export const makeColorPicker = (options: IFactoryOptions) =>
ViewPlugin.fromClass(
class ColorPickerViewPlugin {
decorations: DecorationSet
constructor(view: EditorView) {
this.decorations = colorPickersDecorations(view, options.discoverColors)
}
update(update: ViewUpdate) {
if (update.docChanged || update.viewportChanged) {
this.decorations = colorPickersDecorations(
update.view,
options.discoverColors
)
}
}
},
{
decorations: (v) => v.decorations,
eventHandlers: {
change: (e, view) => {
const target = e.target as HTMLInputElement
if (
target.nodeName !== 'INPUT' ||
!target.parentElement ||
!target.parentElement.classList.contains(wrapperClassName)
) {
return false
}
const data = pickerState.get(target)!
let converted = '"' + target.value + data.alpha + '"'
view.dispatch({
changes: {
from: data.from,
to: data.to,
insert: converted,
},
})
return true
},
},
}
)
export const colorPicker: Extension = [
makeColorPicker({ discoverColors: discoverColorsInKCL }),
colorPickerTheme,
]

View File

@ -7,6 +7,7 @@ import type {
} from '@kittycad/codemirror-lsp-client'
import {
lspCodeActionEvent,
lspColorUpdateEvent,
lspFormatCodeEvent,
lspPlugin,
lspRenameEvent,
@ -88,6 +89,8 @@ export class KclPlugin implements PluginValue {
isRelevant = true
} else if (tr.annotation(lspCodeActionEvent.type)) {
isRelevant = true
} else if (tr.annotation(lspColorUpdateEvent.type)) {
isRelevant = true
}
// Don't make this an else.

View File

@ -8,7 +8,6 @@ import type {
import type * as LSP from 'vscode-languageserver-protocol'
import { kclPlugin } from '@src/editor/plugins/lsp/kcl'
import { colorPicker } from '@src/editor/plugins/lsp/kcl/colors'
export interface LanguageOptions {
workspaceFolders: LSP.WorkspaceFolder[]
@ -22,7 +21,6 @@ export interface LanguageOptions {
export function kcl(options: LanguageOptions) {
return new LanguageSupport(KclLanguage, [
colorPicker,
kclPlugin({
documentUri: options.documentUri,
workspaceFolders: options.workspaceFolders,

View File

@ -147,6 +147,14 @@ export class KclManager {
set switchedFiles(switchedFiles: boolean) {
this._switchedFiles = switchedFiles
// These belonged to the previous file
this.lastSuccessfulOperations = []
this.lastSuccessfulVariables = {}
// Without this, when leaving a project which has errors and opening another project which doesn't,
// you'd see the errors from the previous project for a short time until the new code is executed.
this._errors = []
}
get variables() {

View File

@ -23,7 +23,7 @@ import {
getThemeColorForEngine,
} from '@src/lib/theme'
import { reportRejection } from '@src/lib/trap'
import { binaryToUuid, uuidv4 } from '@src/lib/utils'
import { binaryToUuid, isArray, uuidv4 } from '@src/lib/utils'
const pingIntervalMs = 1_000
@ -2010,12 +2010,20 @@ export class EngineCommandManager extends EventTarget {
return Promise.reject(EXECUTE_AST_INTERRUPT_ERROR_MESSAGE)
}
const resp = await this.sendCommand(id, {
command,
range,
idToRangeMap,
})
return BSON.serialize(resp[0])
try {
const resp = await this.sendCommand(id, {
command,
range,
idToRangeMap,
})
return BSON.serialize(resp[0])
} catch (e) {
if (isArray(e) && e.length > 0) {
return Promise.reject(JSON.stringify(e[0]))
}
return Promise.reject(JSON.stringify(e))
}
}
/**
* Common send command function used for both modeling and scene commands

View File

@ -31,7 +31,7 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
)
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS + ' [TEST]', {
id: toastId,
duration: 5_000,
duration: 10_000,
})
return
}

View File

@ -0,0 +1,40 @@
import { DEFAULT_DEFAULT_LENGTH_UNIT } from '@src/lib/constants'
import { isPlaywright } from '@src/lib/isPlaywright'
import { engineCommandManager, kclManager } from '@src/lib/singletons'
import {
engineStreamZoomToFit,
engineViewIsometricWithoutGeometryPresent,
engineViewIsometricWithGeometryPresent,
} from '@src/lib/utils'
/**
* Reset the camera position to a baseline, which is isometric for
* normal users and a deprecated "front-down" view for playwright tests.
*
* Gotcha: Playwright E2E tests will be zoom_to_fit, when you try to recreate the e2e test manually
* your localhost will do view_isometric. Turn this boolean on to have the same experience when manually
* debugging e2e tests
*/
export async function resetCameraPosition() {
// We need a padding of 0.1 for zoom_to_fit for all E2E tests since they were originally
// written with zoom_to_fit with padding 0.1
const padding = 0.1
if (isPlaywright()) {
await engineStreamZoomToFit({ engineCommandManager, padding })
} else {
// If the scene is empty you cannot use view_isometric, it will not move the camera
if (kclManager.isAstBodyEmpty(kclManager.ast)) {
await engineViewIsometricWithoutGeometryPresent({
engineCommandManager,
unit:
kclManager.fileSettings.defaultLengthUnit ||
DEFAULT_DEFAULT_LENGTH_UNIT,
})
} else {
await engineViewIsometricWithGeometryPresent({
engineCommandManager,
padding,
})
}
}
}

View File

@ -139,18 +139,20 @@ export const settingsMachine = setup({
return () => darkModeMatcher?.removeEventListener('change', listener)
}),
registerCommands: fromCallback<
{ type: 'update' },
{ type: 'update'; settings: SettingsType },
{ settings: SettingsType; actor: AnyActorRef }
>(({ input, receive, system }) => {
// This assumes this actor is running in a system with a command palette
const commandBarActor = system.get(ACTOR_IDS.COMMAND_BAR)
// If the user wants to hide the settings commands
//from the command bar don't add them.
if (settings.commandBar.includeSettings.current === false) return
if (settings.commandBar.includeSettings.current === false) {
return
}
let commands: Command[] = []
const updateCommands = () =>
settingsWithCommandConfigs(input.settings)
const updateCommands = (newSettings: SettingsType) =>
settingsWithCommandConfigs(newSettings)
.map((type) =>
createSettingsCommand({
type,
@ -175,14 +177,19 @@ export const settingsMachine = setup({
data: { commands: commands },
})
receive((event) => {
if (event.type !== 'update') return
receive(({ type, settings: newSettings }) => {
if (type !== 'update') {
return
}
removeCommands()
commands = updateCommands()
commands =
newSettings.commandBar.includeSettings.current === false
? []
: updateCommands(newSettings)
addCommands()
})
commands = updateCommands()
commands = updateCommands(settings)
addCommands()
return () => {
@ -205,7 +212,9 @@ export const settingsMachine = setup({
const sceneInfra = rootContext.sceneInfra
const sceneEntitiesManager = rootContext.sceneEntitiesManager
if (!sceneInfra || !sceneEntitiesManager) return
if (!sceneInfra || !sceneEntitiesManager) {
return
}
const opposingTheme = getOppositeTheme(context.app.theme.current)
sceneInfra.theme = opposingTheme
sceneEntitiesManager.updateSegmentBaseColor(opposingTheme)
@ -213,13 +222,17 @@ export const settingsMachine = setup({
setAllowOrbitInSketchMode: ({ context, self }) => {
const rootContext = self.system.get('root').getSnapshot().context
const sceneInfra = rootContext.sceneInfra
if (!sceneInfra.camControls) return
if (!sceneInfra.camControls) {
return
}
sceneInfra.camControls._setting_allowOrbitInSketchMode =
context.app.allowOrbitInSketchMode.current
// ModelingMachineProvider will do a use effect to trigger the camera engine sync
},
toastSuccess: ({ event }) => {
if (!('data' in event)) return
if (!('data' in event)) {
return
}
const eventParts = event.type.replace(/^set./, '').split('.') as [
keyof typeof settings,
string,
@ -435,6 +448,22 @@ export const settingsMachine = setup({
actions: ['setSettingAtLevel', 'setThemeColor'],
},
'set.commandBar.includeSettings': {
target: 'persisting settings',
actions: [
'setSettingAtLevel',
'toastSuccess',
sendTo(
'registerCommands',
({ context: { currentProject: _, ...settings } }) => ({
type: 'update',
settings,
})
),
],
},
'set.modeling.defaultUnit': {
target: 'persisting settings',
@ -497,6 +526,13 @@ export const settingsMachine = setup({
'setClientTheme',
'setAllowOrbitInSketchMode',
'sendThemeToWatcher',
sendTo(
'registerCommands',
({ context: { currentProject: _, ...settings } }) => ({
type: 'update',
settings,
})
),
],
},
@ -510,6 +546,13 @@ export const settingsMachine = setup({
'setClientTheme',
'setAllowOrbitInSketchMode',
'sendThemeToWatcher',
sendTo(
'registerCommands',
({ context: { currentProject: _, ...settings } }) => ({
type: 'update',
settings,
})
),
],
},
@ -529,7 +572,13 @@ export const settingsMachine = setup({
'clearProjectSettings',
'clearCurrentProject',
'setThemeColor',
sendTo('registerCommands', { type: 'update' }),
sendTo(
'registerCommands',
({ context: { currentProject: _, ...settings } }) => ({
type: 'update',
settings,
})
),
],
},
},
@ -582,6 +631,13 @@ export const settingsMachine = setup({
'setClientTheme',
'setAllowOrbitInSketchMode',
'sendThemeToWatcher',
sendTo(
'registerCommands',
({ context: { currentProject: _, ...settings } }) => ({
type: 'update',
settings,
})
),
],
},
onError: {
@ -612,7 +668,13 @@ export const settingsMachine = setup({
'setClientTheme',
'setAllowOrbitInSketchMode',
'sendThemeToWatcher',
sendTo('registerCommands', { type: 'update' }),
sendTo(
'registerCommands',
({ context: { currentProject: _, ...settings } }) => ({
type: 'update',
settings,
})
),
],
},
onError: 'idle',

View File

@ -1,5 +1,5 @@
import type { FormEvent, HTMLProps } from 'react'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { toast } from 'react-hot-toast'
import { useHotkeys } from 'react-hotkeys-hook'
import {
@ -70,13 +70,20 @@ type ReadWriteProjectState = {
// This route only opens in the desktop context for now,
// as defined in Router.tsx, so we can use the desktop APIs and types.
const Home = () => {
const navigate = useNavigate()
const readWriteProjectDir = useCanReadWriteProjectDirectory()
const [nativeFileMenuCreated, setNativeFileMenuCreated] = useState(false)
const apiToken = useToken()
// Only create the native file menus on desktop
useEffect(() => {
if (isDesktop()) {
window.electron.createHomePageMenu().catch(reportRejection)
window.electron
.createHomePageMenu()
.then(() => {
setNativeFileMenuCreated(true)
})
.catch(reportRejection)
}
billingActor.send({ type: BillingTransition.Update, apiToken })
}, [])
@ -94,7 +101,6 @@ const Home = () => {
})
const location = useLocation()
const navigate = useNavigate()
const settings = useSettings()
const onboardingStatus = settings.app.onboardingStatus.current
@ -217,7 +223,10 @@ const Home = () => {
return (
<div className="relative flex flex-col items-stretch h-screen w-screen overflow-hidden">
<AppHeader showToolbar={false} />
<AppHeader
nativeFileMenuCreated={nativeFileMenuCreated}
showToolbar={false}
/>
<div className="overflow-hidden self-stretch w-full flex-1 home-layout max-w-4xl lg:max-w-5xl xl:max-w-7xl mb-12 px-4 mx-auto mt-8 lg:mt-24 lg:px-0">
<HomeHeader
setQuery={setQuery}

View File

@ -1,6 +1,5 @@
import { Dialog, Transition } from '@headlessui/react'
import { Fragment, useEffect, useRef } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import { CustomIcon } from '@src/components/CustomIcon'
@ -10,14 +9,19 @@ import { KeybindingsSectionsList } from '@src/components/Settings/KeybindingsSec
import { SettingsSearchBar } from '@src/components/Settings/SettingsSearchBar'
import { SettingsSectionsList } from '@src/components/Settings/SettingsSectionsList'
import { SettingsTabs } from '@src/components/Settings/SettingsTabs'
import { useDotDotSlash } from '@src/hooks/useDotDotSlash'
import { PATHS } from '@src/lib/paths'
import type { SettingsLevel } from '@src/lib/settings/settingsTypes'
export const Settings = () => {
const navigate = useNavigate()
const [searchParams, setSearchParams] = useSearchParams()
const close = () => navigate(location.pathname.replace(PATHS.SETTINGS, ''))
const close = () => {
// This makes sure input texts are saved before closing the dialog (eg. default project name).
if (document.activeElement instanceof HTMLInputElement) {
document.activeElement.blur()
}
navigate(location.pathname.replace(PATHS.SETTINGS, ''))
}
const location = useLocation()
const isFileSettings = location.pathname.includes(PATHS.FILE)
const searchParamTab =
@ -25,20 +29,21 @@ export const Settings = () => {
(isFileSettings ? 'project' : 'user')
const scrollRef = useRef<HTMLDivElement>(null)
const dotDotSlash = useDotDotSlash()
useHotkeys('esc', () => navigate(dotDotSlash()))
// Scroll to the hash on load if it exists
useEffect(() => {
console.log('hash', location.hash)
if (location.hash) {
const element = document.getElementById(location.hash.slice(1))
if (element) {
element.scrollIntoView({ block: 'center', behavior: 'smooth' })
;(
element.querySelector('input, select, textarea') as HTMLInputElement
)?.focus()
}
setTimeout(() => {
// GOTCHA: Next tick required, you can instantly navigate to a path and this code will find a null element and not scroll into view.
const element = document.getElementById(location.hash.slice(1))
if (element) {
element.scrollIntoView({ block: 'center', behavior: 'smooth' })
;(
element.querySelector('input, select, textarea') as HTMLInputElement
)?.focus()
}
}, 0)
}
}, [location.hash])