Compare commits

..

1 Commits

Author SHA1 Message Date
bfefa0f51a Animate using a KCL function 2025-06-24 16:01:36 -04:00
39 changed files with 620 additions and 511 deletions

View File

@ -362,7 +362,7 @@ jobs:
- name: Authenticate to Google Cloud
if: ${{ env.IS_STAGING == 'true' }}
uses: 'google-github-actions/auth@v2.1.10'
uses: 'google-github-actions/auth@v2.1.8'
with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'

View File

@ -328,7 +328,7 @@ jobs:
mkdir -p releases/language-server/${{ env.TAG }}
cp -r build/* releases/language-server/${{ env.TAG }}
- name: "Authenticate to Google Cloud"
uses: "google-github-actions/auth@v2.1.10"
uses: "google-github-actions/auth@v2.1.8"
with:
credentials_json: "${{ secrets.GOOGLE_CLOUD_DL_SA }}"
- name: Set up Cloud SDK

View File

@ -108,7 +108,7 @@ jobs:
run: npm run files:set-notes
- name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.1.10'
uses: 'google-github-actions/auth@v2.1.8'
with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'

View File

@ -62,10 +62,7 @@ else
endif
public/kcl-samples/manifest.json: $(KCL_SOURCES)
ifndef WINDOWS
cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest
@ touch $@
endif
.vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
npm run tronb:vite:dev

View File

@ -12,7 +12,7 @@ test.describe('Point and click for boolean workflows', () => {
},
{
name: 'subtract',
code: 'subtract(extrude001, tools = extrude006)',
code: 'subtract([extrude001], tools = [extrude006])',
},
{
name: 'intersect',
@ -81,8 +81,6 @@ test.describe('Point and click for boolean workflows', () => {
if (operationName !== 'subtract') {
// should down shift key to select multiple objects
await page.keyboard.down('Shift')
} else {
await cmdBar.progressCmdBar()
}
// Select second object
@ -105,8 +103,8 @@ test.describe('Point and click for boolean workflows', () => {
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Solids: '1 path',
Tools: '1 path',
Tool: '1 path',
Target: '1 path',
},
commandName,
})

View File

@ -288,7 +288,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
// error text on hover
await page.hover('.cm-lint-marker-info')
await expect(
page.getByText('Identifiers should be lowerCamelCase').first()
page.getByText('Identifiers must be lowerCamelCase').first()
).toBeVisible()
await page.locator('#code-pane button:first-child').click()
@ -314,7 +314,7 @@ sketch_001 = startSketchOn(XY)
// error text on hover
await page.hover('.cm-lint-marker-info')
await expect(
page.getByText('Identifiers should be lowerCamelCase').first()
page.getByText('Identifiers must be lowerCamelCase').first()
).toBeVisible()
})
@ -511,7 +511,7 @@ sketch_001 = startSketchOn(XY)
// error text on hover
await page.hover('.cm-lint-marker-info')
await expect(
page.getByText('Identifiers should be lowerCamelCase').first()
page.getByText('Identifiers must be lowerCamelCase').first()
).toBeVisible()
// focus the editor
@ -539,7 +539,7 @@ sketch_001 = startSketchOn(XY)
// error text on hover
await page.hover('.cm-lint-marker-info')
await expect(
page.getByText('Identifiers should be lowerCamelCase').first()
page.getByText('Identifiers must be lowerCamelCase').first()
).toBeVisible()
})
@ -681,7 +681,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
// error text on hover
await page.hover('.cm-lint-marker-info')
await expect(
page.getByText('Identifiers should be lowerCamelCase').first()
page.getByText('Identifiers must be lowerCamelCase').first()
).toBeVisible()
// select the line that's causing the error and delete it

View File

@ -7,7 +7,6 @@ import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
import { expect, test } from '@e2e/playwright/zoo-test'
import { bracket } from '@e2e/playwright/fixtures/bracket'
import type { CmdBarSerialised } from '@e2e/playwright/fixtures/cmdBarFixture'
// test file is for testing point an click code gen functionality that's not sketch mode related
@ -1142,20 +1141,6 @@ openSketch = startSketchOn(XY)
})
})
const initialCmdBarStateHelix: CmdBarSerialised = {
stage: 'arguments',
currentArgKey: 'mode',
currentArgValue: '',
headerArguments: {
Mode: '',
AngleStart: '',
Revolutions: '',
Radius: '',
},
highlightedHeaderArg: 'mode',
commandName: 'Helix',
}
test('Helix point-and-click on default axis', async ({
context,
page,
@ -1165,14 +1150,30 @@ openSketch = startSketchOn(XY)
toolbar,
cmdBar,
}) => {
const expectedOutput = `helix001 = helix( axis = X, radius = 5, length = 5, revolutions = 1, angleStart = 270,)`
// One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 }
const expectedOutput = `helix001 = helix( axis = X, radius = 5, length = 5, revolutions = 1, angleStart = 270, ccw = false,)`
const expectedLine = `axis=X,`
await homePage.goToModelingScene()
await scene.connectionEstablished()
await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click()
await cmdBar.expectState(initialCmdBarStateHelix)
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'mode',
currentArgValue: '',
headerArguments: {
Mode: '',
AngleStart: '',
Revolutions: '',
Radius: '',
CounterClockWise: '',
},
highlightedHeaderArg: 'mode',
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await expect.poll(() => page.getByText('Axis').count()).toBe(6)
await cmdBar.progressCmdBar()
@ -1189,6 +1190,7 @@ openSketch = startSketchOn(XY)
AngleStart: '',
Length: '',
Radius: '',
CounterClockWise: '',
},
commandName: 'Helix',
})
@ -1205,10 +1207,11 @@ openSketch = startSketchOn(XY)
Revolutions: '1',
Length: '5',
Radius: '5',
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.submit()
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
@ -1218,6 +1221,8 @@ openSketch = startSketchOn(XY)
activeLines: [expectedLine],
highlightedCode: '',
})
// Red plane is now gone, white helix is there
await scene.expectPixelColor([250, 250, 250], testPoint, 15)
})
await test.step(`Edit helix through the feature tree`, async () => {
@ -1229,18 +1234,21 @@ openSketch = startSketchOn(XY)
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'length',
currentArgValue: '5',
currentArgKey: 'CounterClockWise',
currentArgValue: '',
headerArguments: {
Axis: 'X',
AngleStart: '270',
Revolutions: '1',
Radius: '5',
Length: initialInput,
CounterClockWise: '',
},
highlightedHeaderArg: 'length',
highlightedHeaderArg: 'CounterClockWise',
})
await page.keyboard.insertText(newInput)
await page.keyboard.press('Shift+Backspace')
await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput.locator('.cm-content').fill(newInput)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
@ -1250,10 +1258,11 @@ openSketch = startSketchOn(XY)
Revolutions: '1',
Radius: '5',
Length: newInput,
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.submit()
await cmdBar.progressCmdBar()
await toolbar.closeFeatureTreePane()
await editor.openPane()
await editor.expectEditor.toContain('length = ' + newInput)
@ -1264,81 +1273,28 @@ openSketch = startSketchOn(XY)
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await scene.settled(cmdBar)
await editor.expectEditor.not.toContain('helix')
await expect(
await toolbar.getFeatureTreeOperation('Helix', 0)
).not.toBeVisible()
// Red plane is back
await scene.expectPixelColor([96, 52, 52], testPoint, 15)
})
})
test(`Helix point-and-click around segment`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [0, 0])
|> yLine(length = 100)
|> line(endAbsolute = [100, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.settled(cmdBar)
await test.step(`Go through the command bar flow`, async () => {
await toolbar.closePane('code')
await toolbar.helixButton.click()
await cmdBar.expectState(initialCmdBarStateHelix)
await cmdBar.selectOption({ name: 'Edge' }).click()
await editor.selectText('yLine(length = 100)')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('1')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('2')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('3')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Mode: 'Edge',
Edge: `1 segment`,
AngleStart: '2',
Revolutions: '1',
Radius: '3',
const helixCases = [
{
selectionType: 'segment',
testPoint: { x: 513, y: 221 },
expectedOutput: `helix001 = helix( axis = seg01, radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
expectedEditedOutput: `helix001 = helix( axis = seg01, radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
},
commandName: 'Helix',
})
await cmdBar.submit()
await scene.settled(cmdBar)
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await toolbar.openPane('code')
await editor.expectEditor.toContain(
`
helix001 = helix(
axis = seg01,
radius = 3,
revolutions = 1,
angleStart = 2,
)`,
{ shouldNormalise: true }
)
await toolbar.closePane('code')
})
})
test(`Helix point-and-click around sweepEdge with edit and delete flows`, async ({
{
selectionType: 'sweepEdge',
testPoint: { x: 564, y: 364 },
expectedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
expectedEditedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
},
]
helixCases.map(
({ selectionType, testPoint, expectedOutput, expectedEditedOutput }) => {
test(`Helix point-and-click around ${selectionType}`, async ({
context,
page,
homePage,
@ -1347,6 +1303,7 @@ profile001 = startProfile(sketch001, at = [0, 0])
toolbar,
cmdBar,
}) => {
page.on('console', console.log)
const initialCode = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [0, 0])
|> yLine(length = 100)
@ -1355,8 +1312,7 @@ profile001 = startProfile(sketch001, at = [0, 0])
|> close()
extrude001 = extrude(profile001, length = 100)`
// One dumb hardcoded screen pixel value to click on the sweepEdge, can't think of another way?
const testPoint = { x: 564, y: 364 }
// One dumb hardcoded screen pixel value
const [clickOnEdge] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
await context.addInitScript((initialCode) => {
@ -1369,14 +1325,30 @@ extrude001 = extrude(profile001, length = 100)`
await test.step(`Go through the command bar flow`, async () => {
await toolbar.closePane('code')
await toolbar.helixButton.click()
await cmdBar.expectState(initialCmdBarStateHelix)
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'mode',
currentArgValue: '',
headerArguments: {
AngleStart: '',
Mode: '',
CounterClockWise: '',
Radius: '',
Revolutions: '',
},
highlightedHeaderArg: 'mode',
commandName: 'Helix',
})
await cmdBar.selectOption({ name: 'Edge' }).click()
await expect
.poll(() => page.getByText('Please select one').count())
.toBe(1)
await clickOnEdge()
await page.waitForTimeout(1000)
await cmdBar.progressCmdBar()
await page.waitForTimeout(1000)
await cmdBar.argumentInput.focus()
await page.waitForTimeout(1000)
await page.keyboard.insertText('20')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('0')
@ -1388,62 +1360,33 @@ extrude001 = extrude(profile001, length = 100)`
stage: 'review',
headerArguments: {
Mode: 'Edge',
Edge: `1 sweepEdge`,
Edge: `1 ${selectionType}`,
AngleStart: '0',
Revolutions: '20',
Radius: '1',
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.submit()
await scene.settled(cmdBar)
await cmdBar.progressCmdBar()
await page.waitForTimeout(1000)
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await toolbar.openPane('code')
await editor.expectEditor.toContain(
`
helix001 = helix(
axis = getOppositeEdge(seg01),
radius = 1,
revolutions = 20,
angleStart = 0,
)`,
{ shouldNormalise: true }
)
await editor.expectEditor.toContain(expectedOutput)
await toolbar.closePane('code')
})
await test.step(`Edit helix through the feature tree`, async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
const operationButton = await toolbar.getFeatureTreeOperation(
'Helix',
0
)
await operationButton.dblclick()
const initialInput = '1'
const newInput = '5'
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'radius',
currentArgValue: initialInput,
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: initialInput,
},
highlightedHeaderArg: 'radius',
})
await page.keyboard.insertText(newInput)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
},
commandName: 'Helix',
})
await cmdBar.clickOptionalArgument('ccw')
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
@ -1452,12 +1395,19 @@ extrude001 = extrude(profile001, length = 100)`
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
Radius: initialInput,
CounterClockWise: '',
},
highlightedHeaderArg: 'CounterClockWise',
})
await cmdBar.selectOption({ name: 'True' }).click()
await page
.getByRole('button', { name: 'radius', exact: false })
.click()
await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput
.locator('.cm-content')
.fill(newInput)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
@ -1468,34 +1418,29 @@ extrude001 = extrude(profile001, length = 100)`
},
commandName: 'Helix',
})
await cmdBar.submit()
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await toolbar.openPane('code')
await editor.expectEditor.toContain(
`
helix001 = helix(
axis = getOppositeEdge(seg01),
radius = 5,
revolutions = 20,
angleStart = 0,
ccw = true,
)`,
{ shouldNormalise: true }
)
await editor.expectEditor.toContain(expectedEditedOutput)
await toolbar.closePane('code')
})
await test.step('Delete helix via feature tree selection', async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
const operationButton = await toolbar.getFeatureTreeOperation(
'Helix',
0
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await editor.expectEditor.not.toContain('helix')
await editor.expectEditor.not.toContain(expectedEditedOutput)
await expect(
await toolbar.getFeatureTreeOperation('Helix', 0)
).not.toBeVisible()
})
})
}
)
test('Helix point-and-click on cylinder', async ({
context,
@ -1525,12 +1470,26 @@ extrude001 = extrude(profile001, length = 100)
// One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 }
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const expectedOutput = `helix001 = helix(cylinder = extrude001, revolutions = 1, angleStart = 360)`
const expectedEditedOutput = `helix001 = helix(cylinder = extrude001, revolutions = 1, angleStart = 10)`
const expectedOutput = `helix001 = helix( cylinder = extrude001, revolutions = 1, angleStart = 360, ccw = false,)`
const expectedLine = `cylinder = extrude001,`
const expectedEditedOutput = `helix001 = helix( cylinder = extrude001, revolutions = 1, angleStart = 360, ccw = true,)`
await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click()
await cmdBar.expectState(initialCmdBarStateHelix)
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'mode',
currentArgValue: '',
headerArguments: {
Mode: '',
AngleStart: '',
Revolutions: '',
Radius: '',
CounterClockWise: '',
},
highlightedHeaderArg: 'mode',
commandName: 'Helix',
})
await cmdBar.selectOption({ name: 'Cylinder' }).click()
await cmdBar.expectState({
stage: 'arguments',
@ -1541,6 +1500,7 @@ extrude001 = extrude(profile001, length = 100)
Cylinder: '',
AngleStart: '',
Revolutions: '',
CounterClockWise: '',
},
highlightedHeaderArg: 'cylinder',
commandName: 'Helix',
@ -1556,17 +1516,18 @@ extrude001 = extrude(profile001, length = 100)
Cylinder: '1 face',
AngleStart: '360',
Revolutions: '1',
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.submit()
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await editor.expectEditor.toContain(expectedOutput)
await editor.expectState({
diagnostics: [],
activeLines: [expectedOutput],
activeLines: [expectedLine],
highlightedCode: '',
})
})
@ -1578,21 +1539,22 @@ extrude001 = extrude(profile001, length = 100)
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'angleStart',
currentArgValue: '360',
currentArgKey: 'CounterClockWise',
currentArgValue: '',
headerArguments: {
AngleStart: '360',
Revolutions: '1',
CounterClockWise: '',
},
highlightedHeaderArg: 'angleStart',
highlightedHeaderArg: 'CounterClockWise',
})
await page.keyboard.insertText('10')
await cmdBar.progressCmdBar()
await cmdBar.selectOption({ name: 'True' }).click()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '10',
AngleStart: '360',
Revolutions: '1',
CounterClockWise: 'true',
},
commandName: 'Helix',
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

View File

@ -74,7 +74,7 @@
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
ELECTRON_OVERRIDE_DIST_PATH =
if pkgs.stdenv.isDarwin
then "${pkgs.electron}/Applications"
then "${pkgs.electron}/Applications/Electron.app/Contents/MacOS/"
else "${pkgs.electron}/bin";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";

View File

@ -15,14 +15,14 @@ import "car-tire.kcl" as carTire
import * from "parameters.kcl"
// Place the car rotor
carRotor
rotor = carRotor
|> translate(x = 0, y = 0.5, z = 0)
// Place the car wheel
carWheel
// Place the lug nuts
lugNut
lgnut = lugNut
|> patternCircular3d(
arcDegrees = 360,
axis = [0, 1, 0],
@ -32,8 +32,19 @@ lugNut
)
// Place the brake caliper
brakeCaliper
cal = brakeCaliper
|> translate(x = 0, y = 0.5, z = 0)
// Place the car tire
carTire
fn animate(step: number(_)) {
angle = 0.6deg
rotate(rotor, pitch = angle)
rotate(lgnut, pitch = angle)
rotate(cal, pitch = angle)
rotate(carWheel, pitch = angle)
rotate(carTire, pitch = angle)
return 0
}

View File

@ -369,7 +369,7 @@ profile007 = startProfile(
|> line(%, endAbsolute = [profileStartX(%), profileStartY(%)])
|> close(%)
profile008 = circle(sketch005, center = [0, 0], diameter = nubDiameter)
subtract2d(profile007, tool = profile008)
hourHand = subtract2d(profile007, tool = profile008)
|> extrude(%, length = 5)
|> appearance(%, color = "#404040")
@ -413,7 +413,7 @@ profile009 = startProfile(
|> line(%, endAbsolute = [profileStartX(%), profileStartY(%)])
|> close(%)
profile010 = circle(sketch006, center = [0, 0], diameter = 30)
subtract2d(profile009, tool = profile010)
minuteHand = subtract2d(profile009, tool = profile010)
|> extrude(%, length = 5)
|> appearance(%, color = "#404040")
@ -439,3 +439,8 @@ profile004 = startProfile(sketch003, at = [-slotWidth / 2, 200])
|> extrude(%, length = -20)
// todo: create cavity for the screw to slide into (need csg update)
fn animate(step: number(_)) {
rotate(hourHand, yaw = -0.1deg)
return rotate(minuteHand, yaw = -0.6deg)
}

20
rust/Cargo.lock generated
View File

@ -1814,7 +1814,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.83"
version = "0.1.82"
dependencies = [
"anyhow",
"clap",
@ -1825,7 +1825,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.83"
version = "0.1.82"
dependencies = [
"proc-macro2",
"quote",
@ -1834,7 +1834,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.83"
version = "0.1.82"
dependencies = [
"convert_case",
"proc-macro2",
@ -1844,7 +1844,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.83"
version = "0.2.82"
dependencies = [
"anyhow",
"clap",
@ -1865,7 +1865,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.83"
version = "0.1.82"
dependencies = [
"anyhow",
"clap",
@ -1885,7 +1885,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.83"
version = "0.2.82"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1962,7 +1962,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.83"
version = "0.3.82"
dependencies = [
"anyhow",
"kcl-lib",
@ -1977,7 +1977,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.83"
version = "0.1.82"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -1990,7 +1990,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.83"
version = "0.1.82"
dependencies = [
"anyhow",
"async-trait",
@ -2004,7 +2004,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.83"
version = "0.1.82"
dependencies = [
"anyhow",
"bson",

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-bumper"
version = "0.1.83"
version = "0.1.82"
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.83"
version = "0.1.82"
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.83"
version = "0.1.82"
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.83"
version = "0.1.82"
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.83"
version = "0.2.82"
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.83"
version = "0.2.82"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -17,14 +17,11 @@ use crate::{
},
fmt,
modules::{ModuleId, ModulePath, ModuleRepr},
parsing::{
ast::types::{
parsing::ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier,
LiteralValue, MemberExpression, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program,
TagDeclarator, Type, UnaryExpression, UnaryOperator,
},
token::NumericSuffix,
LiteralValue, MemberExpression, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program, TagDeclarator,
Type, UnaryExpression, UnaryOperator,
},
source_range::SourceRange,
std::args::TyF64,
@ -1669,18 +1666,12 @@ impl Property {
LiteralIdentifier::Literal(literal) => {
let value = literal.value.clone();
match value {
n @ LiteralValue::Number { value, suffix } => {
if !matches!(suffix, NumericSuffix::None | NumericSuffix::Count) {
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("{n} is not a valid index, indices must be non-dimensional numbers"),
property_sr,
)));
}
LiteralValue::Number { value, .. } => {
if let Some(x) = crate::try_f64_to_usize(value) {
Ok(Property::UInt(x))
} else {
Err(KclError::new_semantic(KclErrorDetails::new(
format!("{n} is not a valid index, indices must be whole numbers >= 0"),
format!("{value} is not a valid index, indices must be whole numbers >= 0"),
property_sr,
)))
}
@ -1699,13 +1690,10 @@ fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -
let make_err =
|message: String| Err::<Property, _>(KclError::new_semantic(KclErrorDetails::new(message, property_sr)));
match value {
n @ KclValue::Number{value: num, ty, .. } => {
if !matches!(ty, NumericType::Known(crate::exec::UnitType::Count) | NumericType::Default { .. } | NumericType::Any ) {
return make_err(format!("arrays can only be indexed by non-dimensioned numbers, found {}", n.human_friendly_type()));
}
KclValue::Number{value: num, .. } => {
let num = *num;
if num < 0.0 {
return make_err(format!("'{num}' is negative, so you can't index an array with it"));
return make_err(format!("'{num}' is negative, so you can't index an array with it"))
}
let nearest_int = crate::try_f64_to_usize(num);
if let Some(nearest_int) = nearest_int {
@ -2153,23 +2141,4 @@ c = ((PI * 2) / 3): number(deg)
let result = parse_execute(ast).await.unwrap();
assert_eq!(result.exec_state.errors().len(), 2);
}
#[tokio::test(flavor = "multi_thread")]
async fn non_count_indexing() {
let ast = r#"x = [0, 0]
y = x[1mm]
"#;
parse_execute(ast).await.unwrap_err();
let ast = r#"x = [0, 0]
y = 1deg
z = x[y]
"#;
parse_execute(ast).await.unwrap_err();
let ast = r#"x = [0, 0]
y = x[0mm + 1]
"#;
parse_execute(ast).await.unwrap_err();
}
}

View File

@ -805,6 +805,43 @@ impl ExecutorContext {
Ok(outcome)
}
pub async fn run_additional(&self, program: crate::Program) -> Result<ExecOutcome, KclErrorWithOutputs> {
assert!(!self.is_mock());
let (program, exec_state, result) = match cache::read_old_ast().await {
Some(cached_state) => {
let mut exec_state = cached_state.reconstitute_exec_state();
exec_state.mut_stack().restore_env(cached_state.main.result_env);
let result = self.run_concurrent(&program, &mut exec_state, None, true).await;
(program, exec_state, result)
}
None => {
let mut exec_state = ExecState::new(self);
let result = self.run_concurrent(&program, &mut exec_state, None, false).await;
(program, exec_state, result)
}
};
// Throw the error.
let result = result?;
// Save this as the last successful execution to the cache.
cache::write_old_ast(GlobalState::new(
exec_state.clone(),
self.settings.clone(),
program.ast,
result.0,
))
.await;
let outcome = exec_state.into_exec_outcome(result.0, self).await;
Ok(outcome)
}
/// Perform the execution of a program.
///
/// To access non-fatal errors and warnings, extract them from the `ExecState`.

View File

@ -11,10 +11,10 @@ use crate::{
def_finding!(
Z0001,
"Identifiers should be lowerCamelCase",
"Identifiers must be lowerCamelCase",
"\
By convention, variable names are lowerCamelCase, not snake_case, kebab-case,
nor upper CamelCase (aka PascalCase). 🐪
nor CammelCase. 🐪
For instance, a good identifier for the variable representing 'box height'
would be 'boxHeight', not 'BOX_HEIGHT', 'box_height' nor 'BoxHeight'. For

View File

@ -2359,7 +2359,7 @@ async fn test_kcl_lsp_diagnostic_has_lints() {
assert_eq!(diagnostics.full_document_diagnostic_report.items.len(), 1);
assert_eq!(
diagnostics.full_document_diagnostic_report.items[0].message,
"Identifiers should be lowerCamelCase"
"Identifiers must be lowerCamelCase"
);
} else {
panic!("Expected full diagnostics");

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-python-bindings"
version = "0.3.83"
version = "0.3.82"
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.83"
version = "0.1.82"
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.83"
version = "0.1.82"
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.83"
version = "0.1.82"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-app"
rust-version = "1.83"

View File

@ -111,6 +111,48 @@ impl Context {
ctx.run_with_caching(program).await
}
/// Execute an additional program using the cache.
#[wasm_bindgen(js_name = executeAdditional)]
pub async fn execute_additional(
&self,
program_ast_json: &str,
path: Option<String>,
settings: &str,
) -> Result<JsValue, JsValue> {
console_error_panic_hook::set_once();
self.execute_additional_typed(program_ast_json, path, settings)
.await
.and_then(|outcome| {
JsValue::from_serde(&outcome).map_err(|e| {
// The serde-wasm-bindgen does not work here because of weird HashMap issues.
// DO NOT USE serde_wasm_bindgen::to_value it will break the frontend.
KclErrorWithOutputs::no_outputs(KclError::internal(format!(
"Could not serialize successful KCL result. {TRUE_BUG} Details: {e}"
)))
})
})
.map_err(|e: KclErrorWithOutputs| JsValue::from_serde(&e).unwrap())
}
async fn execute_additional_typed(
&self,
program_ast_json: &str,
path: Option<String>,
settings: &str,
) -> Result<ExecOutcome, KclErrorWithOutputs> {
let program: Program = serde_json::from_str(program_ast_json).map_err(|e| {
let err = KclError::internal(format!("Could not deserialize KCL AST. {TRUE_BUG} Details: {e}"));
KclErrorWithOutputs::no_outputs(err)
})?;
let ctx = self.create_executor_ctx(settings, path, false).map_err(|e| {
KclErrorWithOutputs::no_outputs(KclError::internal(format!(
"Could not create KCL executor context. {TRUE_BUG} Details: {e}"
)))
})?;
ctx.run_additional(program).await
}
/// Reset the scene and bust the cache.
/// ONLY use this if you absolutely need to reset the scene and bust the cache.
#[wasm_bindgen(js_name = bustCacheAndResetScene)]

View File

@ -7,6 +7,8 @@ import type { CustomIconName } from '@src/components/CustomIcon'
import Tooltip from '@src/components/Tooltip'
import styles from './ModelingPane.module.css'
import { reportRejection } from '@src/lib/trap'
import { kclManager } from '@src/lib/singletons'
export interface ModelingPaneProps {
id: string
@ -19,6 +21,28 @@ export interface ModelingPaneProps {
onClose: () => void
}
const ANIMATE_INTERVAL = 16 // milliseconds
let animateTimeout: NodeJS.Timeout | null = null
function doAnimate() {
;(async () => {
await kclManager.executeAnimate()
if (animateTimeout !== null) {
animateTimeout = setTimeout(doAnimate, ANIMATE_INTERVAL)
}
})().catch(reportRejection)
}
function onPlay() {
console.log('Play button clicked')
if (animateTimeout) {
clearTimeout(animateTimeout)
animateTimeout = null
} else {
animateTimeout = setTimeout(doAnimate, ANIMATE_INTERVAL)
}
}
export const ModelingPaneHeader = ({
id,
icon,
@ -40,6 +64,20 @@ export const ModelingPaneHeader = ({
)}
<span data-testid={id + '-header'}>{title}</span>
</div>
{id === 'code' && (
<ActionButton
Element="button"
iconStart={{
icon: 'play',
iconClassName: '!text-current',
bgClassName: 'bg-transparent dark:bg-transparent',
}}
className="!p-0 !bg-transparent hover:text-primary border-transparent dark:!border-transparent hover:!border-primary dark:hover:!border-chalkboard-70 !outline-none"
onClick={() => onPlay()}
>
<Tooltip position="bottom-right">Play</Tooltip>
</ActionButton>
)}
{Menu instanceof Function ? <Menu /> : Menu}
<ActionButton
Element="button"

View File

@ -17,7 +17,12 @@ import {
compilationErrorsToDiagnostics,
kclErrorsToDiagnostics,
} from '@src/lang/errors'
import { executeAst, executeAstMock, lintAst } from '@src/lang/langHelpers'
import {
executeAdditional,
executeAst,
executeAstMock,
lintAst,
} from '@src/lang/langHelpers'
import { getNodeFromPath, getSettingsAnnotation } from '@src/lang/queryAst'
import { CommandLogType } from '@src/lang/std/commandLog'
import type { EngineCommandManager } from '@src/lang/std/engineConnection'
@ -106,6 +111,7 @@ export class KclManager extends EventTarget {
preComments: [],
commentStart: 0,
}
private _animateState = { step: 0 }
private _execState: ExecState = emptyExecState()
private _variables: VariableMap = {}
lastSuccessfulVariables: VariableMap = {}
@ -450,6 +456,7 @@ export class KclManager extends EventTarget {
const ast = args.ast || this.ast
markOnce('code/startExecuteAst')
this._animateState.step = 0
const currentExecutionId = args.executionId || Date.now()
this._cancelTokens.set(currentExecutionId, false)
@ -541,6 +548,39 @@ export class KclManager extends EventTarget {
markOnce('code/endExecuteAst')
}
async executeAnimate(): Promise<void> {
if (this.isExecuting) {
return
}
const code = `animate(step = ${this._animateState.step})`
const result = parse(code)
if (err(result)) {
console.error(result)
return
}
const program = result.program
if (!program) {
console.error('No program returned from parse')
return
}
const { errors } = await executeAdditional({
ast: program,
path: this.singletons.codeManager.currentFilePath || undefined,
rustContext: this.singletons.rustContext,
})
if (errors.length > 0) {
console.error('Errors executing animate:', errors)
return
}
this._animateState.step += 1
if (this._animateState.step === Number.MAX_SAFE_INTEGER) {
this._animateState.step = 0
}
}
// DO NOT CALL THIS from codemirror ever.
async executeAstMock(ast: Program): Promise<null | Error> {
await this.ensureWasmInit()

View File

@ -84,6 +84,31 @@ export async function executeAst({
}
}
export async function executeAdditional({
ast,
rustContext,
path,
}: {
ast: Node<Program>
rustContext: RustContext
path?: string
}): Promise<ExecutionResult> {
try {
const settings = await jsAppSettings()
const execState = await rustContext.executeAdditional(ast, settings, path)
await rustContext.waitForAllEngineCommands()
return {
logs: [],
errors: [],
execState,
isInterrupted: false,
}
} catch (e: any) {
return handleExecuteError(e)
}
}
export async function executeAstMock({
ast,
rustContext,

View File

@ -590,7 +590,7 @@ export function addHelix({
angleStart: Expr
radius?: Expr
length?: Expr
ccw?: boolean
ccw: boolean
insertIndex?: number
variableName?: string
}): { modifiedAst: Node<Program>; pathToNode: PathToNode } {
@ -610,9 +610,6 @@ export function addHelix({
)
}
// Extra labeled args expressions
const ccwExpr = ccw ? [createLabeledArg('ccw', createLiteral(ccw))] : []
const variable = createVariableDeclaration(
name,
createCallExpressionStdLibKw(
@ -622,7 +619,7 @@ export function addHelix({
...modeArgs,
createLabeledArg('revolutions', revolutions),
createLabeledArg('angleStart', angleStart),
...ccwExpr,
createLabeledArg('ccw', createLiteral(ccw)),
]
)
)

View File

@ -23,13 +23,13 @@ import type {
VariableDeclaration,
} from '@src/lang/wasm'
import { EXECUTION_TYPE_REAL } from '@src/lib/constants'
import type { Selections } from '@src/lib/selections'
import type { Selection, Selections } from '@src/lib/selections'
import { err } from '@src/lib/trap'
import { isArray } from '@src/lib/utils'
export async function applySubtractFromTargetOperatorSelections(
solids: Selections,
tools: Selections,
target: Selection,
tool: Selection,
dependencies: {
kclManager: KclManager
engineCommandManager: EngineCommandManager
@ -38,28 +38,28 @@ export async function applySubtractFromTargetOperatorSelections(
}
): Promise<Error | void> {
const ast = dependencies.kclManager.ast
const lastSolidsVars = getLastVariableDeclarationsFromSelections(
solids,
ast,
if (!target.artifact || !tool.artifact) {
return new Error('No artifact found')
}
const orderedChildrenTarget = findAllChildrenAndOrderByPlaceInCode(
target.artifact,
dependencies.kclManager.artifactGraph
)
if (err(lastSolidsVars) || lastSolidsVars.length < 1) {
return new Error('Not enough or invalid solids variables found')
}
const lastToolsVars = getLastVariableDeclarationsFromSelections(
tools,
ast,
const orderedChildrenTool = findAllChildrenAndOrderByPlaceInCode(
tool.artifact,
dependencies.kclManager.artifactGraph
)
if (err(lastToolsVars) || lastToolsVars.length < 1) {
return new Error('Not enough or invalid tools variables found')
}
const lastVarTarget = getLastVariable(orderedChildrenTarget, ast)
const lastVarTool = getLastVariable(orderedChildrenTool, ast)
if (!lastVarTarget || !lastVarTool) {
return new Error('No variable found')
}
const modifiedAst = booleanSubtractAstMod({
ast,
solids: lastSolidsVars,
tools: lastToolsVars,
targets: [lastVarTarget?.variableDeclaration?.node],
tools: [lastVarTool?.variableDeclaration.node],
})
await updateModelingState(modifiedAst, EXECUTION_TYPE_REAL, dependencies)
@ -75,13 +75,34 @@ export async function applyUnionFromTargetOperatorSelections(
}
): Promise<Error | void> {
const ast = dependencies.kclManager.ast
const lastVars = getLastVariableDeclarationsFromSelections(
solids,
ast,
const artifacts: Artifact[] = []
for (const selection of solids.graphSelections) {
if (selection.artifact) {
artifacts.push(selection.artifact)
}
}
if (artifacts.length < 2) {
return new Error('Not enough artifacts selected')
}
const orderedChildrenEach = artifacts.map((artifact) =>
findAllChildrenAndOrderByPlaceInCode(
artifact,
dependencies.kclManager.artifactGraph
)
if (err(lastVars) || lastVars.length < 2) {
return new Error('Not enough or invalid solids variables found')
)
const lastVars: VariableDeclaration[] = []
for (const orderedArtifactLeaves of orderedChildrenEach) {
const lastVar = getLastVariable(orderedArtifactLeaves, ast)
if (!lastVar) continue
lastVars.push(lastVar.variableDeclaration.node)
}
if (lastVars.length < 2) {
return new Error('Not enough variables found')
}
const modifiedAst = booleanUnionAstMod({
@ -101,36 +122,23 @@ export async function applyIntersectFromTargetOperatorSelections(
}
): Promise<Error | void> {
const ast = dependencies.kclManager.ast
const lastVars = getLastVariableDeclarationsFromSelections(
solids,
ast,
dependencies.kclManager.artifactGraph
)
if (err(lastVars) || lastVars.length < 2) {
return new Error('Not enough or invalid solids variables found')
}
const modifiedAst = booleanIntersectAstMod({
ast,
solids: lastVars,
})
await updateModelingState(modifiedAst, EXECUTION_TYPE_REAL, dependencies)
}
function getLastVariableDeclarationsFromSelections(
selections: Selections,
ast: Node<Program>,
artifactGraph: ArtifactGraph
): Error | VariableDeclaration[] {
const artifacts: Artifact[] = []
for (const selection of selections.graphSelections) {
for (const selection of solids.graphSelections) {
if (selection.artifact) {
artifacts.push(selection.artifact)
}
}
if (artifacts.length < 2) {
return new Error('Not enough artifacts selected')
}
const orderedChildrenEach = artifacts.map((artifact) =>
findAllChildrenAndOrderByPlaceInCode(artifact, artifactGraph)
findAllChildrenAndOrderByPlaceInCode(
artifact,
dependencies.kclManager.artifactGraph
)
)
const lastVars: VariableDeclaration[] = []
@ -140,7 +148,15 @@ function getLastVariableDeclarationsFromSelections(
lastVars.push(lastVar.variableDeclaration.node)
}
return lastVars
if (lastVars.length < 2) {
return new Error('Not enough variables found')
}
const modifiedAst = booleanIntersectAstMod({
ast,
solids: lastVars,
})
await updateModelingState(modifiedAst, EXECUTION_TYPE_REAL, dependencies)
}
/** returns all children of a given artifact, and sorts them DESC by start sourceRange
@ -255,27 +271,25 @@ export function getLastVariable(
export function booleanSubtractAstMod({
ast,
solids,
targets,
tools,
}: {
ast: Node<Program>
solids: VariableDeclaration[]
targets: VariableDeclaration[]
tools: VariableDeclaration[]
}): Node<Program> {
const newAst = structuredClone(ast)
const newVarName = findUniqueName(newAst, 'solid')
const createArrExpr = (varDecs: VariableDeclaration[]) => {
const names = varDecs.map((varDec) =>
createLocalName(varDec.declaration.id.name)
const createArrExpr = (varDecs: VariableDeclaration[]) =>
createArrayExpression(
varDecs.map((varDec) => createLocalName(varDec.declaration.id.name))
)
return names.length === 1 ? names[0] : createArrayExpression(names)
}
const solidsArrayExpression = createArrExpr(solids)
const targetsArrayExpression = createArrExpr(targets)
const toolsArrayExpression = createArrExpr(tools)
const newVarDec = createVariableDeclaration(
newVarName,
createCallExpressionStdLibKw('subtract', solidsArrayExpression, [
createCallExpressionStdLibKw('subtract', targetsArrayExpression, [
createLabeledArg('tools', toolsArrayExpression),
])
)

View File

@ -204,8 +204,8 @@ export type ModelingCommandSchema = {
variableName: string
}
'Boolean Subtract': {
solids: Selections
tools: Selections
target: Selections
tool: Selections
}
'Boolean Union': {
solids: Selections
@ -595,21 +595,23 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
icon: 'booleanSubtract',
needsReview: true,
args: {
solids: {
target: {
inputType: 'selection',
selectionTypes: ['path'],
selectionFilter: ['object'],
multiple: true,
multiple: false,
required: true,
skip: true,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
tools: {
tool: {
clearSelectionFirst: true,
inputType: 'selection',
selectionTypes: ['path'],
selectionFilter: ['object'],
multiple: true,
multiple: false,
required: true,
skip: false,
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
},
@ -688,35 +690,32 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
},
axis: {
inputType: 'options',
required: (commandContext) =>
['Axis'].includes(commandContext.argumentsToSubmit.mode as string),
options: [
{ name: 'X Axis', value: 'X' },
{ name: 'Y Axis', value: 'Y' },
{ name: 'Z Axis', value: 'Z' },
],
required: (context) =>
['Axis'].includes(context.argumentsToSubmit.mode as string),
hidden: (context) =>
!['Axis'].includes(context.argumentsToSubmit.mode as string),
hidden: false, // for consistency here, we can actually edit here since it's not a selection
},
edge: {
required: (commandContext) =>
['Edge'].includes(commandContext.argumentsToSubmit.mode as string),
inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge'],
multiple: false,
required: (context) =>
['Edge'].includes(context.argumentsToSubmit.mode as string),
hidden: (context) =>
Boolean(context.argumentsToSubmit.nodeToEdit) ||
!['Edge'].includes(context.argumentsToSubmit.mode as string),
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
cylinder: {
required: (commandContext) =>
['Cylinder'].includes(
commandContext.argumentsToSubmit.mode as string
),
inputType: 'selection',
selectionTypes: ['wall'],
multiple: false,
required: (context) =>
['Cylinder'].includes(context.argumentsToSubmit.mode as string),
hidden: (context) =>
Boolean(context.argumentsToSubmit.nodeToEdit) ||
!['Cylinder'].includes(context.argumentsToSubmit.mode as string),
hidden: (context) => Boolean(context.argumentsToSubmit.nodeToEdit),
},
revolutions: {
inputType: 'kcl',
@ -731,30 +730,34 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
radius: {
inputType: 'kcl',
defaultValue: KCL_DEFAULT_LENGTH,
required: (context) =>
!['Cylinder'].includes(context.argumentsToSubmit.mode as string),
hidden: (context) =>
['Cylinder'].includes(context.argumentsToSubmit.mode as string),
required: (commandContext) =>
!['Cylinder'].includes(
commandContext.argumentsToSubmit.mode as string
),
},
length: {
inputType: 'kcl',
defaultValue: KCL_DEFAULT_LENGTH,
required: (commandContext) =>
['Axis'].includes(commandContext.argumentsToSubmit.mode as string),
// No need for hidden here, as it works with all modes
},
ccw: {
inputType: 'options',
required: false,
skip: true,
required: true,
defaultValue: false,
valueSummary: (value) => String(value),
displayName: 'CounterClockWise',
options: [
options: (commandContext) => [
{
name: 'False',
value: false,
isCurrent: !Boolean(commandContext.argumentsToSubmit.ccw),
},
{
name: 'True',
value: true,
isCurrent: Boolean(commandContext.argumentsToSubmit.ccw),
},
],
},

View File

@ -222,6 +222,3 @@ export const CODE_QUERY_PARAM = 'code'
/** A query parameter to skip the sign-on view if unnecessary. */
export const IMMEDIATE_SIGN_IN_IF_NECESSARY_QUERY_PARAM =
'immediate-sign-in-if-necessary'
// Only used by the desktop app
export const OAUTH2_DEVICE_CLIENT_ID = '2af127fb-e14e-400a-9c57-a9ed08d1a5b7'

View File

@ -96,6 +96,34 @@ export default class RustContext {
}
}
/** Execute an additional program using the cache. */
async executeAdditional(
node: Node<Program>,
settings: DeepPartial<Configuration>,
path?: string
): Promise<ExecState> {
const instance = await this._checkInstance()
try {
const result = await instance.executeAdditional(
JSON.stringify(node),
path,
JSON.stringify(settings)
)
// Set the default planes, safe to call after execute.
const outcome = execStateFromRust(result)
this._defaultPlanes = outcome.defaultPlanes
// Return the result.
return outcome
} catch (e: any) {
const err = errFromErrWithOutputs(e)
this._defaultPlanes = err.defaultPlanes
return Promise.reject(err)
}
}
/** Execute a program with in mock mode. */
async executeMock(
node: Node<Program>,

View File

@ -7,7 +7,7 @@ import {
} from '@src/env'
import { assign, fromPromise, setup } from 'xstate'
import { COOKIE_NAME, OAUTH2_DEVICE_CLIENT_ID } from '@src/lib/constants'
import { COOKIE_NAME } from '@src/lib/constants'
import {
getUser as getUserDesktop,
readTokenFile,
@ -254,33 +254,9 @@ async function getAndSyncStoredToken(input: {
async function logout() {
localStorage.removeItem(TOKEN_PERSIST_KEY)
if (isDesktop()) {
try {
let token = await readTokenFile()
if (token) {
try {
await fetch(withBaseUrl('/oauth2/token/revoke'), {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
token: token,
client_id: OAUTH2_DEVICE_CLIENT_ID,
}).toString(),
})
} catch (e) {
console.error('Error revoking token:', e)
}
await writeTokenFile('')
return Promise.resolve(null)
}
} catch (e) {
console.error('Error reading token during logout (ignoring):', e)
}
}
return fetch(withBaseUrl('/logout'), {
method: 'POST',

View File

@ -3570,17 +3570,17 @@ export const modelingMachine = setup({
return Promise.reject(new Error(NO_INPUT_PROVIDED_MESSAGE))
}
const { solids, tools } = input
const { target, tool } = input
if (
!solids.graphSelections.some((selection) => selection.artifact) ||
!tools.graphSelections.some((selection) => selection.artifact)
!target.graphSelections[0].artifact ||
!tool.graphSelections[0].artifact
) {
return Promise.reject(new Error('No artifact in selections found'))
}
const result = await applySubtractFromTargetOperatorSelections(
solids,
tools,
await applySubtractFromTargetOperatorSelections(
target.graphSelections[0],
tool.graphSelections[0],
{
kclManager,
codeManager,
@ -3588,9 +3588,6 @@ export const modelingMachine = setup({
editorManager,
}
)
if (err(result)) {
return Promise.reject(result)
}
}
),
boolUnionAstMod: fromPromise(
@ -3608,15 +3605,12 @@ export const modelingMachine = setup({
return Promise.reject(new Error('No artifact in selections found'))
}
const result = await applyUnionFromTargetOperatorSelections(solids, {
await applyUnionFromTargetOperatorSelections(solids, {
kclManager,
codeManager,
engineCommandManager,
editorManager,
})
if (err(result)) {
return Promise.reject(result)
}
}
),
boolIntersectAstMod: fromPromise(
@ -3634,18 +3628,12 @@ export const modelingMachine = setup({
return Promise.reject(new Error('No artifact in selections found'))
}
const result = await applyIntersectFromTargetOperatorSelections(
solids,
{
await applyIntersectFromTargetOperatorSelections(solids, {
kclManager,
codeManager,
engineCommandManager,
editorManager,
}
)
if (err(result)) {
return Promise.reject(result)
}
})
}
),

View File

@ -15,7 +15,6 @@ import {
dialog,
ipcMain,
nativeTheme,
session,
screen,
shell,
systemPreferences,
@ -29,10 +28,7 @@ import {
parseCLIArgs,
} from '@src/commandLineArgs'
import { initPromiseNode } from '@src/lang/wasmUtilsNode'
import {
ZOO_STUDIO_PROTOCOL,
OAUTH2_DEVICE_CLIENT_ID,
} from '@src/lib/constants'
import { ZOO_STUDIO_PROTOCOL } from '@src/lib/constants'
import getCurrentProjectFile from '@src/lib/getCurrentProjectFile'
import { reportRejection } from '@src/lib/trap'
import {
@ -297,21 +293,7 @@ app.on('window-all-closed', () => {
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', async (event, data) => {
try {
await session.defaultSession.cookies.set({
url: 'https://api.dev.zoo.dev',
name: 'preview-pr-2718',
value: 'always',
domain: '.dev.zoo.dev',
path: '/',
secure: true,
sameSite: 'no_restriction',
})
console.log('[preview] cookie seeded')
} catch (err) {
console.error('[preview] failed to set cookie', err)
}
app.on('ready', (event, data) => {
// Avoid potentially 2 ready fires
if (mainWindow) return
// Create the mainWindow
@ -420,7 +402,7 @@ ipcMain.handle('startDeviceFlow', async (_, host: string) => {
// We can hardcode the client ID.
// This value is safe to be embedded in version control.
// This is the client ID of the KittyCAD app.
client_id: OAUTH2_DEVICE_CLIENT_ID,
client_id: '2af127fb-e14e-400a-9c57-a9ed08d1a5b7',
token_endpoint_auth_method: 'none',
})