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 - name: Authenticate to Google Cloud
if: ${{ env.IS_STAGING == 'true' }} if: ${{ env.IS_STAGING == 'true' }}
uses: 'google-github-actions/auth@v2.1.10' uses: 'google-github-actions/auth@v2.1.8'
with: with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}' credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'

View File

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

View File

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

View File

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

View File

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

View File

@ -288,7 +288,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-info') await page.hover('.cm-lint-marker-info')
await expect( await expect(
page.getByText('Identifiers should be lowerCamelCase').first() page.getByText('Identifiers must be lowerCamelCase').first()
).toBeVisible() ).toBeVisible()
await page.locator('#code-pane button:first-child').click() await page.locator('#code-pane button:first-child').click()
@ -314,7 +314,7 @@ sketch_001 = startSketchOn(XY)
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-info') await page.hover('.cm-lint-marker-info')
await expect( await expect(
page.getByText('Identifiers should be lowerCamelCase').first() page.getByText('Identifiers must be lowerCamelCase').first()
).toBeVisible() ).toBeVisible()
}) })
@ -511,7 +511,7 @@ sketch_001 = startSketchOn(XY)
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-info') await page.hover('.cm-lint-marker-info')
await expect( await expect(
page.getByText('Identifiers should be lowerCamelCase').first() page.getByText('Identifiers must be lowerCamelCase').first()
).toBeVisible() ).toBeVisible()
// focus the editor // focus the editor
@ -539,7 +539,7 @@ sketch_001 = startSketchOn(XY)
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-info') await page.hover('.cm-lint-marker-info')
await expect( await expect(
page.getByText('Identifiers should be lowerCamelCase').first() page.getByText('Identifiers must be lowerCamelCase').first()
).toBeVisible() ).toBeVisible()
}) })
@ -681,7 +681,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-info') await page.hover('.cm-lint-marker-info')
await expect( await expect(
page.getByText('Identifiers should be lowerCamelCase').first() page.getByText('Identifiers must be lowerCamelCase').first()
).toBeVisible() ).toBeVisible()
// select the line that's causing the error and delete it // 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 type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
import { bracket } from '@e2e/playwright/fixtures/bracket' 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 // 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 ({ test('Helix point-and-click on default axis', async ({
context, context,
page, page,
@ -1165,14 +1150,30 @@ openSketch = startSketchOn(XY)
toolbar, toolbar,
cmdBar, 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,` const expectedLine = `axis=X,`
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.connectionEstablished() await scene.connectionEstablished()
await test.step(`Go through the command bar flow`, async () => { await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click() 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 cmdBar.progressCmdBar()
await expect.poll(() => page.getByText('Axis').count()).toBe(6) await expect.poll(() => page.getByText('Axis').count()).toBe(6)
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
@ -1189,6 +1190,7 @@ openSketch = startSketchOn(XY)
AngleStart: '', AngleStart: '',
Length: '', Length: '',
Radius: '', Radius: '',
CounterClockWise: '',
}, },
commandName: 'Helix', commandName: 'Helix',
}) })
@ -1205,10 +1207,11 @@ openSketch = startSketchOn(XY)
Revolutions: '1', Revolutions: '1',
Length: '5', Length: '5',
Radius: '5', Radius: '5',
CounterClockWise: '',
}, },
commandName: 'Helix', commandName: 'Helix',
}) })
await cmdBar.submit() await cmdBar.progressCmdBar()
}) })
await test.step(`Confirm code is added to the editor, scene has changed`, async () => { await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
@ -1218,6 +1221,8 @@ openSketch = startSketchOn(XY)
activeLines: [expectedLine], activeLines: [expectedLine],
highlightedCode: '', 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 () => { await test.step(`Edit helix through the feature tree`, async () => {
@ -1229,18 +1234,21 @@ openSketch = startSketchOn(XY)
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Helix', commandName: 'Helix',
stage: 'arguments', stage: 'arguments',
currentArgKey: 'length', currentArgKey: 'CounterClockWise',
currentArgValue: '5', currentArgValue: '',
headerArguments: { headerArguments: {
Axis: 'X', Axis: 'X',
AngleStart: '270', AngleStart: '270',
Revolutions: '1', Revolutions: '1',
Radius: '5', Radius: '5',
Length: initialInput, 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.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
@ -1250,10 +1258,11 @@ openSketch = startSketchOn(XY)
Revolutions: '1', Revolutions: '1',
Radius: '5', Radius: '5',
Length: newInput, Length: newInput,
CounterClockWise: '',
}, },
commandName: 'Helix', commandName: 'Helix',
}) })
await cmdBar.submit() await cmdBar.progressCmdBar()
await toolbar.closeFeatureTreePane() await toolbar.closeFeatureTreePane()
await editor.openPane() await editor.openPane()
await editor.expectEditor.toContain('length = ' + newInput) await editor.expectEditor.toContain('length = ' + newInput)
@ -1264,238 +1273,174 @@ openSketch = startSketchOn(XY)
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0) const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
await operationButton.click({ button: 'left' }) await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete') await page.keyboard.press('Delete')
await scene.settled(cmdBar) // Red plane is back
await editor.expectEditor.not.toContain('helix') await scene.expectPixelColor([96, 52, 52], testPoint, 15)
await expect(
await toolbar.getFeatureTreeOperation('Helix', 0)
).not.toBeVisible()
}) })
}) })
test(`Helix point-and-click around segment`, async ({ const helixCases = [
context, {
page, selectionType: 'segment',
homePage, testPoint: { x: 513, y: 221 },
scene, expectedOutput: `helix001 = helix( axis = seg01, radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
editor, expectedEditedOutput: `helix001 = helix( axis = seg01, radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
toolbar, },
cmdBar, {
}) => { selectionType: 'sweepEdge',
const initialCode = `sketch001 = startSketchOn(XZ) testPoint: { x: 564, y: 364 },
profile001 = startProfile(sketch001, at = [0, 0]) expectedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
|> yLine(length = 100) expectedEditedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
|> line(endAbsolute = [100, 0]) },
|> line(endAbsolute = [profileStartX(%), profileStartY(%)]) ]
|> close()` helixCases.map(
await context.addInitScript((initialCode) => { ({ selectionType, testPoint, expectedOutput, expectedEditedOutput }) => {
localStorage.setItem('persistCode', initialCode) test(`Helix point-and-click around ${selectionType}`, async ({
}, initialCode) context,
await page.setBodyDimensions({ width: 1000, height: 500 }) page,
await homePage.goToModelingScene() homePage,
await scene.settled(cmdBar) 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 () => { // One dumb hardcoded screen pixel value
await toolbar.closePane('code') const [clickOnEdge] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
await toolbar.helixButton.click()
await cmdBar.expectState(initialCmdBarStateHelix) await context.addInitScript((initialCode) => {
await cmdBar.selectOption({ name: 'Edge' }).click() localStorage.setItem('persistCode', initialCode)
await editor.selectText('yLine(length = 100)') }, initialCode)
await cmdBar.progressCmdBar() await page.setBodyDimensions({ width: 1000, height: 500 })
await page.keyboard.insertText('1') await homePage.goToModelingScene()
await cmdBar.progressCmdBar() await scene.settled(cmdBar)
await page.keyboard.insertText('2')
await cmdBar.progressCmdBar() await test.step(`Go through the command bar flow`, async () => {
await page.keyboard.insertText('3') await toolbar.closePane('code')
await cmdBar.progressCmdBar() await toolbar.helixButton.click()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'arguments',
headerArguments: { currentArgKey: 'mode',
Mode: 'Edge', currentArgValue: '',
Edge: `1 segment`, headerArguments: {
AngleStart: '2', AngleStart: '',
Revolutions: '1', Mode: '',
Radius: '3', CounterClockWise: '',
}, Radius: '',
commandName: 'Helix', 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 ({ test('Helix point-and-click on cylinder', async ({
context, context,
@ -1525,12 +1470,26 @@ extrude001 = extrude(profile001, length = 100)
// One dumb hardcoded screen pixel value // One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 } const testPoint = { x: 620, y: 257 }
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y) const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const expectedOutput = `helix001 = helix(cylinder = extrude001, revolutions = 1, angleStart = 360)` const expectedOutput = `helix001 = helix( cylinder = extrude001, revolutions = 1, angleStart = 360, ccw = false,)`
const expectedEditedOutput = `helix001 = helix(cylinder = extrude001, revolutions = 1, angleStart = 10)` 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 test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click() 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.selectOption({ name: 'Cylinder' }).click()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'arguments', stage: 'arguments',
@ -1541,6 +1500,7 @@ extrude001 = extrude(profile001, length = 100)
Cylinder: '', Cylinder: '',
AngleStart: '', AngleStart: '',
Revolutions: '', Revolutions: '',
CounterClockWise: '',
}, },
highlightedHeaderArg: 'cylinder', highlightedHeaderArg: 'cylinder',
commandName: 'Helix', commandName: 'Helix',
@ -1556,17 +1516,18 @@ extrude001 = extrude(profile001, length = 100)
Cylinder: '1 face', Cylinder: '1 face',
AngleStart: '360', AngleStart: '360',
Revolutions: '1', Revolutions: '1',
CounterClockWise: '',
}, },
commandName: 'Helix', commandName: 'Helix',
}) })
await cmdBar.submit() await cmdBar.progressCmdBar()
}) })
await test.step(`Confirm code is added to the editor, scene has changed`, async () => { await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await editor.expectEditor.toContain(expectedOutput) await editor.expectEditor.toContain(expectedOutput)
await editor.expectState({ await editor.expectState({
diagnostics: [], diagnostics: [],
activeLines: [expectedOutput], activeLines: [expectedLine],
highlightedCode: '', highlightedCode: '',
}) })
}) })
@ -1578,21 +1539,22 @@ extrude001 = extrude(profile001, length = 100)
await cmdBar.expectState({ await cmdBar.expectState({
commandName: 'Helix', commandName: 'Helix',
stage: 'arguments', stage: 'arguments',
currentArgKey: 'angleStart', currentArgKey: 'CounterClockWise',
currentArgValue: '360', currentArgValue: '',
headerArguments: { headerArguments: {
AngleStart: '360', AngleStart: '360',
Revolutions: '1', Revolutions: '1',
CounterClockWise: '',
}, },
highlightedHeaderArg: 'angleStart', highlightedHeaderArg: 'CounterClockWise',
}) })
await page.keyboard.insertText('10') await cmdBar.selectOption({ name: 'True' }).click()
await cmdBar.progressCmdBar()
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
AngleStart: '10', AngleStart: '360',
Revolutions: '1', Revolutions: '1',
CounterClockWise: 'true',
}, },
commandName: 'Helix', 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"; LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
ELECTRON_OVERRIDE_DIST_PATH = ELECTRON_OVERRIDE_DIST_PATH =
if pkgs.stdenv.isDarwin if pkgs.stdenv.isDarwin
then "${pkgs.electron}/Applications" then "${pkgs.electron}/Applications/Electron.app/Contents/MacOS/"
else "${pkgs.electron}/bin"; else "${pkgs.electron}/bin";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true; PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome"; 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" import * from "parameters.kcl"
// Place the car rotor // Place the car rotor
carRotor rotor = carRotor
|> translate(x = 0, y = 0.5, z = 0) |> translate(x = 0, y = 0.5, z = 0)
// Place the car wheel // Place the car wheel
carWheel carWheel
// Place the lug nuts // Place the lug nuts
lugNut lgnut = lugNut
|> patternCircular3d( |> patternCircular3d(
arcDegrees = 360, arcDegrees = 360,
axis = [0, 1, 0], axis = [0, 1, 0],
@ -32,8 +32,19 @@ lugNut
) )
// Place the brake caliper // Place the brake caliper
brakeCaliper cal = brakeCaliper
|> translate(x = 0, y = 0.5, z = 0) |> translate(x = 0, y = 0.5, z = 0)
// Place the car tire // Place the car tire
carTire 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(%)]) |> line(%, endAbsolute = [profileStartX(%), profileStartY(%)])
|> close(%) |> close(%)
profile008 = circle(sketch005, center = [0, 0], diameter = nubDiameter) profile008 = circle(sketch005, center = [0, 0], diameter = nubDiameter)
subtract2d(profile007, tool = profile008) hourHand = subtract2d(profile007, tool = profile008)
|> extrude(%, length = 5) |> extrude(%, length = 5)
|> appearance(%, color = "#404040") |> appearance(%, color = "#404040")
@ -413,7 +413,7 @@ profile009 = startProfile(
|> line(%, endAbsolute = [profileStartX(%), profileStartY(%)]) |> line(%, endAbsolute = [profileStartX(%), profileStartY(%)])
|> close(%) |> close(%)
profile010 = circle(sketch006, center = [0, 0], diameter = 30) profile010 = circle(sketch006, center = [0, 0], diameter = 30)
subtract2d(profile009, tool = profile010) minuteHand = subtract2d(profile009, tool = profile010)
|> extrude(%, length = 5) |> extrude(%, length = 5)
|> appearance(%, color = "#404040") |> appearance(%, color = "#404040")
@ -439,3 +439,8 @@ profile004 = startProfile(sketch003, at = [-slotWidth / 2, 200])
|> extrude(%, length = -20) |> extrude(%, length = -20)
// todo: create cavity for the screw to slide into (need csg update) // 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]] [[package]]
name = "kcl-bumper" name = "kcl-bumper"
version = "0.1.83" version = "0.1.82"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1825,7 +1825,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-derive-docs" name = "kcl-derive-docs"
version = "0.1.83" version = "0.1.82"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1834,7 +1834,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-directory-test-macro" name = "kcl-directory-test-macro"
version = "0.1.83" version = "0.1.82"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"proc-macro2", "proc-macro2",
@ -1844,7 +1844,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server" name = "kcl-language-server"
version = "0.2.83" version = "0.2.82"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1865,7 +1865,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server-release" name = "kcl-language-server-release"
version = "0.1.83" version = "0.1.82"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1885,7 +1885,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.2.83" version = "0.2.82"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx 0.5.1", "approx 0.5.1",
@ -1962,7 +1962,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-python-bindings" name = "kcl-python-bindings"
version = "0.3.83" version = "0.3.82"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kcl-lib", "kcl-lib",
@ -1977,7 +1977,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-test-server" name = "kcl-test-server"
version = "0.1.83" version = "0.1.82"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hyper 0.14.32", "hyper 0.14.32",
@ -1990,7 +1990,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-to-core" name = "kcl-to-core"
version = "0.1.83" version = "0.1.82"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -2004,7 +2004,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-wasm-lib" name = "kcl-wasm-lib"
version = "0.1.83" version = "0.1.82"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bson", "bson",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,14 +17,11 @@ use crate::{
}, },
fmt, fmt,
modules::{ModuleId, ModulePath, ModuleRepr}, modules::{ModuleId, ModulePath, ModuleRepr},
parsing::{ parsing::ast::types::{
ast::types::{ Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier,
BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program, TagDeclarator,
LiteralValue, MemberExpression, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program, Type, UnaryExpression, UnaryOperator,
TagDeclarator, Type, UnaryExpression, UnaryOperator,
},
token::NumericSuffix,
}, },
source_range::SourceRange, source_range::SourceRange,
std::args::TyF64, std::args::TyF64,
@ -1669,18 +1666,12 @@ impl Property {
LiteralIdentifier::Literal(literal) => { LiteralIdentifier::Literal(literal) => {
let value = literal.value.clone(); let value = literal.value.clone();
match value { match value {
n @ LiteralValue::Number { value, suffix } => { LiteralValue::Number { value, .. } => {
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,
)));
}
if let Some(x) = crate::try_f64_to_usize(value) { if let Some(x) = crate::try_f64_to_usize(value) {
Ok(Property::UInt(x)) Ok(Property::UInt(x))
} else { } else {
Err(KclError::new_semantic(KclErrorDetails::new( 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, property_sr,
))) )))
} }
@ -1699,13 +1690,10 @@ fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -
let make_err = let make_err =
|message: String| Err::<Property, _>(KclError::new_semantic(KclErrorDetails::new(message, property_sr))); |message: String| Err::<Property, _>(KclError::new_semantic(KclErrorDetails::new(message, property_sr)));
match value { match value {
n @ KclValue::Number{value: num, ty, .. } => { KclValue::Number{value: num, .. } => {
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()));
}
let num = *num; let num = *num;
if num < 0.0 { 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); let nearest_int = crate::try_f64_to_usize(num);
if let Some(nearest_int) = nearest_int { if let Some(nearest_int) = nearest_int {
@ -2153,23 +2141,4 @@ c = ((PI * 2) / 3): number(deg)
let result = parse_execute(ast).await.unwrap(); let result = parse_execute(ast).await.unwrap();
assert_eq!(result.exec_state.errors().len(), 2); 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) 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. /// Perform the execution of a program.
/// ///
/// To access non-fatal errors and warnings, extract them from the `ExecState`. /// To access non-fatal errors and warnings, extract them from the `ExecState`.

View File

@ -11,10 +11,10 @@ use crate::{
def_finding!( def_finding!(
Z0001, Z0001,
"Identifiers should be lowerCamelCase", "Identifiers must be lowerCamelCase",
"\ "\
By convention, variable names are lowerCamelCase, not snake_case, kebab-case, 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' For instance, a good identifier for the variable representing 'box height'
would be 'boxHeight', not 'BOX_HEIGHT', 'box_height' nor 'BoxHeight'. For 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.len(), 1);
assert_eq!( assert_eq!(
diagnostics.full_document_diagnostic_report.items[0].message, diagnostics.full_document_diagnostic_report.items[0].message,
"Identifiers should be lowerCamelCase" "Identifiers must be lowerCamelCase"
); );
} else { } else {
panic!("Expected full diagnostics"); panic!("Expected full diagnostics");

View File

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

View File

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

View File

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

View File

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

View File

@ -111,6 +111,48 @@ impl Context {
ctx.run_with_caching(program).await 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. /// Reset the scene and bust the cache.
/// ONLY use this if you absolutely need to 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)] #[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 Tooltip from '@src/components/Tooltip'
import styles from './ModelingPane.module.css' import styles from './ModelingPane.module.css'
import { reportRejection } from '@src/lib/trap'
import { kclManager } from '@src/lib/singletons'
export interface ModelingPaneProps { export interface ModelingPaneProps {
id: string id: string
@ -19,6 +21,28 @@ export interface ModelingPaneProps {
onClose: () => void 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 = ({ export const ModelingPaneHeader = ({
id, id,
icon, icon,
@ -40,6 +64,20 @@ export const ModelingPaneHeader = ({
)} )}
<span data-testid={id + '-header'}>{title}</span> <span data-testid={id + '-header'}>{title}</span>
</div> </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} {Menu instanceof Function ? <Menu /> : Menu}
<ActionButton <ActionButton
Element="button" Element="button"

View File

@ -17,7 +17,12 @@ import {
compilationErrorsToDiagnostics, compilationErrorsToDiagnostics,
kclErrorsToDiagnostics, kclErrorsToDiagnostics,
} from '@src/lang/errors' } 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 { getNodeFromPath, getSettingsAnnotation } from '@src/lang/queryAst'
import { CommandLogType } from '@src/lang/std/commandLog' import { CommandLogType } from '@src/lang/std/commandLog'
import type { EngineCommandManager } from '@src/lang/std/engineConnection' import type { EngineCommandManager } from '@src/lang/std/engineConnection'
@ -106,6 +111,7 @@ export class KclManager extends EventTarget {
preComments: [], preComments: [],
commentStart: 0, commentStart: 0,
} }
private _animateState = { step: 0 }
private _execState: ExecState = emptyExecState() private _execState: ExecState = emptyExecState()
private _variables: VariableMap = {} private _variables: VariableMap = {}
lastSuccessfulVariables: VariableMap = {} lastSuccessfulVariables: VariableMap = {}
@ -450,6 +456,7 @@ export class KclManager extends EventTarget {
const ast = args.ast || this.ast const ast = args.ast || this.ast
markOnce('code/startExecuteAst') markOnce('code/startExecuteAst')
this._animateState.step = 0
const currentExecutionId = args.executionId || Date.now() const currentExecutionId = args.executionId || Date.now()
this._cancelTokens.set(currentExecutionId, false) this._cancelTokens.set(currentExecutionId, false)
@ -541,6 +548,39 @@ export class KclManager extends EventTarget {
markOnce('code/endExecuteAst') 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. // DO NOT CALL THIS from codemirror ever.
async executeAstMock(ast: Program): Promise<null | Error> { async executeAstMock(ast: Program): Promise<null | Error> {
await this.ensureWasmInit() 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({ export async function executeAstMock({
ast, ast,
rustContext, rustContext,

View File

@ -590,7 +590,7 @@ export function addHelix({
angleStart: Expr angleStart: Expr
radius?: Expr radius?: Expr
length?: Expr length?: Expr
ccw?: boolean ccw: boolean
insertIndex?: number insertIndex?: number
variableName?: string variableName?: string
}): { modifiedAst: Node<Program>; pathToNode: PathToNode } { }): { 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( const variable = createVariableDeclaration(
name, name,
createCallExpressionStdLibKw( createCallExpressionStdLibKw(
@ -622,7 +619,7 @@ export function addHelix({
...modeArgs, ...modeArgs,
createLabeledArg('revolutions', revolutions), createLabeledArg('revolutions', revolutions),
createLabeledArg('angleStart', angleStart), createLabeledArg('angleStart', angleStart),
...ccwExpr, createLabeledArg('ccw', createLiteral(ccw)),
] ]
) )
) )

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import {
} from '@src/env' } from '@src/env'
import { assign, fromPromise, setup } from 'xstate' 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 { import {
getUser as getUserDesktop, getUser as getUserDesktop,
readTokenFile, readTokenFile,
@ -254,32 +254,8 @@ async function getAndSyncStoredToken(input: {
async function logout() { async function logout() {
localStorage.removeItem(TOKEN_PERSIST_KEY) localStorage.removeItem(TOKEN_PERSIST_KEY)
if (isDesktop()) { if (isDesktop()) {
try { await writeTokenFile('')
let token = await readTokenFile() return Promise.resolve(null)
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'), { return fetch(withBaseUrl('/logout'), {

View File

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

View File

@ -15,7 +15,6 @@ import {
dialog, dialog,
ipcMain, ipcMain,
nativeTheme, nativeTheme,
session,
screen, screen,
shell, shell,
systemPreferences, systemPreferences,
@ -29,10 +28,7 @@ import {
parseCLIArgs, parseCLIArgs,
} from '@src/commandLineArgs' } from '@src/commandLineArgs'
import { initPromiseNode } from '@src/lang/wasmUtilsNode' import { initPromiseNode } from '@src/lang/wasmUtilsNode'
import { import { ZOO_STUDIO_PROTOCOL } from '@src/lib/constants'
ZOO_STUDIO_PROTOCOL,
OAUTH2_DEVICE_CLIENT_ID,
} from '@src/lib/constants'
import getCurrentProjectFile from '@src/lib/getCurrentProjectFile' import getCurrentProjectFile from '@src/lib/getCurrentProjectFile'
import { reportRejection } from '@src/lib/trap' import { reportRejection } from '@src/lib/trap'
import { import {
@ -297,21 +293,7 @@ app.on('window-all-closed', () => {
// This method will be called when Electron has finished // This method will be called when Electron has finished
// initialization and is ready to create browser windows. // initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs. // Some APIs can only be used after this event occurs.
app.on('ready', async (event, data) => { app.on('ready', (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)
}
// Avoid potentially 2 ready fires // Avoid potentially 2 ready fires
if (mainWindow) return if (mainWindow) return
// Create the mainWindow // Create the mainWindow
@ -420,7 +402,7 @@ ipcMain.handle('startDeviceFlow', async (_, host: string) => {
// We can hardcode the client ID. // We can hardcode the client ID.
// This value is safe to be embedded in version control. // This value is safe to be embedded in version control.
// This is the client ID of the KittyCAD app. // 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', token_endpoint_auth_method: 'none',
}) })