Compare commits

...

7 Commits

Author SHA1 Message Date
32068983a0 Merge branch 'main' into jtran/fix-constraints 2024-07-10 15:39:44 -04:00
263a4f324d Handle the case of no avatar (#2959)
* Handle the case of no avatar

* ci go

* Scope to the top if...

* Account for CI's usage of dev API key causing avatar to show
2024-07-10 13:13:33 -04:00
3160c58d8a After a sketch keep the extrude button active (#2961)
* After a sketch keep the extrude button active

* add test

* Compare to 0,0 not any x,x or y,y

---------

Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2024-07-10 10:08:15 -04:00
73e26cbb4d Extrude bug (#2986)
* fix bug

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* images

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* docs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-07-09 23:39:59 -04:00
21e2a92f54 Fix Creo camera controls to use correct gestures (#2963)
Co-authored-by: Frank Noirot <frank@zoo.dev>
2024-07-10 09:04:58 +10:00
d7f2bfdabe deleting start of sketch => line tool should still work (#2983)
* deleting start of sketch line tool should still work

* add test

* fmt

* put big timout back in

* shotkey test patch

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)"

This reverts commit 6ee690a65a.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-07-10 07:01:49 +10:00
e63134e9fb Fix constraints to be enabled after dragging sketch
Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2024-07-05 17:41:22 -04:00
33 changed files with 293 additions and 36 deletions

2
.gitignore vendored
View File

@ -58,3 +58,5 @@ src/wasm-lib/grackle/stdlib_cube_partial.json
Mac_App_Distribution.provisionprofile
*.tsbuildinfo
venv

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2458,6 +2458,44 @@ 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', () => {
@ -3928,6 +3966,55 @@ 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 () => {
@ -4317,7 +4404,7 @@ test.describe('Sketch tests', () => {
await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([6.44, -12.07], %)
|> line([14.72, 2.01], %)
|> line([14.72, 1.97], %)
|> tangentialArcTo([24.95, -5.38], %)
|> line([1.97, 2.06], %)
|> close(%)
@ -4514,6 +4601,53 @@ 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 () => {
@ -7506,17 +7640,25 @@ 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(2000)
await page.waitForTimeout(1000)
// 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(2000)
await page.waitForTimeout(1000)
await page.mouse.move(800, 300, { steps: 5 })
await page.mouse.click(800, 300)
await page.waitForTimeout(2000)
await page.waitForTimeout(1000)
await expect(lineButton).toHaveAttribute('aria-pressed', 'true', {
timeout: 15_000,
})

View File

@ -857,6 +857,11 @@ 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({

View File

@ -35,6 +35,7 @@ import {
canExtrudeSelection,
handleSelectionBatch,
isSelectionLastLine,
isRangeInbetweenCharacters,
isSketchPipe,
updateSelections,
} from 'lib/selections'
@ -425,6 +426,7 @@ export const ModelingMachineProvider = ({
if (
selectionRanges.codeBasedSelections.length === 0 ||
isRangeInbetweenCharacters(selectionRanges) ||
isSelectionLastLine(selectionRanges, codeManager.code)
) {
// they have no selection, we should enable the button

View File

@ -154,6 +154,16 @@ 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: [],

View File

@ -51,8 +51,16 @@ export function getNodeFromPath<T>(
let successfulPaths: PathToNode = []
let pathsExplored: PathToNode = []
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')
}
currentNode = currentNode?.[pathItem[0]]
successfulPaths.push(pathItem)
if (!stopAtNode) {

View File

@ -156,17 +156,20 @@ export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
},
Creo: {
pan: {
description: 'Middle click + Shift + drag',
callback: (e) => butName(e).middle && e.shiftKey,
description: 'Left click + Ctrl + drag',
callback: (e) => butName(e).left && !butName(e).right && e.ctrlKey,
},
zoom: {
description: 'Scroll wheel or Middle click + Ctrl + drag',
dragCallback: (e) => butName(e).middle && e.ctrlKey,
description: 'Scroll wheel or Right click + Ctrl + drag',
dragCallback: (e) => butName(e).right && !butName(e).left && e.ctrlKey,
scrollCallback: () => true,
},
rotate: {
description: 'Middle click + drag',
callback: (e) => butName(e).middle && noModifiersPressed(e),
description: 'Middle (or Left + Right) click + Ctrl + drag',
callback: (e) => {
const b = butName(e)
return (b.middle || (b.left && b.right)) && e.ctrlKey
},
},
},
AutoCAD: {

View File

@ -360,6 +360,14 @@ 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

View File

@ -126,11 +126,17 @@ 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)
if (SKIP_AUTH) {
// For local tests
if (localStorage.getItem('FORCE_NO_IMAGE')) {
LOCAL_USER.image = ''
}
return {
user: LOCAL_USER,
token,
}
}
const userPromise = !isTauri()
? fetch(url, {
@ -144,6 +150,11 @@ 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 {

File diff suppressed because one or more lines are too long

View File

@ -2,13 +2,18 @@ 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 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,
// so the onboarding text can be updated if it fails to load.

View File

@ -1385,7 +1385,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.1.71"
version = "0.1.72"
dependencies = [
"anyhow",
"approx",

View File

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

View File

@ -77,14 +77,24 @@ 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 {
// 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?;
}
// 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?;
args.send_modeling_cmd(
id,
@ -95,6 +105,10 @@ 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?);
}
@ -107,13 +121,6 @@ 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(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

View File

@ -0,0 +1,29 @@
// 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)

View File

@ -2501,3 +2501,10 @@ 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);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 102 KiB