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
|
||||
/.pnp
|
||||
.pnp.js
|
||||
venv
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
@ -58,5 +59,3 @@ src/wasm-lib/grackle/stdlib_cube_partial.json
|
||||
Mac_App_Distribution.provisionprofile
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
venv
|
||||
|
@ -2458,44 +2458,6 @@ test.describe('Onboarding tests', () => {
|
||||
await expect(onboardingOverlayLocator).toBeVisible()
|
||||
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', () => {
|
||||
@ -3966,55 +3928,6 @@ test.describe('Sketch tests', () => {
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).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 }) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
@ -4404,7 +4317,7 @@ test.describe('Sketch tests', () => {
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([6.44, -12.07], %)
|
||||
|> line([14.72, 1.97], %)
|
||||
|> line([14.72, 2.01], %)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> line([1.97, 2.06], %)
|
||||
|> close(%)
|
||||
@ -4601,53 +4514,6 @@ test.describe('Sketch tests', () => {
|
||||
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 }) => {
|
||||
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
||||
await page.addInitScript(async () => {
|
||||
@ -7640,25 +7506,17 @@ test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
|
||||
await page.keyboard.press('e')
|
||||
await expect(page.locator('.cm-content')).toHaveText('//slae')
|
||||
await page.keyboard.press('Meta+/')
|
||||
await page.waitForTimeout(1000)
|
||||
await page.waitForTimeout(2000)
|
||||
// Test these hotkeys perform actions when
|
||||
// focus is on the canvas
|
||||
await page.mouse.move(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
|
||||
await page.keyboard.press('s')
|
||||
await page.waitForTimeout(1000)
|
||||
await page.waitForTimeout(2000)
|
||||
await page.mouse.move(800, 300, { steps: 5 })
|
||||
await page.mouse.click(800, 300)
|
||||
await page.waitForTimeout(1000)
|
||||
await page.waitForTimeout(2000)
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
|
||||
timeout: 15_000,
|
||||
})
|
||||
|
@ -857,11 +857,6 @@ export class SceneEntities {
|
||||
let addingNewSegmentStatus: 'nothing' | 'pending' | 'added' = 'nothing'
|
||||
sceneInfra.setCallbacks({
|
||||
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') {
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
this.setupSketch({
|
||||
|
@ -35,7 +35,6 @@ import {
|
||||
canExtrudeSelection,
|
||||
handleSelectionBatch,
|
||||
isSelectionLastLine,
|
||||
isRangeInbetweenCharacters,
|
||||
isSketchPipe,
|
||||
updateSelections,
|
||||
} from 'lib/selections'
|
||||
@ -426,7 +425,6 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
if (
|
||||
selectionRanges.codeBasedSelections.length === 0 ||
|
||||
isRangeInbetweenCharacters(selectionRanges) ||
|
||||
isSelectionLastLine(selectionRanges, codeManager.code)
|
||||
) {
|
||||
// they have no selection, we should enable the button
|
||||
|
@ -154,16 +154,6 @@ export class KclManager {
|
||||
this._executeCallback = callback
|
||||
}
|
||||
|
||||
updateSourceRanges(): Error | null {
|
||||
const newAst = parse(recast(this.ast))
|
||||
if (err(newAst)) {
|
||||
return newAst
|
||||
}
|
||||
|
||||
this.ast = newAst
|
||||
return null
|
||||
}
|
||||
|
||||
clearAst() {
|
||||
this._ast = {
|
||||
body: [],
|
||||
|
@ -51,16 +51,8 @@ export function getNodeFromPath<T>(
|
||||
let successfulPaths: PathToNode = []
|
||||
let pathsExplored: PathToNode = []
|
||||
for (const pathItem of path) {
|
||||
if (typeof currentNode[pathItem[0]] !== 'object') {
|
||||
if (stopAtNode) {
|
||||
return {
|
||||
node: stopAtNode,
|
||||
shallowPath: pathsExplored,
|
||||
deepPath: successfulPaths,
|
||||
}
|
||||
}
|
||||
if (typeof currentNode[pathItem[0]] !== 'object')
|
||||
return new Error('not an object')
|
||||
}
|
||||
currentNode = currentNode?.[pathItem[0]]
|
||||
successfulPaths.push(pathItem)
|
||||
if (!stopAtNode) {
|
||||
|
@ -156,20 +156,17 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
|
||||
},
|
||||
Creo: {
|
||||
pan: {
|
||||
description: 'Left click + Ctrl + drag',
|
||||
callback: (e) => butName(e).left && !butName(e).right && e.ctrlKey,
|
||||
description: 'Middle click + Shift + drag',
|
||||
callback: (e) => butName(e).middle && e.shiftKey,
|
||||
},
|
||||
zoom: {
|
||||
description: 'Scroll wheel or Right click + Ctrl + drag',
|
||||
dragCallback: (e) => butName(e).right && !butName(e).left && e.ctrlKey,
|
||||
description: 'Scroll wheel or Middle click + Ctrl + drag',
|
||||
dragCallback: (e) => butName(e).middle && e.ctrlKey,
|
||||
scrollCallback: () => true,
|
||||
},
|
||||
rotate: {
|
||||
description: 'Middle (or Left + Right) click + Ctrl + drag',
|
||||
callback: (e) => {
|
||||
const b = butName(e)
|
||||
return (b.middle || (b.left && b.right)) && e.ctrlKey
|
||||
},
|
||||
description: 'Middle click + drag',
|
||||
callback: (e) => butName(e).middle && noModifiersPressed(e),
|
||||
},
|
||||
},
|
||||
AutoCAD: {
|
||||
|
@ -360,14 +360,6 @@ export function isSelectionLastLine(
|
||||
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 = {
|
||||
selection: Selection
|
||||
ast: Program
|
||||
|
@ -126,17 +126,11 @@ async function getUser(context: UserContext) {
|
||||
if (!token && isTauri()) return Promise.reject(new Error('No token found'))
|
||||
if (token) headers['Authorization'] = `Bearer ${context.token}`
|
||||
|
||||
if (SKIP_AUTH) {
|
||||
// For local tests
|
||||
if (localStorage.getItem('FORCE_NO_IMAGE')) {
|
||||
LOCAL_USER.image = ''
|
||||
}
|
||||
|
||||
if (SKIP_AUTH)
|
||||
return {
|
||||
user: LOCAL_USER,
|
||||
token,
|
||||
}
|
||||
}
|
||||
|
||||
const userPromise = !isTauri()
|
||||
? fetch(url, {
|
||||
@ -150,11 +144,6 @@ async function getUser(context: UserContext) {
|
||||
|
||||
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))
|
||||
|
||||
return {
|
||||
|
@ -2,18 +2,13 @@ import { OnboardingButtons, useDismiss, useNextClick } from '.'
|
||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
|
||||
export default function UserMenu() {
|
||||
const { context } = useModelingContext()
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.PROJECT_MENU)
|
||||
const [avatarErrored, setAvatarErrored] = useState(false)
|
||||
|
||||
const user = auth?.context?.user
|
||||
const errorOrNoImage = !user?.image || avatarErrored
|
||||
const buttonDescription = errorOrNoImage ? 'the menu button' : 'your avatar'
|
||||
const buttonDescription = !avatarErrored ? 'your avatar' : 'the menu button'
|
||||
|
||||
// Set up error handling for the user's avatar image,
|
||||
// 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]]
|
||||
name = "kcl-lib"
|
||||
version = "0.1.72"
|
||||
version = "0.1.71"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx",
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-lib"
|
||||
description = "KittyCAD Language implementation and tools"
|
||||
version = "0.1.72"
|
||||
version = "0.1.71"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
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 mut extrude_groups = Vec::new();
|
||||
for sketch_group in &sketch_groups {
|
||||
// Before we extrude, we need to enable the sketch mode.
|
||||
// We do this here in case extrude is called out of order.
|
||||
args.batch_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
kittycad::types::ModelingCmd::EnableSketchMode {
|
||||
animated: false,
|
||||
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?;
|
||||
// Make sure we exited sketch mode if sketching on a plane.
|
||||
if let SketchSurface::Plane(_) = sketch_group.on {
|
||||
// Disable the sketch mode.
|
||||
// This is necessary for when people don't close the sketch explicitly.
|
||||
// The sketch mode will mess up the extrude direction if still active.
|
||||
args.batch_modeling_cmd(uuid::Uuid::new_v4(), kittycad::types::ModelingCmd::SketchModeDisable {})
|
||||
.await?;
|
||||
}
|
||||
|
||||
args.send_modeling_cmd(
|
||||
id,
|
||||
@ -105,10 +95,6 @@ async fn inner_extrude(length: f64, sketch_group_set: SketchGroupSet, args: Args
|
||||
},
|
||||
)
|
||||
.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?);
|
||||
}
|
||||
|
||||
@ -121,6 +107,13 @@ pub(crate) async fn do_post_extrude(
|
||||
id: Uuid,
|
||||
args: Args,
|
||||
) -> 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.
|
||||
// See: https://github.com/KittyCAD/modeling-app/issues/806
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
#[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 |