Compare commits

...

13 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
80 changed files with 11948 additions and 47811 deletions

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

@ -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

@ -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

@ -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)

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

@ -1311,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 {
@ -1323,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,
@ -1347,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
@ -1368,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) => {
@ -1865,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) => {
@ -1959,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,
)
@ -1974,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],
})
})?;
}
@ -2139,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,
)
@ -2152,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],
})
@ -2224,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,
@ -2665,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}"
@ -2691,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)
@ -2703,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

@ -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>,
@ -342,6 +344,7 @@ impl Args {
self.kw_args
.unlabeled
.as_ref()
.map(|(_, a)| a)
.or(self.args.first())
.or(self.pipe_value.as_ref())
}

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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -151,6 +151,10 @@ export class KclManager {
// 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,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,8 +29,6 @@ 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(() => {