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,238 +1273,174 @@ 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)
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,)`,
},
{
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,
scene,
editor,
toolbar,
cmdBar,
}) => {
page.on('console', console.log)
const initialCode = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [0, 0])
|> yLine(length = 100)
|> line(endAbsolute = [100, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
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.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',
},
commandName: 'Helix',
// One dumb hardcoded screen pixel value
const [clickOnEdge] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
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({
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')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('1')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('100')
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Mode: 'Edge',
Edge: `1 ${selectionType}`,
AngleStart: '0',
Revolutions: '20',
Radius: '1',
CounterClockWise: '',
},
commandName: 'Helix',
})
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(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
)
await operationButton.dblclick()
const initialInput = '1'
const newInput = '5'
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'CounterClockWise',
currentArgValue: '',
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: initialInput,
CounterClockWise: '',
},
highlightedHeaderArg: 'CounterClockWise',
})
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: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await toolbar.openPane('code')
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
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await editor.expectEditor.not.toContain(expectedEditedOutput)
await expect(
await toolbar.getFeatureTreeOperation('Helix', 0)
).not.toBeVisible()
})
})
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 ({
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()
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 }
const [clickOnEdge] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
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 expect
.poll(() => page.getByText('Please select one').count())
.toBe(1)
await clickOnEdge()
await cmdBar.progressCmdBar()
await cmdBar.argumentInput.focus()
await page.keyboard.insertText('20')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('0')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('1')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('100')
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Mode: 'Edge',
Edge: `1 sweepEdge`,
AngleStart: '0',
Revolutions: '20',
Radius: '1',
},
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 = getOppositeEdge(seg01),
radius = 1,
revolutions = 20,
angleStart = 0,
)`,
{ shouldNormalise: true }
)
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)
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',
currentArgKey: 'CounterClockWise',
currentArgValue: '',
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
CounterClockWise: '',
},
highlightedHeaderArg: 'CounterClockWise',
})
await cmdBar.selectOption({ name: 'True' }).click()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
CounterClockWise: '',
},
commandName: 'Helix',
})
await cmdBar.submit()
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 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)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await editor.expectEditor.not.toContain('helix')
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::{
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,
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,
},
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,
dependencies.kclManager.artifactGraph
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,32 +254,8 @@ 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)
}
await writeTokenFile('')
return Promise.resolve(null)
}
return fetch(withBaseUrl('/logout'), {

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,
{
kclManager,
codeManager,
engineCommandManager,
editorManager,
}
)
if (err(result)) {
return Promise.reject(result)
}
await applyIntersectFromTargetOperatorSelections(solids, {
kclManager,
codeManager,
engineCommandManager,
editorManager,
})
}
),

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',
})