Compare commits
1 Commits
jtran/fix-
...
2469
Author | SHA1 | Date | |
---|---|---|---|
a905874930 |
141
.github/workflows/find_duplicate_issues.py
vendored
Executable file
@ -0,0 +1,141 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import os
|
||||||
|
import openai
|
||||||
|
import json
|
||||||
|
from github import Github
|
||||||
|
|
||||||
|
# Initialize GitHub and OpenAI clients
|
||||||
|
if not os.getenv("GITHUB_TOKEN"):
|
||||||
|
print("Please set the GITHUB_TOKEN environment variable.")
|
||||||
|
exit(1)
|
||||||
|
g = Github(os.getenv("GITHUB_TOKEN"))
|
||||||
|
|
||||||
|
if not os.getenv("OPENAI_API_KEY"):
|
||||||
|
print("Please set the OPENAI_API_KEY environment variable.")
|
||||||
|
exit(1)
|
||||||
|
openai.api_key = os.getenv("OPENAI_API_KEY")
|
||||||
|
|
||||||
|
# Target repository
|
||||||
|
repo_name = os.getenv("GITHUB_REPOSITORY")
|
||||||
|
if not repo_name:
|
||||||
|
print("Please set the GITHUB_REPOSITORY environment variable.")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
repo = g.get_repo(repo_name)
|
||||||
|
|
||||||
|
# Fetch all issues and comments
|
||||||
|
issues = repo.get_issues(state="open")
|
||||||
|
all_issues = {}
|
||||||
|
for issue in issues:
|
||||||
|
comments = issue.get_comments()
|
||||||
|
all_comments = [comment.body for comment in comments]
|
||||||
|
all_issues[issue.number] = {
|
||||||
|
"title": issue.title,
|
||||||
|
"body": issue.body,
|
||||||
|
"comments": all_comments,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create the start of the prompt template with all the issues and bodies.
|
||||||
|
system_issues_prompt = ""
|
||||||
|
for issue_number, issue_data in all_issues.items():
|
||||||
|
system_issues_prompt += f"""
|
||||||
|
#{issue_number}: {issue_data['title']}
|
||||||
|
|
||||||
|
# body
|
||||||
|
|
||||||
|
{issue_data['body']}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(system_issues_prompt)
|
||||||
|
|
||||||
|
# Get the token count for the system_issues_prompt
|
||||||
|
print(f"Token count for system_issues_prompt: {len(system_issues_prompt.split())}")
|
||||||
|
|
||||||
|
|
||||||
|
# Analyze issues for duplicates using OpenAI's GPT-4
|
||||||
|
potential_duplicates = []
|
||||||
|
for issue_number, issue_data in all_issues.items():
|
||||||
|
print(f"Analyzing issue #{issue_number} {issue_data['title']} ...")
|
||||||
|
response = openai.chat.completions.create(
|
||||||
|
model="gpt-4o",
|
||||||
|
response_format={ "type": "json_object" },
|
||||||
|
messages=[
|
||||||
|
{
|
||||||
|
"role": "system",
|
||||||
|
"content": """You are a distinguished engineer. Your peers
|
||||||
|
keep creating duplicate GitHub issues and you have OCD. You have decided to use your
|
||||||
|
skills to find duplicates for them. You are analyzing the issues in the repository.
|
||||||
|
Your goal is to find potential duplicate GitHub issues.
|
||||||
|
Do not return the current issue as a duplicate of itself. Use the issue title, body,
|
||||||
|
and comments to find potential duplicates.
|
||||||
|
|
||||||
|
Whenever you find a potential duplicate, you need to be very very sure that it is a duplicate.
|
||||||
|
Error on the side of caution. If you are not sure, do not return it as a duplicate.
|
||||||
|
Most won't have duplicates and that is fine! You are looking for the ones that do.
|
||||||
|
If you mistakenly return a non-duplicate, you will be penalized to spend time with the
|
||||||
|
interns helping them learn to exit vim. You do not want to do that.
|
||||||
|
|
||||||
|
Check and make sure that no one else has already commented that the issue is a duplicate.
|
||||||
|
If they have commented, you should not return it as a duplicate.
|
||||||
|
|
||||||
|
Your confidence level must be over 90% to return an issue as a duplicate.
|
||||||
|
|
||||||
|
Take a deep breath and begin your analysis. Your reputation is on the line.
|
||||||
|
Your responses should be formatted as a json array.
|
||||||
|
The following are examples of valid responses:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[]
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
[{"issue_number": 1234, "title": "Issue title"}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Below are the current open issues in the repository. They are formatted as:
|
||||||
|
|
||||||
|
#{issue number}: {issue title}
|
||||||
|
|
||||||
|
# body
|
||||||
|
|
||||||
|
{issue body}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The current open issues in the repository are:
|
||||||
|
"""
|
||||||
|
+ system_issues_prompt,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": f"""Find duplicates for GitHub issue #{issue_number} titled:
|
||||||
|
{issue_data['title']}
|
||||||
|
|
||||||
|
# issue body
|
||||||
|
{issue_data['body']}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# issue comments:
|
||||||
|
{issue_data['comments']}""",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(response.choices) == 0:
|
||||||
|
print("No duplicate issues found.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
for response in response.choices:
|
||||||
|
print(response.message.content)
|
||||||
|
|
||||||
|
# Print potential duplicates
|
||||||
|
print("Potential duplicate issues:")
|
||||||
|
for issue_number in potential_duplicates:
|
||||||
|
issue = all_issues[issue_number]
|
||||||
|
print(
|
||||||
|
f"Issue #{issue_number}: {issue['title']} - {repo.html_url}/issues/{issue_number}"
|
||||||
|
)
|
31
.github/workflows/identify-duplicates.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
name: Identify Duplicate Issues
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *' # Runs at midnight every day
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
find-duplicates:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install PyGithub openai
|
||||||
|
|
||||||
|
- name: Run script to identify duplicates
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||||
|
run: python .github/scripts/find_duplicate_issues.py
|
3
.gitignore
vendored
@ -4,6 +4,7 @@
|
|||||||
/node_modules
|
/node_modules
|
||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
venv
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
@ -58,5 +59,3 @@ src/wasm-lib/grackle/stdlib_cube_partial.json
|
|||||||
Mac_App_Distribution.provisionprofile
|
Mac_App_Distribution.provisionprofile
|
||||||
|
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
venv
|
|
||||||
|
@ -2458,44 +2458,6 @@ test.describe('Onboarding tests', () => {
|
|||||||
await expect(onboardingOverlayLocator).toBeVisible()
|
await expect(onboardingOverlayLocator).toBeVisible()
|
||||||
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
||||||
})
|
})
|
||||||
|
|
||||||
test("Avatar text doesn't mention avatar when no avatar", async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
// Override beforeEach test setup
|
|
||||||
await page.addInitScript(
|
|
||||||
async ({ settingsKey, settings }) => {
|
|
||||||
localStorage.setItem(settingsKey, settings)
|
|
||||||
localStorage.setItem('FORCE_NO_IMAGE', 'FORCE_NO_IMAGE')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
settingsKey: TEST_SETTINGS_KEY,
|
|
||||||
settings: TOML.stringify({
|
|
||||||
settings: TEST_SETTINGS_ONBOARDING_USER_MENU,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
|
|
||||||
|
|
||||||
// Test that the text in this step is correct
|
|
||||||
const avatarLocator = await page
|
|
||||||
.getByTestId('user-sidebar-toggle')
|
|
||||||
.locator('img')
|
|
||||||
const onboardingOverlayLocator = await page
|
|
||||||
.getByTestId('onboarding-content')
|
|
||||||
.locator('div')
|
|
||||||
.nth(1)
|
|
||||||
|
|
||||||
// Expect the avatar to be visible and for the text to reference it
|
|
||||||
await expect(avatarLocator).not.toBeVisible()
|
|
||||||
await expect(onboardingOverlayLocator).toBeVisible()
|
|
||||||
await expect(onboardingOverlayLocator).toContainText('the menu button')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test.describe('Testing selections', () => {
|
test.describe('Testing selections', () => {
|
||||||
@ -3966,55 +3928,6 @@ test.describe('Sketch tests', () => {
|
|||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
test('Can delete most of a sketch and the line tool will still work', async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'persistCode',
|
|
||||||
`const sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([4.61, -14.01], %)
|
|
||||||
|> line([12.73, -0.09], %)
|
|
||||||
|> tangentialArcTo([24.95, -5.38], %)`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
|
||||||
).toBeEnabled()
|
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
|
||||||
|
|
||||||
await page.waitForTimeout(600) // wait for animation
|
|
||||||
|
|
||||||
await page.getByText('tangentialArcTo([24.95, -5.38], %)').click()
|
|
||||||
await page.keyboard.press('End')
|
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await page.keyboard.press('ArrowUp')
|
|
||||||
await page.keyboard.press('Home')
|
|
||||||
await page.keyboard.up('Shift')
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
await u.openAndClearDebugPanel()
|
|
||||||
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Line' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
await page.mouse.click(700, 200)
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
|
||||||
`const sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([4.61, -14.01], %)
|
|
||||||
|> line([0.31, 16.47], %)`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
test('Can exit selection of face', async ({ page }) => {
|
test('Can exit selection of face', async ({ page }) => {
|
||||||
// Load the app with the code panes
|
// Load the app with the code panes
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -4404,7 +4317,7 @@ test.describe('Sketch tests', () => {
|
|||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([6.44, -12.07], %)
|
|> startProfileAt([6.44, -12.07], %)
|
||||||
|> line([14.72, 1.97], %)
|
|> line([14.72, 2.01], %)
|
||||||
|> tangentialArcTo([24.95, -5.38], %)
|
|> tangentialArcTo([24.95, -5.38], %)
|
||||||
|> line([1.97, 2.06], %)
|
|> line([1.97, 2.06], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
@ -4601,53 +4514,6 @@ test.describe('Sketch tests', () => {
|
|||||||
await doSnapAtDifferentScales(page, [0, 10000, 10000])
|
await doSnapAtDifferentScales(page, [0, 10000, 10000])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
test('exiting a close extrude, has the extrude button enabled ready to go', async ({
|
|
||||||
page,
|
|
||||||
}) => {
|
|
||||||
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
|
||||||
await page.addInitScript(async () => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'persistCode',
|
|
||||||
`const sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([-0.45, 0.87], %)
|
|
||||||
|> line([1.32, 0.38], %)
|
|
||||||
|> line([1.02, -1.32], %, $seg01)
|
|
||||||
|> line([-1.01, -0.77], %)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
|> close(%)
|
|
||||||
`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
await u.waitForAuthSkipAppStart()
|
|
||||||
|
|
||||||
// click "line([1.32, 0.38], %)"
|
|
||||||
await page.getByText(`line([1.32, 0.38], %)`).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
// click edit sketch
|
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
|
||||||
await page.waitForTimeout(600) // wait for animation
|
|
||||||
|
|
||||||
// exit sketch
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
|
|
||||||
// expect extrude button to be enabled
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Extrude' })
|
|
||||||
).not.toBeDisabled()
|
|
||||||
|
|
||||||
// click extrude
|
|
||||||
await page.getByRole('button', { name: 'Extrude' }).click()
|
|
||||||
|
|
||||||
// sketch selection should already have been made. "Selection 1 face" only show up when the selection has been made already
|
|
||||||
// otherwise the cmdbar would be waiting for a selection.
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Selection 1 face' })
|
|
||||||
).toBeVisible()
|
|
||||||
})
|
|
||||||
test("Existing sketch with bad code delete user's code", async ({ page }) => {
|
test("Existing sketch with bad code delete user's code", async ({ page }) => {
|
||||||
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -7640,25 +7506,17 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
|||||||
await page.keyboard.press('e')
|
await page.keyboard.press('e')
|
||||||
await expect(page.locator('.cm-content')).toHaveText('//slae')
|
await expect(page.locator('.cm-content')).toHaveText('//slae')
|
||||||
await page.keyboard.press('Meta+/')
|
await page.keyboard.press('Meta+/')
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(2000)
|
||||||
// Test these hotkeys perform actions when
|
// Test these hotkeys perform actions when
|
||||||
// focus is on the canvas
|
// focus is on the canvas
|
||||||
await page.mouse.move(600, 250)
|
await page.mouse.move(600, 250)
|
||||||
await page.mouse.click(600, 250)
|
await page.mouse.click(600, 250)
|
||||||
|
|
||||||
// work-around: to stop "keyboard.press('s')" from typing in the editor even when it should be blurred
|
|
||||||
await page.getByRole('button', { name: 'Commands ⌘K' }).click()
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.keyboard.press('Escape')
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
// end work-around
|
|
||||||
|
|
||||||
// Start a sketch
|
// Start a sketch
|
||||||
await page.keyboard.press('s')
|
await page.keyboard.press('s')
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(2000)
|
||||||
await page.mouse.move(800, 300, { steps: 5 })
|
await page.mouse.move(800, 300, { steps: 5 })
|
||||||
await page.mouse.click(800, 300)
|
await page.mouse.click(800, 300)
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(2000)
|
||||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
|
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
|
||||||
timeout: 15_000,
|
timeout: 15_000,
|
||||||
})
|
})
|
||||||
|
@ -857,11 +857,6 @@ export class SceneEntities {
|
|||||||
let addingNewSegmentStatus: 'nothing' | 'pending' | 'added' = 'nothing'
|
let addingNewSegmentStatus: 'nothing' | 'pending' | 'added' = 'nothing'
|
||||||
sceneInfra.setCallbacks({
|
sceneInfra.setCallbacks({
|
||||||
onDragEnd: async () => {
|
onDragEnd: async () => {
|
||||||
// After the user drags, code has been updated, and source ranges are
|
|
||||||
// potentially stale.
|
|
||||||
const astResult = kclManager.updateSourceRanges()
|
|
||||||
if (trap(astResult)) return
|
|
||||||
|
|
||||||
if (addingNewSegmentStatus !== 'nothing') {
|
if (addingNewSegmentStatus !== 'nothing') {
|
||||||
await this.tearDownSketch({ removeAxis: false })
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
this.setupSketch({
|
this.setupSketch({
|
||||||
|
@ -35,7 +35,6 @@ import {
|
|||||||
canExtrudeSelection,
|
canExtrudeSelection,
|
||||||
handleSelectionBatch,
|
handleSelectionBatch,
|
||||||
isSelectionLastLine,
|
isSelectionLastLine,
|
||||||
isRangeInbetweenCharacters,
|
|
||||||
isSketchPipe,
|
isSketchPipe,
|
||||||
updateSelections,
|
updateSelections,
|
||||||
} from 'lib/selections'
|
} from 'lib/selections'
|
||||||
@ -426,7 +425,6 @@ export const ModelingMachineProvider = ({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
selectionRanges.codeBasedSelections.length === 0 ||
|
selectionRanges.codeBasedSelections.length === 0 ||
|
||||||
isRangeInbetweenCharacters(selectionRanges) ||
|
|
||||||
isSelectionLastLine(selectionRanges, codeManager.code)
|
isSelectionLastLine(selectionRanges, codeManager.code)
|
||||||
) {
|
) {
|
||||||
// they have no selection, we should enable the button
|
// they have no selection, we should enable the button
|
||||||
|
@ -154,16 +154,6 @@ export class KclManager {
|
|||||||
this._executeCallback = callback
|
this._executeCallback = callback
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSourceRanges(): Error | null {
|
|
||||||
const newAst = parse(recast(this.ast))
|
|
||||||
if (err(newAst)) {
|
|
||||||
return newAst
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ast = newAst
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
clearAst() {
|
clearAst() {
|
||||||
this._ast = {
|
this._ast = {
|
||||||
body: [],
|
body: [],
|
||||||
|
@ -51,16 +51,8 @@ export function getNodeFromPath<T>(
|
|||||||
let successfulPaths: PathToNode = []
|
let successfulPaths: PathToNode = []
|
||||||
let pathsExplored: PathToNode = []
|
let pathsExplored: PathToNode = []
|
||||||
for (const pathItem of path) {
|
for (const pathItem of path) {
|
||||||
if (typeof currentNode[pathItem[0]] !== 'object') {
|
if (typeof currentNode[pathItem[0]] !== 'object')
|
||||||
if (stopAtNode) {
|
|
||||||
return {
|
|
||||||
node: stopAtNode,
|
|
||||||
shallowPath: pathsExplored,
|
|
||||||
deepPath: successfulPaths,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Error('not an object')
|
return new Error('not an object')
|
||||||
}
|
|
||||||
currentNode = currentNode?.[pathItem[0]]
|
currentNode = currentNode?.[pathItem[0]]
|
||||||
successfulPaths.push(pathItem)
|
successfulPaths.push(pathItem)
|
||||||
if (!stopAtNode) {
|
if (!stopAtNode) {
|
||||||
|
@ -156,20 +156,17 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
|||||||
},
|
},
|
||||||
Creo: {
|
Creo: {
|
||||||
pan: {
|
pan: {
|
||||||
description: 'Left click + Ctrl + drag',
|
description: 'Middle click + Shift + drag',
|
||||||
callback: (e) => butName(e).left && !butName(e).right && e.ctrlKey,
|
callback: (e) => butName(e).middle && e.shiftKey,
|
||||||
},
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
description: 'Scroll wheel or Right click + Ctrl + drag',
|
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
||||||
dragCallback: (e) => butName(e).right && !butName(e).left && e.ctrlKey,
|
dragCallback: (e) => butName(e).middle && e.ctrlKey,
|
||||||
scrollCallback: () => true,
|
scrollCallback: () => true,
|
||||||
},
|
},
|
||||||
rotate: {
|
rotate: {
|
||||||
description: 'Middle (or Left + Right) click + Ctrl + drag',
|
description: 'Middle click + drag',
|
||||||
callback: (e) => {
|
callback: (e) => butName(e).middle && noModifiersPressed(e),
|
||||||
const b = butName(e)
|
|
||||||
return (b.middle || (b.left && b.right)) && e.ctrlKey
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AutoCAD: {
|
AutoCAD: {
|
||||||
|
@ -360,14 +360,6 @@ export function isSelectionLastLine(
|
|||||||
return selectionRanges.codeBasedSelections[i].range[1] === code.length
|
return selectionRanges.codeBasedSelections[i].range[1] === code.length
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isRangeInbetweenCharacters(selectionRanges: Selections) {
|
|
||||||
return (
|
|
||||||
selectionRanges.codeBasedSelections.length === 1 &&
|
|
||||||
selectionRanges.codeBasedSelections[0].range[0] === 0 &&
|
|
||||||
selectionRanges.codeBasedSelections[0].range[1] === 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CommonASTNode = {
|
export type CommonASTNode = {
|
||||||
selection: Selection
|
selection: Selection
|
||||||
ast: Program
|
ast: Program
|
||||||
|
@ -126,17 +126,11 @@ async function getUser(context: UserContext) {
|
|||||||
if (!token && isTauri()) return Promise.reject(new Error('No token found'))
|
if (!token && isTauri()) return Promise.reject(new Error('No token found'))
|
||||||
if (token) headers['Authorization'] = `Bearer ${context.token}`
|
if (token) headers['Authorization'] = `Bearer ${context.token}`
|
||||||
|
|
||||||
if (SKIP_AUTH) {
|
if (SKIP_AUTH)
|
||||||
// For local tests
|
|
||||||
if (localStorage.getItem('FORCE_NO_IMAGE')) {
|
|
||||||
LOCAL_USER.image = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user: LOCAL_USER,
|
user: LOCAL_USER,
|
||||||
token,
|
token,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const userPromise = !isTauri()
|
const userPromise = !isTauri()
|
||||||
? fetch(url, {
|
? fetch(url, {
|
||||||
@ -150,11 +144,6 @@ async function getUser(context: UserContext) {
|
|||||||
|
|
||||||
const user = await userPromise
|
const user = await userPromise
|
||||||
|
|
||||||
// Necessary here because we use Kurt's API key in CI
|
|
||||||
if (localStorage.getItem('FORCE_NO_IMAGE')) {
|
|
||||||
user.image = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('error_code' in user) return Promise.reject(new Error(user.message))
|
if ('error_code' in user) return Promise.reject(new Error(user.message))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -2,18 +2,13 @@ import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
|||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
|
||||||
|
|
||||||
export default function UserMenu() {
|
export default function UserMenu() {
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
const { auth } = useSettingsAuthContext()
|
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
const next = useNextClick(onboardingPaths.PROJECT_MENU)
|
const next = useNextClick(onboardingPaths.PROJECT_MENU)
|
||||||
const [avatarErrored, setAvatarErrored] = useState(false)
|
const [avatarErrored, setAvatarErrored] = useState(false)
|
||||||
|
const buttonDescription = !avatarErrored ? 'your avatar' : 'the menu button'
|
||||||
const user = auth?.context?.user
|
|
||||||
const errorOrNoImage = !user?.image || avatarErrored
|
|
||||||
const buttonDescription = errorOrNoImage ? 'the menu button' : 'your avatar'
|
|
||||||
|
|
||||||
// Set up error handling for the user's avatar image,
|
// Set up error handling for the user's avatar image,
|
||||||
// so the onboarding text can be updated if it fails to load.
|
// so the onboarding text can be updated if it fails to load.
|
||||||
|
2
src/wasm-lib/Cargo.lock
generated
@ -1385,7 +1385,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.1.72"
|
version = "0.1.71"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx",
|
"approx",
|
||||||
|
@ -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.1.72"
|
version = "0.1.71"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
@ -77,24 +77,14 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args
|
|||||||
let sketch_groups: Vec<Box<SketchGroup>> = sketch_group_set.into();
|
let sketch_groups: Vec<Box<SketchGroup>> = sketch_group_set.into();
|
||||||
let mut extrude_groups = Vec::new();
|
let mut extrude_groups = Vec::new();
|
||||||
for sketch_group in &sketch_groups {
|
for sketch_group in &sketch_groups {
|
||||||
// Before we extrude, we need to enable the sketch mode.
|
// Make sure we exited sketch mode if sketching on a plane.
|
||||||
// We do this here in case extrude is called out of order.
|
if let SketchSurface::Plane(_) = sketch_group.on {
|
||||||
args.batch_modeling_cmd(
|
// Disable the sketch mode.
|
||||||
uuid::Uuid::new_v4(),
|
// This is necessary for when people don't close the sketch explicitly.
|
||||||
kittycad::types::ModelingCmd::EnableSketchMode {
|
// The sketch mode will mess up the extrude direction if still active.
|
||||||
animated: false,
|
args.batch_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
|
||||||
ortho: false,
|
|
||||||
entity_id: sketch_group.on.id(),
|
|
||||||
adjust_camera: false,
|
|
||||||
planar_normal: if let SketchSurface::Plane(plane) = &sketch_group.on {
|
|
||||||
// We pass in the normal for the plane here.
|
|
||||||
Some(plane.z_axis.clone().into())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
args.send_modeling_cmd(
|
args.send_modeling_cmd(
|
||||||
id,
|
id,
|
||||||
@ -105,10 +95,6 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Disable the sketch mode.
|
|
||||||
args.batch_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
|
|
||||||
.await?;
|
|
||||||
extrude_groups.push(do_post_extrude(sketch_group.clone(), length, id, args.clone()).await?);
|
extrude_groups.push(do_post_extrude(sketch_group.clone(), length, id, args.clone()).await?);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +107,13 @@ pub(crate) async fn do_post_extrude(
|
|||||||
id: Uuid,
|
id: Uuid,
|
||||||
args: Args,
|
args: Args,
|
||||||
) -> Result<Box<ExtrudeGroup>, KclError> {
|
) -> Result<Box<ExtrudeGroup>, KclError> {
|
||||||
|
// We need to do this after extrude for sketch on face.
|
||||||
|
if let SketchSurface::Face(_) = sketch_group.on {
|
||||||
|
// Disable the sketch mode.
|
||||||
|
args.batch_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
// Bring the object to the front of the scene.
|
// Bring the object to the front of the scene.
|
||||||
// See: https://github.com/KittyCAD/modeling-app/issues/806
|
// See: https://github.com/KittyCAD/modeling-app/issues/806
|
||||||
args.batch_modeling_cmd(
|
args.batch_modeling_cmd(
|
||||||
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 167 KiB |
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
@ -1,29 +0,0 @@
|
|||||||
// create a sketch with name sketch000
|
|
||||||
const sketch000 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([0.0, 0.0], %)
|
|
||||||
|> line([1.0, 1.0], %, $line000)
|
|
||||||
|> line([0.0, -1.0], %, $line001)
|
|
||||||
|> line([-1.0, 0.0], %, $line002)
|
|
||||||
|
|
||||||
// create an extrusion with name extrude000
|
|
||||||
const extrude000 = extrude(1.0, sketch000)
|
|
||||||
|
|
||||||
// define a plane with name plane005
|
|
||||||
const plane005 = {
|
|
||||||
plane: {
|
|
||||||
origin: [0.0, 0.0, 1.0],
|
|
||||||
x_axis: [0.707107, 0.707107, 0.0],
|
|
||||||
y_axis: [-0.0, 0.0, 1.0],
|
|
||||||
z_axis: [0.707107, -0.707107, 0.0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create a sketch with name sketch001
|
|
||||||
const sketch001 = startSketchOn(plane005)
|
|
||||||
|> startProfileAt([0.100000, 0.250000], %)
|
|
||||||
|> line([0.075545, 0.494260], %, $line003)
|
|
||||||
|> line([0.741390, -0.113317], %, $line004)
|
|
||||||
|> line([-0.816935, -0.380943], %, $line005)
|
|
||||||
|
|
||||||
// create an extrusion with name extrude001
|
|
||||||
const extrude001 = extrude(1.0, sketch001)
|
|
@ -2501,10 +2501,3 @@ async fn serial_test_order_sketch_extrude_out_of_order() {
|
|||||||
0.999,
|
0.999,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
|
||||||
async fn serial_test_extrude_custom_plane() {
|
|
||||||
let code = include_str!("inputs/extrude-custom-plane.kcl");
|
|
||||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/extrude-custom-plane.png", &result, 0.999);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 141 KiB |
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 150 KiB |
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 167 KiB |
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 168 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 104 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 104 KiB |