Compare commits

...

10 Commits

Author SHA1 Message Date
fcf3e89013 Look at this (photo)Graph *in the voice of Nickelback* 2024-08-12 06:23:46 +00:00
ed7b54c52e update graph snapshot sizes (too cluttered 2024-08-12 16:16:11 +10:00
f715509a86 tweak fillet ast mod for adjacent opposite edges 2024-08-12 16:16:11 +10:00
d73e5ca7c0 add edges to artifact graph 2024-08-12 16:16:11 +10:00
b9fe3ed9e0 try smaller ubuntu for playwright (#3358)
* try smaller ubuntu

* fix reconnect test
2024-08-12 16:09:44 +10:00
5a25b60485 fix playwright (#3352)
* fix playwright

* fix another test
2024-08-12 15:16:13 +10:00
4b9f71c994 Update machine-api spec (#3346)
* YOYO NEW API SPEC!

* New machine-api types

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-11 18:04:30 -07:00
e86a5622c8 the printer slicer expects mm (#3341)
* the printer slicer expects mm

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

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

* empty

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-08-09 18:05:18 -07:00
a503d1ce50 Use named constants for settings URL query params (#3333) 2024-08-09 02:47:25 -04:00
11a94cc99e set global test timeout using the Playwright config (#3330)
set global test timeout

Co-authored-by: ryanrosello-og <ry@zoo.dev>
2024-08-08 10:49:16 +00:00
48 changed files with 582 additions and 183 deletions

View File

@ -35,7 +35,7 @@ jobs:
playwright-ubuntu: playwright-ubuntu:
timeout-minutes: 30 timeout-minutes: 30
runs-on: ubuntu-latest-8-cores runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:

View File

@ -151,11 +151,12 @@ test.describe('Sketch tests', () => {
await page.mouse.click(700, 200) await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText( await expect.poll(u.normalisedEditorCode)
`const sketch001 = startSketchOn('XZ') .toBe(`const sketch001 = startSketchOn('XZ')
|> startProfileAt([4.61, -14.01], %) |> startProfileAt([12.34, -12.34], %)
|> line([0.31, 16.47], %)` |> line([-12.34, 12.34], %)
)
`)
}) })
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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -204,19 +204,24 @@ test.describe('Test network and connection issues', () => {
// Ensure we can continue sketching // Ensure we can continue sketching
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content')) await expect.poll(u.normalisedEditorCode)
.toHaveText(`const sketch001 = startSketchOn('XZ') .toBe(`const sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %) |> startProfileAt([12.34, -12.34], %)
|> line([${commonPoints.num1}, 0], %) |> line([12.34, 0], %)
|> line([-8.84, 8.75], %)`) |> line([-12.34, 12.34], %)
`)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20) await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const sketch001 = startSketchOn('XZ') await expect.poll(u.normalisedEditorCode)
|> startProfileAt(${commonPoints.startAt}, %) .toBe(`const sketch001 = startSketchOn('XZ')
|> line([${commonPoints.num1}, 0], %) |> startProfileAt([12.34, -12.34], %)
|> line([-8.84, 8.75], %) |> line([12.34, 0], %)
|> line([-5.6, 0], %)`) |> line([-12.34, 12.34], %)
|> line([-12.34, 0], %)
`)
// Unequip line tool // Unequip line tool
await page.keyboard.press('Escape') await page.keyboard.press('Escape')

View File

@ -268,6 +268,18 @@ async function waitForAuthAndLsp(page: Page) {
return waitForLspPromise return waitForLspPromise
} }
export function normaliseKclNumbers(code: string, ignoreZero = true): string {
const numberRegexp = /(?<!\w)-?\b\d+(\.\d+)?\b(?!\w)/g
const replaceNumber = (number: string) => {
if (ignoreZero && (number === '0' || number === '-0')) return number
const sign = number.startsWith('-') ? '-' : ''
return `${sign}12.34`
}
const replaceNumbers = (text: string) =>
text.replace(numberRegexp, replaceNumber)
return replaceNumbers(code)
}
export async function getUtils(page: Page) { export async function getUtils(page: Page) {
// Chrome devtools protocol session only works in Chromium // Chrome devtools protocol session only works in Chromium
const browserType = page.context().browser()?.browserType().name() const browserType = page.context().browser()?.browserType().name()
@ -330,6 +342,11 @@ export async function getUtils(page: Page) {
.boundingBox() .boundingBox()
.then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })), .then((box) => ({ ...box, x: box?.x || 0, y: box?.y || 0 })),
codeLocator: page.locator('.cm-content'), codeLocator: page.locator('.cm-content'),
normalisedEditorCode: async () => {
const code = await page.locator('.cm-content').innerText()
return normaliseKclNumbers(code)
},
normalisedCode: (code: string) => normaliseKclNumbers(code),
canvasLocator: page.getByTestId('client-side-scene'), canvasLocator: page.getByTestId('client-side-scene'),
doAndWaitForCmd: async ( doAndWaitForCmd: async (
fn: () => Promise<void>, fn: () => Promise<void>,

View File

@ -80,11 +80,11 @@ const part001 = startSketchOn('XZ')
|> line([74.36, 130.4], %, $seg01) |> line([74.36, 130.4], %, $seg01)
|> line([78.92, -120.11], %) |> line([78.92, -120.11], %)
|> angledLine([segAng(seg01), yo], %) |> angledLine([segAng(seg01), yo], %)
|> line([41.19, 28.97 + 5], %) |> line([41.19, 58.97 + 5], %)
const part002 = startSketchOn('XZ') const part002 = startSketchOn('XZ')
|> startProfileAt([299.05, 231.45], %) |> startProfileAt([299.05, 120], %)
|> xLine(-425.34, %, $seg_what) |> xLine(-385.34, %, $seg_what)
|> yLine(-264.06, %) |> yLine(-170.06, %)
|> xLine(segLen(seg_what), %) |> xLine(segLen(seg_what), %)
|> lineTo([profileStartX(%), profileStartY(%)], %)` |> lineTo([profileStartX(%), profileStartY(%)], %)`
) )
@ -138,7 +138,7 @@ const part001 = startSketchOn('XZ')
|> line([74.36, 130.4], %, $seg01) |> line([74.36, 130.4], %, $seg01)
|> line([78.92, -120.11], %) |> line([78.92, -120.11], %)
|> angledLine([segAng(seg01), 78.33], %) |> angledLine([segAng(seg01), 78.33], %)
|> line([41.19, 28.97], %) |> line([51.19, 48.97], %)
const part002 = startSketchOn('XZ') const part002 = startSketchOn('XZ')
|> startProfileAt([299.05, 231.45], %) |> startProfileAt([299.05, 231.45], %)
|> xLine(-425.34, %, $seg_what) |> xLine(-425.34, %, $seg_what)
@ -237,7 +237,7 @@ const part001 = startSketchOn('XZ')
|> line([74.36, 130.4], %) |> line([74.36, 130.4], %)
|> line([78.92, -120.11], %) |> line([78.92, -120.11], %)
|> line([9.16, 77.79], %) |> line([9.16, 77.79], %)
|> line([41.19, 28.97], %) |> line([51.19, 48.97], %)
const part002 = startSketchOn('XZ') const part002 = startSketchOn('XZ')
|> startProfileAt([299.05, 231.45], %) |> startProfileAt([299.05, 231.45], %)
|> xLine(-425.34, %, $seg_what) |> xLine(-425.34, %, $seg_what)
@ -343,7 +343,7 @@ const part001 = startSketchOn('XZ')
|> line([74.36, 130.4], %) |> line([74.36, 130.4], %)
|> line([78.92, -120.11], %) |> line([78.92, -120.11], %)
|> line([9.16, 77.79], %) |> line([9.16, 77.79], %)
|> line([41.19, 28.97], %) |> line([51.19, 48.97], %)
const part002 = startSketchOn('XZ') const part002 = startSketchOn('XZ')
|> startProfileAt([299.05, 231.45], %) |> startProfileAt([299.05, 231.45], %)
|> xLine(-425.34, %, $seg_what) |> xLine(-425.34, %, $seg_what)
@ -450,7 +450,7 @@ const part001 = startSketchOn('XZ')
|> line([74.36, 130.4], %) |> line([74.36, 130.4], %)
|> line([78.92, -120.11], %) |> line([78.92, -120.11], %)
|> line([9.16, 77.79], %) |> line([9.16, 77.79], %)
|> line([41.19, 28.97], %) |> line([51.19, 48.97], %)
const part002 = startSketchOn('XZ') const part002 = startSketchOn('XZ')
|> startProfileAt([299.05, 231.45], %) |> startProfileAt([299.05, 231.45], %)
|> xLine(-425.34, %, $seg_what) |> xLine(-425.34, %, $seg_what)
@ -560,7 +560,7 @@ const part001 = startSketchOn('XZ')
|> line([74.36, 130.4], %) |> line([74.36, 130.4], %)
|> line([78.92, -120.11], %) |> line([78.92, -120.11], %)
|> line([9.16, 77.79], %) |> line([9.16, 77.79], %)
|> line([41.19, 28.97], %) |> line([51.19, 48.97], %)
const part002 = startSketchOn('XZ') const part002 = startSketchOn('XZ')
|> startProfileAt([299.05, 231.45], %) |> startProfileAt([299.05, 231.45], %)
|> xLine(-425.34, %, $seg_what) |> xLine(-425.34, %, $seg_what)
@ -613,14 +613,14 @@ const part002 = startSketchOn('XZ')
codeAfter: [ codeAfter: [
`|> yLine(130.4, %)`, `|> yLine(130.4, %)`,
`|> yLine(77.79, %)`, `|> yLine(77.79, %)`,
`|> yLine(28.97, %)`, `|> yLine(48.97, %)`,
], ],
}, },
{ {
codeAfter: [ codeAfter: [
`|> xLine(74.36, %)`, `|> xLine(74.36, %)`,
`|> xLine(9.16, %)`, `|> xLine(9.16, %)`,
`|> xLine(41.19, %)`, `|> xLine(51.19, %)`,
], ],
constraintName: 'Horizontal', constraintName: 'Horizontal',
}, },
@ -636,7 +636,7 @@ const part001 = startSketchOn('XZ')
|> line([74.36, 130.4], %) |> line([74.36, 130.4], %)
|> line([78.92, -120.11], %) |> line([78.92, -120.11], %)
|> line([9.16, 77.79], %) |> line([9.16, 77.79], %)
|> line([41.19, 28.97], %) |> line([51.19, 48.97], %)
const part002 = startSketchOn('XZ') const part002 = startSketchOn('XZ')
|> startProfileAt([299.05, 231.45], %) |> startProfileAt([299.05, 231.45], %)
|> xLine(-425.34, %, $seg_what) |> xLine(-425.34, %, $seg_what)

View File

@ -468,7 +468,7 @@ test('Sketch on face', async ({ page }) => {
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await u.doAndWaitForCmd( await u.doAndWaitForCmd(
() => page.mouse.click(625, 133), () => page.mouse.click(625, 165),
'default_camera_get_settings', 'default_camera_get_settings',
true true
) )
@ -498,13 +498,14 @@ test('Sketch on face', async ({ page }) => {
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText() previousCodeContent = await page.locator('.cm-content').innerText()
await expect(page.locator('.cm-content')) await expect.poll(u.normalisedEditorCode).toContain(
.toContainText(`const sketch002 = startSketchOn(extrude001, seg01) u.normalisedCode(`const sketch002 = startSketchOn(extrude001, seg01)
|> startProfileAt([-12.94, 6.6], %) |> startProfileAt([-12.94, 6.6], %)
|> line([2.45, -0.2], %) |> line([2.45, -0.2], %)
|> line([-2.6, -1.25], %) |> line([-2.6, -1.25], %)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)`) |> close(%)`)
)
await u.openAndClearDebugPanel() await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click() await page.getByRole('button', { name: 'Exit Sketch' }).click()
@ -513,7 +514,7 @@ test('Sketch on face', async ({ page }) => {
await u.updateCamPosition([1049, 239, 686]) await u.updateCamPosition([1049, 239, 686])
await u.closeDebugPanel() await u.closeDebugPanel()
await page.getByText('startProfileAt([-12.94, 6.6], %)').click() await page.getByText('startProfileAt([-12').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible() await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400) await page.waitForTimeout(400)
@ -549,7 +550,7 @@ test('Sketch on face', async ({ page }) => {
await page.getByRole('button', { name: 'Exit Sketch' }).click() await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await page.getByText('startProfileAt([-12.94, 6.6], %)').click() await page.getByText('startProfileAt([-12').click()
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled() await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
await page.waitForTimeout(100) await page.waitForTimeout(100)

View File

@ -79,7 +79,8 @@
"$ref": "#/components/schemas/Result" "$ref": "#/components/schemas/Result"
} }
], ],
"description": "The result of the info command." "description": "The result of the info command.",
"nullable": true
}, },
"sequence_id": { "sequence_id": {
"allOf": [ "allOf": [
@ -93,7 +94,6 @@
"required": [ "required": [
"command", "command",
"module", "module",
"result",
"sequence_id" "sequence_id"
], ],
"type": "object" "type": "object"

View File

@ -10,6 +10,7 @@ import { defineConfig, devices } from '@playwright/test'
* See https://playwright.dev/docs/test-configuration. * See https://playwright.dev/docs/test-configuration.
*/ */
export default defineConfig({ export default defineConfig({
timeout: 120_000, // override the default 30s timeout
testDir: './e2e/playwright', testDir: './e2e/playwright',
/* Run tests in files in parallel */ /* Run tests in files in parallel */
fullyParallel: true, fullyParallel: true,

View File

@ -9,7 +9,7 @@ import { useHotkeys } from 'react-hotkeys-hook'
import { getNormalisedCoordinates } from './lib/utils' import { getNormalisedCoordinates } from './lib/utils'
import { useLoaderData, useNavigate } from 'react-router-dom' import { useLoaderData, useNavigate } from 'react-router-dom'
import { type IndexLoaderData } from 'lib/types' import { type IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions' import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
@ -28,7 +28,7 @@ import { CoreDumpManager } from 'lib/coredump'
import { UnitsMenu } from 'components/UnitsMenu' import { UnitsMenu } from 'components/UnitsMenu'
export function App() { export function App() {
useRefreshSettings(paths.FILE + 'SETTINGS') useRefreshSettings(PATHS.FILE + 'SETTINGS')
const { project, file } = useLoaderData() as IndexLoaderData const { project, file } = useLoaderData() as IndexLoaderData
const navigate = useNavigate() const navigate = useNavigate()
const filePath = useAbsoluteFilePath() const filePath = useAbsoluteFilePath()
@ -63,7 +63,7 @@ export function App() {
}) })
useHotkeyWrapper( useHotkeyWrapper(
[isTauri() ? 'mod + ,' : 'shift + mod + ,'], [isTauri() ? 'mod + ,' : 'shift + mod + ,'],
() => navigate(filePath + paths.SETTINGS), () => navigate(filePath + PATHS.SETTINGS),
{ {
splitKey: '|', splitKey: '|',
} }

View File

@ -20,7 +20,7 @@ import { WasmErrBanner } from 'components/WasmErrBanner'
import { CommandBar } from 'components/CommandBar/CommandBar' import { CommandBar } from 'components/CommandBar/CommandBar'
import ModelingMachineProvider from 'components/ModelingMachineProvider' import ModelingMachineProvider from 'components/ModelingMachineProvider'
import FileMachineProvider from 'components/FileMachineProvider' import FileMachineProvider from 'components/FileMachineProvider'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { import {
fileLoader, fileLoader,
homeLoader, homeLoader,
@ -45,7 +45,7 @@ import { AppStateProvider } from 'AppState'
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
loader: settingsLoader, loader: settingsLoader,
id: paths.INDEX, id: PATHS.INDEX,
/* Make sure auth is the outermost provider or else we will have /* Make sure auth is the outermost provider or else we will have
* inefficient re-renders, use the react profiler to see. */ * inefficient re-renders, use the react profiler to see. */
element: ( element: (
@ -64,7 +64,7 @@ const router = createBrowserRouter([
errorElement: <ErrorPage />, errorElement: <ErrorPage />,
children: [ children: [
{ {
path: paths.INDEX, path: PATHS.INDEX,
loader: async () => { loader: async () => {
const inTauri = isTauri() const inTauri = isTauri()
if (inTauri) { if (inTauri) {
@ -78,21 +78,21 @@ const router = createBrowserRouter([
// Redirect to the file if we have a file path. // Redirect to the file if we have a file path.
if (appState.current_file) { if (appState.current_file) {
return redirect( return redirect(
paths.FILE + '/' + encodeURIComponent(appState.current_file) PATHS.FILE + '/' + encodeURIComponent(appState.current_file)
) )
} }
} }
} }
return inTauri return inTauri
? redirect(paths.HOME) ? redirect(PATHS.HOME)
: redirect(paths.FILE + '/%2F' + BROWSER_PROJECT_NAME) : redirect(PATHS.FILE + '/%2F' + BROWSER_PROJECT_NAME)
}, },
}, },
{ {
loader: fileLoader, loader: fileLoader,
id: paths.FILE, id: PATHS.FILE,
path: paths.FILE + '/:id', path: PATHS.FILE + '/:id',
element: ( element: (
<Auth> <Auth>
<FileMachineProvider> <FileMachineProvider>
@ -109,7 +109,7 @@ const router = createBrowserRouter([
), ),
children: [ children: [
{ {
id: paths.FILE + 'SETTINGS', id: PATHS.FILE + 'SETTINGS',
loader: settingsLoader, loader: settingsLoader,
children: [ children: [
{ {
@ -118,11 +118,11 @@ const router = createBrowserRouter([
element: <></>, element: <></>,
}, },
{ {
path: makeUrlPathRelative(paths.SETTINGS), path: makeUrlPathRelative(PATHS.SETTINGS),
element: <Settings />, element: <Settings />,
}, },
{ {
path: makeUrlPathRelative(paths.ONBOARDING.INDEX), path: makeUrlPathRelative(PATHS.ONBOARDING.INDEX),
element: <Onboarding />, element: <Onboarding />,
children: onboardingRoutes, children: onboardingRoutes,
}, },
@ -131,7 +131,7 @@ const router = createBrowserRouter([
], ],
}, },
{ {
path: paths.HOME, path: PATHS.HOME,
element: ( element: (
<Auth> <Auth>
<Outlet /> <Outlet />
@ -139,24 +139,24 @@ const router = createBrowserRouter([
<CommandBar /> <CommandBar />
</Auth> </Auth>
), ),
id: paths.HOME, id: PATHS.HOME,
loader: homeLoader, loader: homeLoader,
children: [ children: [
{ {
index: true, index: true,
element: <></>, element: <></>,
id: paths.HOME + 'SETTINGS', id: PATHS.HOME + 'SETTINGS',
loader: settingsLoader, loader: settingsLoader,
}, },
{ {
path: makeUrlPathRelative(paths.SETTINGS), path: makeUrlPathRelative(PATHS.SETTINGS),
loader: settingsLoader, loader: settingsLoader,
element: <Settings />, element: <Settings />,
}, },
], ],
}, },
{ {
path: paths.SIGN_IN, path: PATHS.SIGN_IN,
element: <SignIn />, element: <SignIn />,
}, },
], ],

View File

@ -1,6 +1,6 @@
import { ActionIcon, ActionIconProps } from './ActionIcon' import { ActionIcon, ActionIconProps } from './ActionIcon'
import React, { ForwardedRef, forwardRef } from 'react' import React, { ForwardedRef, forwardRef } from 'react'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import type { LinkProps } from 'react-router-dom' import type { LinkProps } from 'react-router-dom'
@ -82,7 +82,7 @@ export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
return ( return (
<Link <Link
ref={ref as ForwardedRef<HTMLAnchorElement>} ref={ref as ForwardedRef<HTMLAnchorElement>}
to={to || paths.INDEX} to={to || PATHS.INDEX}
className={classNames} className={classNames}
{...rest} {...rest}
> >
@ -105,7 +105,7 @@ export const ActionButton = forwardRef((props: ActionButtonProps, ref) => {
return ( return (
<Link <Link
ref={ref as ForwardedRef<HTMLAnchorElement>} ref={ref as ForwardedRef<HTMLAnchorElement>}
to={to || paths.INDEX} to={to || PATHS.INDEX}
className={classNames} className={classNames}
{...rest} {...rest}
target="_blank" target="_blank"

View File

@ -1,7 +1,7 @@
import { useMachine } from '@xstate/react' import { useMachine } from '@xstate/react'
import { useNavigate, useRouteLoaderData } from 'react-router-dom' import { useNavigate, useRouteLoaderData } from 'react-router-dom'
import { type IndexLoaderData } from 'lib/types' import { type IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import React, { createContext } from 'react' import React, { createContext } from 'react'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { import {
@ -38,7 +38,7 @@ export const FileMachineProvider = ({
}) => { }) => {
const navigate = useNavigate() const navigate = useNavigate()
const { commandBarSend } = useCommandsContext() const { commandBarSend } = useCommandsContext()
const { project, file } = useRouteLoaderData(paths.FILE) as IndexLoaderData const { project, file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const [state, send] = useMachine(fileMachine, { const [state, send] = useMachine(fileMachine, {
context: { context: {
@ -50,7 +50,7 @@ export const FileMachineProvider = ({
if (event.data && 'name' in event.data) { if (event.data && 'name' in event.data) {
commandBarSend({ type: 'Close' }) commandBarSend({ type: 'Close' })
navigate( navigate(
`${paths.FILE}/${encodeURIComponent( `${PATHS.FILE}/${encodeURIComponent(
context.selectedDirectory + sep() + event.data.name context.selectedDirectory + sep() + event.data.name
)}` )}`
) )
@ -60,7 +60,7 @@ export const FileMachineProvider = ({
event.data.path.endsWith(FILE_EXT) event.data.path.endsWith(FILE_EXT)
) { ) {
// Don't navigate to newly created directories // Don't navigate to newly created directories
navigate(`${paths.FILE}/${encodeURIComponent(event.data.path)}`) navigate(`${PATHS.FILE}/${encodeURIComponent(event.data.path)}`)
} }
}, },
addFileToRenamingQueue: assign({ addFileToRenamingQueue: assign({
@ -130,11 +130,11 @@ export const FileMachineProvider = ({
if (oldPath === file?.path && project?.path) { if (oldPath === file?.path && project?.path) {
// If we just renamed the current file, navigate to the new path // If we just renamed the current file, navigate to the new path
navigate(paths.FILE + '/' + encodeURIComponent(newPath)) navigate(PATHS.FILE + '/' + encodeURIComponent(newPath))
} else if (file?.path.includes(oldPath)) { } else if (file?.path.includes(oldPath)) {
// If we just renamed a directory that the current file is in, navigate to the new path // If we just renamed a directory that the current file is in, navigate to the new path
navigate( navigate(
paths.FILE + PATHS.FILE +
'/' + '/' +
encodeURIComponent(file.path.replace(oldPath, newDirPath)) encodeURIComponent(file.path.replace(oldPath, newDirPath))
) )
@ -169,7 +169,7 @@ export const FileMachineProvider = ({
file?.path.includes(event.data.path)) && file?.path.includes(event.data.path)) &&
project?.path project?.path
) { ) {
navigate(paths.FILE + '/' + encodeURIComponent(project.path)) navigate(PATHS.FILE + '/' + encodeURIComponent(project.path))
} }
return `Successfully deleted ${isDir ? 'folder' : 'file'} "${ return `Successfully deleted ${isDir ? 'folder' : 'file'} "${

View File

@ -1,5 +1,5 @@
import type { FileEntry, IndexLoaderData } from 'lib/types' import type { FileEntry, IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { ActionButton } from './ActionButton' import { ActionButton } from './ActionButton'
import Tooltip from './Tooltip' import Tooltip from './Tooltip'
import { Dispatch, useCallback, useEffect, useRef, useState } from 'react' import { Dispatch, useCallback, useEffect, useRef, useState } from 'react'
@ -187,7 +187,7 @@ const FileTreeItem = ({
onFileOpen(fileOrDir.path, project?.path || null) onFileOpen(fileOrDir.path, project?.path || null)
// Open kcl files // Open kcl files
navigate(`${paths.FILE}/${encodeURIComponent(fileOrDir.path)}`) navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`)
} }
onNavigateToFile?.() onNavigateToFile?.()
} }
@ -447,7 +447,7 @@ export const FileTreeInner = ({
}: { }: {
onNavigateToFile?: () => void onNavigateToFile?: () => void
}) => { }) => {
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const { send: fileSend, context: fileContext } = useFileContext() const { send: fileSend, context: fileContext } = useFileContext()
const { send: modelingSend } = useModelingContext() const { send: modelingSend } = useModelingContext()
const documentHasFocus = useDocumentHasFocus() const documentHasFocus = useDocumentHasFocus()

View File

@ -4,7 +4,7 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { createAndOpenNewProject } from 'lib/tauriFS' import { createAndOpenNewProject } from 'lib/tauriFS'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { useLspContext } from './LspProvider' import { useLspContext } from './LspProvider'
@ -16,7 +16,7 @@ export function HelpMenu(props: React.PropsWithChildren) {
const location = useLocation() const location = useLocation()
const { onProjectOpen } = useLspContext() const { onProjectOpen } = useLspContext()
const filePath = useAbsoluteFilePath() const filePath = useAbsoluteFilePath()
const isInProject = location.pathname.includes(paths.FILE) const isInProject = location.pathname.includes(PATHS.FILE)
const navigate = useNavigate() const navigate = useNavigate()
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
@ -89,10 +89,10 @@ export function HelpMenu(props: React.PropsWithChildren) {
<HelpMenuItem <HelpMenuItem
as="button" as="button"
onClick={() => { onClick={() => {
const targetPath = location.pathname.includes(paths.FILE) const targetPath = location.pathname.includes(PATHS.FILE)
? filePath + paths.SETTINGS ? filePath + PATHS.SETTINGS_KEYBINDINGS
: paths.HOME + paths.SETTINGS : PATHS.HOME + PATHS.SETTINGS_KEYBINDINGS
navigate(targetPath + '?tab=keybindings') navigate(targetPath)
}} }}
> >
Keyboard shortcuts Keyboard shortcuts
@ -108,7 +108,7 @@ export function HelpMenu(props: React.PropsWithChildren) {
}, },
}) })
if (isInProject) { if (isInProject) {
navigate(filePath + paths.ONBOARDING.INDEX) navigate(filePath + PATHS.ONBOARDING.INDEX)
} else { } else {
createAndOpenNewProject({ onProjectOpen, navigate }) createAndOpenNewProject({ onProjectOpen, navigate })
} }

View File

@ -1,7 +1,7 @@
import { APP_VERSION } from 'routes/Settings' import { APP_VERSION } from 'routes/Settings'
import { CustomIcon } from 'components/CustomIcon' import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { NetworkHealthIndicator } from 'components/NetworkHealthIndicator' import { NetworkHealthIndicator } from 'components/NetworkHealthIndicator'
import { HelpMenu } from './HelpMenu' import { HelpMenu } from './HelpMenu'
import { Link, useLocation } from 'react-router-dom' import { Link, useLocation } from 'react-router-dom'
@ -87,9 +87,9 @@ export function LowerRightControls({
</a> </a>
<Link <Link
to={ to={
location.pathname.includes(paths.FILE) location.pathname.includes(PATHS.FILE)
? filePath + paths.SETTINGS + '?tab=project' ? filePath + PATHS.SETTINGS_PROJECT
: paths.HOME + paths.SETTINGS : PATHS.HOME + PATHS.SETTINGS
} }
> >
<CustomIcon <CustomIcon

View File

@ -14,7 +14,7 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Extension } from '@codemirror/state' import { Extension } from '@codemirror/state'
import { LanguageSupport } from '@codemirror/language' import { LanguageSupport } from '@codemirror/language'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { FileEntry } from 'lib/types' import { FileEntry } from 'lib/types'
import Worker from 'editor/plugins/lsp/worker.ts?worker' import Worker from 'editor/plugins/lsp/worker.ts?worker'
import { import {
@ -260,7 +260,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
}) })
if (redirect) { if (redirect) {
navigate(paths.HOME) navigate(PATHS.HOME)
} }
} }

View File

@ -388,7 +388,8 @@ export const ModelingMachineProvider = ({
}, },
}, },
storage: 'ascii', storage: 'ascii',
units: defaultUnit.current, // Convert all units to mm since that is what the slicer expects.
units: 'mm',
selection: { type: 'default_scene' }, selection: { type: 'default_scene' },
} }

View File

@ -1,5 +1,5 @@
import { FormEvent, useEffect, useRef, useState } from 'react' import { FormEvent, useEffect, useRef, useState } from 'react'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { ActionButton } from '../ActionButton' import { ActionButton } from '../ActionButton'
import { FILE_EXT } from 'lib/constants' import { FILE_EXT } from 'lib/constants'
@ -79,7 +79,7 @@ function ProjectCard({
> >
<Link <Link
data-testid="project-link" data-testid="project-link"
to={`${paths.FILE}/${encodeURIComponent(project.default_file)}`} to={`${PATHS.FILE}/${encodeURIComponent(project.default_file)}`}
className="flex flex-col flex-1 !no-underline !text-chalkboard-110 dark:!text-chalkboard-10 group-hover:!hue-rotate-0 min-h-[5em] divide-y divide-primary/40 dark:divide-chalkboard-80 group-hover:!divide-primary" className="flex flex-col flex-1 !no-underline !text-chalkboard-110 dark:!text-chalkboard-10 group-hover:!hue-rotate-0 min-h-[5em] divide-y divide-primary/40 dark:divide-chalkboard-80 group-hover:!divide-primary"
> >
{/* <div className="h-36 relative overflow-hidden bg-gradient-to-b from-transparent to-primary/10 rounded-t-sm"> {/* <div className="h-36 relative overflow-hidden bg-gradient-to-b from-transparent to-primary/10 rounded-t-sm">

View File

@ -1,7 +1,7 @@
import { Popover, Transition } from '@headlessui/react' import { Popover, Transition } from '@headlessui/react'
import { ActionButton, ActionButtonProps } from './ActionButton' import { ActionButton, ActionButtonProps } from './ActionButton'
import { type IndexLoaderData } from 'lib/types' import { type IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { isTauri } from '../lib/isTauri' import { isTauri } from '../lib/isTauri'
import { Link, useLocation, useNavigate } from 'react-router-dom' import { Link, useLocation, useNavigate } from 'react-router-dom'
import { Fragment, useMemo } from 'react' import { Fragment, useMemo } from 'react'
@ -63,7 +63,7 @@ function AppLogoLink({
// Clear the scene and end the session. // Clear the scene and end the session.
engineCommandManager.endSession() engineCommandManager.endSession()
}} }}
to={paths.HOME} to={PATHS.HOME}
className={wrapperClassName + ' hover:before:brightness-110'} className={wrapperClassName + ' hover:before:brightness-110'}
> >
<Logo className={logoClassName} /> <Logo className={logoClassName} />
@ -116,10 +116,10 @@ function ProjectMenuPopover({
</> </>
), ),
onClick: () => { onClick: () => {
const targetPath = location.pathname.includes(paths.FILE) const targetPath = location.pathname.includes(PATHS.FILE)
? filePath + paths.SETTINGS ? filePath + PATHS.SETTINGS_PROJECT
: paths.HOME + paths.SETTINGS : PATHS.HOME + PATHS.SETTINGS_PROJECT
navigate(targetPath + '?tab=project') navigate(targetPath)
}, },
}, },
'break', 'break',

View File

@ -16,7 +16,7 @@ import { getInitialDefaultDir, showInFolder } from 'lib/tauri'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { APP_VERSION } from 'routes/Settings' import { APP_VERSION } from 'routes/Settings'
import { createAndOpenNewProject, getSettingsFolderPaths } from 'lib/tauriFS' import { createAndOpenNewProject, getSettingsFolderPaths } from 'lib/tauriFS'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { useDotDotSlash } from 'hooks/useDotDotSlash' import { useDotDotSlash } from 'hooks/useDotDotSlash'
import { sep } from '@tauri-apps/api/path' import { sep } from '@tauri-apps/api/path'
import { ForwardedRef, forwardRef, useEffect } from 'react' import { ForwardedRef, forwardRef, useEffect } from 'react'
@ -44,8 +44,8 @@ export const AllSettingsFields = forwardRef(
isFileSettings && isTauri() isFileSettings && isTauri()
? decodeURI( ? decodeURI(
location.pathname location.pathname
.replace(paths.FILE + '/', '') .replace(PATHS.FILE + '/', '')
.replace(paths.SETTINGS, '') .replace(PATHS.SETTINGS, '')
.slice(0, decodeURI(location.pathname).lastIndexOf(sep())) .slice(0, decodeURI(location.pathname).lastIndexOf(sep()))
) )
: undefined : undefined
@ -70,7 +70,7 @@ export const AllSettingsFields = forwardRef(
if (isFileSettings) { if (isFileSettings) {
// If we're in a project, first navigate to the onboarding start here // If we're in a project, first navigate to the onboarding start here
// so we can trigger the warning screen if necessary // so we can trigger the warning screen if necessary
navigate(dotDotSlash(1) + paths.ONBOARDING.INDEX) navigate(dotDotSlash(1) + PATHS.ONBOARDING.INDEX)
} else { } else {
// If we're in the global settings, create a new project and navigate // If we're in the global settings, create a new project and navigate
// to the onboarding start in that project // to the onboarding start in that project

View File

@ -1,6 +1,6 @@
import { useMachine } from '@xstate/react' import { useMachine } from '@xstate/react'
import { useNavigate, useRouteLoaderData } from 'react-router-dom' import { useNavigate, useRouteLoaderData } from 'react-router-dom'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine' import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
import withBaseUrl from '../lib/withBaseURL' import withBaseUrl from '../lib/withBaseURL'
import React, { createContext, useEffect } from 'react' import React, { createContext, useEffect } from 'react'
@ -60,8 +60,8 @@ export const SettingsAuthProvider = ({
}: { }: {
children: React.ReactNode children: React.ReactNode
}) => { }) => {
const loadedSettings = useRouteLoaderData(paths.INDEX) as typeof settings const loadedSettings = useRouteLoaderData(PATHS.INDEX) as typeof settings
const loadedProject = useRouteLoaderData(paths.FILE) as IndexLoaderData const loadedProject = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
return ( return (
<SettingsAuthProviderBase <SettingsAuthProviderBase
loadedSettings={loadedSettings} loadedSettings={loadedSettings}
@ -297,12 +297,12 @@ export const SettingsAuthProviderBase = ({
const [authState, authSend, authActor] = useMachine(authMachine, { const [authState, authSend, authActor] = useMachine(authMachine, {
actions: { actions: {
goToSignInPage: () => { goToSignInPage: () => {
navigate(paths.SIGN_IN) navigate(PATHS.SIGN_IN)
logout() logout()
}, },
goToIndexPage: () => { goToIndexPage: () => {
if (window.location.pathname.includes(paths.SIGN_IN)) { if (window.location.pathname.includes(PATHS.SIGN_IN)) {
navigate(paths.INDEX) navigate(PATHS.INDEX)
} }
}, },
}, },

View File

@ -2,7 +2,7 @@ import { Popover, Transition } from '@headlessui/react'
import { ActionButton, ActionButtonProps } from './ActionButton' import { ActionButton, ActionButtonProps } from './ActionButton'
import { useLocation, useNavigate } from 'react-router-dom' import { useLocation, useNavigate } from 'react-router-dom'
import { Fragment, useMemo, useState } from 'react' import { Fragment, useMemo, useState } from 'react'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
@ -39,10 +39,10 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
), ),
'data-testid': 'user-settings', 'data-testid': 'user-settings',
onClick: () => { onClick: () => {
const targetPath = location.pathname.includes(paths.FILE) const targetPath = location.pathname.includes(PATHS.FILE)
? filePath + paths.SETTINGS ? filePath + PATHS.SETTINGS_USER
: paths.HOME + paths.SETTINGS : PATHS.HOME + PATHS.SETTINGS_USER
navigate(targetPath + '?tab=user') navigate(targetPath)
}, },
}, },
{ {
@ -50,10 +50,10 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
Element: 'button', Element: 'button',
children: 'Keyboard shortcuts', children: 'Keyboard shortcuts',
onClick: () => { onClick: () => {
const targetPath = location.pathname.includes(paths.FILE) const targetPath = location.pathname.includes(PATHS.FILE)
? filePath + paths.SETTINGS ? filePath + PATHS.SETTINGS_KEYBINDINGS
: paths.HOME + paths.SETTINGS : PATHS.HOME + PATHS.SETTINGS_KEYBINDINGS
navigate(targetPath + '?tab=keybindings') navigate(targetPath)
}, },
}, },
{ {

View File

@ -1,11 +1,11 @@
import { type IndexLoaderData } from 'lib/types' import { type IndexLoaderData } from 'lib/types'
import { BROWSER_PATH, paths } from 'lib/paths' import { BROWSER_PATH, PATHS } from 'lib/paths'
import { useRouteLoaderData } from 'react-router-dom' import { useRouteLoaderData } from 'react-router-dom'
export function useAbsoluteFilePath() { export function useAbsoluteFilePath() {
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData const routeData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
return ( return (
paths.FILE + '/' + encodeURIComponent(routeData?.file?.path || BROWSER_PATH) PATHS.FILE + '/' + encodeURIComponent(routeData?.file?.path || BROWSER_PATH)
) )
} }

View File

@ -9,6 +9,7 @@ import { useModelingContext } from './useModelingContext'
import { getEventForSelectWithPoint } from 'lib/selections' import { getEventForSelectWithPoint } from 'lib/selections'
import { import {
getCapCodeRef, getCapCodeRef,
getExtrudeEdgeCodeRef,
getExtrusionFromSuspectedExtrudeSurface, getExtrusionFromSuspectedExtrudeSurface,
getSolid2dCodeRef, getSolid2dCodeRef,
getWallCodeRef, getWallCodeRef,
@ -60,6 +61,13 @@ export function useEngineConnectionSubscriptions() {
? [codeRef.range] ? [codeRef.range]
: [codeRef.range, extrusion.codeRef.range] : [codeRef.range, extrusion.codeRef.range]
) )
} else if (artifact?.type === 'extrudeEdge') {
const codeRef = getExtrudeEdgeCodeRef(
artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return
editorManager.setHighlightRange([codeRef.range])
} else if (artifact?.type === 'segment') { } else if (artifact?.type === 'segment') {
editorManager.setHighlightRange([ editorManager.setHighlightRange([
artifact?.codeRef?.range || [0, 0], artifact?.codeRef?.range || [0, 0],

View File

@ -1,6 +1,6 @@
import { useRouteLoaderData } from 'react-router-dom' import { useRouteLoaderData } from 'react-router-dom'
import { useSettingsAuthContext } from './useSettingsAuthContext' import { useSettingsAuthContext } from './useSettingsAuthContext'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { settings } from 'lib/settings/initialSettings' import { settings } from 'lib/settings/initialSettings'
import { useEffect } from 'react' import { useEffect } from 'react'
@ -10,7 +10,7 @@ import { useEffect } from 'react'
* in conjunction with additional uses of settingsLoader further down the router tree. * in conjunction with additional uses of settingsLoader further down the router tree.
* @param routeId - The id defined in Router.tsx to load the settings from. * @param routeId - The id defined in Router.tsx to load the settings from.
*/ */
export function useRefreshSettings(routeId: string = paths.INDEX) { export function useRefreshSettings(routeId: string = PATHS.INDEX) {
const ctx = useSettingsAuthContext() const ctx = useSettingsAuthContext()
const routeData = useRouteLoaderData(routeId) as typeof settings const routeData = useRouteLoaderData(routeId) as typeof settings

View File

@ -35,6 +35,7 @@ export function addFillet(
node: Program, node: Program,
pathToSegmentNode: PathToNode, pathToSegmentNode: PathToNode,
pathToExtrudeNode: PathToNode, pathToExtrudeNode: PathToNode,
edgeType: 'oppositeEdge' | 'adjacentEdge' | 'default',
radius = createLiteral(5) as Value radius = createLiteral(5) as Value
// shouldPipe = false, // TODO: Implement this feature // shouldPipe = false, // TODO: Implement this feature
): { modifiedAst: Program; pathToFilletNode: PathToNode } | Error { ): { modifiedAst: Program; pathToFilletNode: PathToNode } | Error {
@ -84,10 +85,18 @@ export function addFillet(
*/ */
// Create the fillet call expression in one line // Create the fillet call expression in one line
let tagCall: Value = createIdentifier(tag)
if (edgeType === 'oppositeEdge') {
tagCall = createCallExpressionStdLib('getOppositeEdge', [tagCall])
} else if (edgeType === 'adjacentEdge') {
tagCall = createCallExpressionStdLib('getNextAdjacentEdge', [tagCall])
}
const filletCall = createCallExpressionStdLib('fillet', [ const filletCall = createCallExpressionStdLib('fillet', [
createObjectExpression({ createObjectExpression({
radius: radius, radius: radius,
tags: createArrayExpression([createIdentifier(tag)]), tags: createArrayExpression([tagCall]),
}), }),
createPipeSubstitution(), createPipeSubstitution(),
]) ])
@ -178,11 +187,13 @@ export function addFillet(
extrudeDeclarator.init = createPipeExpression([extrudeInit, filletCall]) extrudeDeclarator.init = createPipeExpression([extrudeInit, filletCall])
return { return {
modifiedAst: _node, modifiedAst: _node,
pathToFilletNode: getPathToNodeOfFilletLiteral( pathToFilletNode: [],
pathToExtrudeNode, // TODO fix and re-enable this
extrudeDeclarator, // pathToFilletNode: getPathToNodeOfFilletLiteral(
tag // pathToExtrudeNode,
), // extrudeDeclarator,
// tag
// ),
} }
} else if (extrudeInit.type === 'PipeExpression') { } else if (extrudeInit.type === 'PipeExpression') {
// 2. fillet case // 2. fillet case

View File

@ -58,7 +58,10 @@ Map {
92, 92,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
@ -77,7 +80,10 @@ Map {
], ],
}, },
"edgeCutId": "UUID", "edgeCutId": "UUID",
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
@ -95,7 +101,10 @@ Map {
156, 156,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
@ -113,7 +122,10 @@ Map {
209, 209,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
@ -152,7 +164,16 @@ Map {
266, 266,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceIds": [ "surfaceIds": [
"UUID", "UUID",
@ -209,6 +230,54 @@ Map {
"type": "cap", "type": "cap",
}, },
"UUID-15" => { "UUID-15" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-16" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-17" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-18" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-19" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-20" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-21" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-22" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-23" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -226,7 +295,7 @@ Map {
"subType": "fillet", "subType": "fillet",
"type": "edgeCut", "type": "edgeCut",
}, },
"UUID-16" => { "UUID-24" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -250,7 +319,7 @@ Map {
"solid2dId": "UUID", "solid2dId": "UUID",
"type": "path", "type": "path",
}, },
"UUID-17" => { "UUID-25" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -263,12 +332,15 @@ Map {
416, 416,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
}, },
"UUID-18" => { "UUID-26" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -281,12 +353,15 @@ Map {
438, 438,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
}, },
"UUID-19" => { "UUID-27" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -299,12 +374,15 @@ Map {
491, 491,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceId": "UUID", "surfaceId": "UUID",
"type": "segment", "type": "segment",
}, },
"UUID-20" => { "UUID-28" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -321,11 +399,11 @@ Map {
"pathId": "UUID", "pathId": "UUID",
"type": "segment", "type": "segment",
}, },
"UUID-21" => { "UUID-29" => {
"pathId": "UUID", "pathId": "UUID",
"type": "solid2D", "type": "solid2D",
}, },
"UUID-22" => { "UUID-30" => {
"codeRef": { "codeRef": {
"pathToNode": [ "pathToNode": [
[ [
@ -338,7 +416,14 @@ Map {
546, 546,
], ],
}, },
"edgeIds": [], "edgeIds": [
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
"UUID",
],
"pathId": "UUID", "pathId": "UUID",
"surfaceIds": [ "surfaceIds": [
"UUID", "UUID",
@ -349,40 +434,76 @@ Map {
], ],
"type": "extrusion", "type": "extrusion",
}, },
"UUID-23" => { "UUID-31" => {
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"extrusionId": "UUID", "extrusionId": "UUID",
"pathIds": [], "pathIds": [],
"segId": "UUID", "segId": "UUID",
"type": "wall", "type": "wall",
}, },
"UUID-24" => { "UUID-32" => {
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"extrusionId": "UUID", "extrusionId": "UUID",
"pathIds": [], "pathIds": [],
"segId": "UUID", "segId": "UUID",
"type": "wall", "type": "wall",
}, },
"UUID-25" => { "UUID-33" => {
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"extrusionId": "UUID", "extrusionId": "UUID",
"pathIds": [], "pathIds": [],
"segId": "UUID", "segId": "UUID",
"type": "wall", "type": "wall",
}, },
"UUID-26" => { "UUID-34" => {
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"extrusionId": "UUID", "extrusionId": "UUID",
"pathIds": [], "pathIds": [],
"subType": "start", "subType": "start",
"type": "cap", "type": "cap",
}, },
"UUID-27" => { "UUID-35" => {
"edgeCutEdgeIds": [], "edgeCutEdgeIds": [],
"extrusionId": "UUID", "extrusionId": "UUID",
"pathIds": [], "pathIds": [],
"subType": "end", "subType": "end",
"type": "cap", "type": "cap",
}, },
"UUID-36" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-37" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-38" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-39" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
"UUID-40" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"type": "extrudeEdge",
},
"UUID-41" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"type": "extrudeEdge",
},
} }
`; `;

View File

@ -251,7 +251,7 @@ describe('testing createArtifactGraph', () => {
// of the edges refers to a non-existent node, the graph will throw. // of the edges refers to a non-existent node, the graph will throw.
// further more we can check that each edge is bi-directional, if it's not // further more we can check that each edge is bi-directional, if it's not
// by checking the arrow heads going both ways, on the graph. // by checking the arrow heads going both ways, on the graph.
await GraphTheGraph(theMap, 1400, 1400, 'exampleCode1.png') await GraphTheGraph(theMap, 2000, 2000, 'exampleCode1.png')
}, 20000) }, 20000)
}) })
}) })
@ -275,7 +275,7 @@ describe('capture graph of sketchOnFaceOnFace...', () => {
// of the edges refers to a non-existent node, the graph will throw. // of the edges refers to a non-existent node, the graph will throw.
// further more we can check that each edge is bi-directional, if it's not // further more we can check that each edge is bi-directional, if it's not
// by checking the arrow heads going both ways, on the graph. // by checking the arrow heads going both ways, on the graph.
await GraphTheGraph(theMap, 2500, 2500, 'sketchOnFaceOnFaceEtc.png') await GraphTheGraph(theMap, 3000, 3000, 'sketchOnFaceOnFaceEtc.png')
}, 20000) }, 20000)
}) })
}) })
@ -607,7 +607,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [98, 125], range: [98, 125],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -627,7 +627,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [162, 209], range: [162, 209],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -637,7 +637,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -654,7 +654,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [131, 156], range: [131, 156],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -664,7 +664,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -681,7 +681,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [98, 125], range: [98, 125],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -692,7 +692,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -709,7 +709,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'segment', type: 'segment',
pathId: expect.any(String), pathId: expect.any(String),
surfaceId: expect.any(String), surfaceId: expect.any(String),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [76, 92], range: [76, 92],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -719,7 +719,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -736,7 +736,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],
@ -753,7 +753,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'extrusion', type: 'extrusion',
pathId: expect.any(String), pathId: expect.any(String),
surfaceIds: expect.any(Array), surfaceIds: expect.any(Array),
edgeIds: [], edgeIds: expect.any(Array),
codeRef: { codeRef: {
range: [243, 266], range: [243, 266],
pathToNode: [['body', '']], pathToNode: [['body', '']],

View File

@ -86,12 +86,11 @@ interface CapArtifact {
extrusionId: string extrusionId: string
pathIds: Array<string> pathIds: Array<string>
} }
export interface ExtrudeEdge {
interface ExtrudeEdge {
type: 'extrudeEdge' type: 'extrudeEdge'
segId: string segId: string
extrusionId: string extrusionId: string
edgeId: string subType: 'opposite' | 'adjacent'
} }
/** A edgeCut is a more generic term for both fillet or chamfer */ /** A edgeCut is a more generic term for both fillet or chamfer */
@ -422,6 +421,54 @@ export function getArtifactsToUpdate({
} }
}) })
return returnArr return returnArr
} else if (
(cmd.type === 'solid3d_get_opposite_edge' &&
response.type === 'modeling' &&
response.data.modeling_response.type === 'solid3d_get_opposite_edge' &&
response.data.modeling_response.data.edge) ||
(cmd.type === 'solid3d_get_prev_adjacent_edge' &&
response.type === 'modeling' &&
response.data.modeling_response.type ===
'solid3d_get_prev_adjacent_edge' &&
response.data.modeling_response.data.edge)
) {
const wall = getArtifact(cmd.face_id)
if (wall?.type !== 'wall') return returnArr
const extrusion = getArtifact(wall.extrusionId)
if (extrusion?.type !== 'extrusion') return returnArr
const path = getArtifact(extrusion.pathId)
if (path?.type !== 'path') return returnArr
const segment = getArtifact(cmd.edge_id)
if (segment?.type !== 'segment') return returnArr
return [
{
id: response.data.modeling_response.data.edge,
artifact: {
type: 'extrudeEdge',
subType:
cmd.type === 'solid3d_get_prev_adjacent_edge'
? 'adjacent'
: 'opposite',
segId: cmd.edge_id,
extrusionId: path.extrusionId,
},
},
{
id: cmd.edge_id,
artifact: {
...segment,
edgeIds: [response.data.modeling_response.data.edge],
},
},
{
id: path.extrusionId,
artifact: {
...extrusion,
edgeIds: [response.data.modeling_response.data.edge],
},
},
]
} else if (cmd.type === 'solid3d_fillet_edge') { } else if (cmd.type === 'solid3d_fillet_edge') {
returnArr.push({ returnArr.push({
id, id,
@ -655,6 +702,18 @@ export function getWallCodeRef(
return seg.codeRef return seg.codeRef
} }
export function getExtrudeEdgeCodeRef(
edge: ExtrudeEdge,
artifactGraph: ArtifactGraph
): CommonCommandProperties | Error {
const seg = getArtifactOfTypes(
{ key: edge.segId, types: ['segment'] },
artifactGraph
)
if (err(seg)) return seg
return seg.codeRef
}
export function getExtrusionFromSuspectedExtrudeSurface( export function getExtrusionFromSuspectedExtrudeSurface(
id: string, id: string,
artifactGraph: ArtifactGraph artifactGraph: ArtifactGraph

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 KiB

After

Width:  |  Height:  |  Size: 617 KiB

View File

@ -110,7 +110,7 @@ export interface components {
/** @description The reason of the info command. */ /** @description The reason of the info command. */
reason?: components['schemas']['Reason'] | null reason?: components['schemas']['Reason'] | null
/** @description The result of the info command. */ /** @description The result of the info command. */
result: components['schemas']['Result'] result?: components['schemas']['Result'] | null
/** @description The sequence id. */ /** @description The sequence id. */
sequence_id: components['schemas']['SequenceId'] sequence_id: components['schemas']['SequenceId']
} & { } & {

View File

@ -22,11 +22,16 @@ type OnboardingPaths = {
[K in keyof typeof onboardingPaths]: `/onboarding${(typeof onboardingPaths)[K]}` [K in keyof typeof onboardingPaths]: `/onboarding${(typeof onboardingPaths)[K]}`
} }
export const paths = { const SETTINGS = '/settings' as const
export const PATHS = {
INDEX: '/', INDEX: '/',
HOME: '/home', HOME: '/home',
FILE: '/file', FILE: '/file',
SETTINGS: '/settings', SETTINGS,
SETTINGS_USER: `${SETTINGS}?tab=user` as const,
SETTINGS_PROJECT: `${SETTINGS}?tab=project` as const,
SETTINGS_KEYBINDINGS: `${SETTINGS}?tab=keybindings` as const,
SIGN_IN: '/signin', SIGN_IN: '/signin',
ONBOARDING: prependRoutes(onboardingPaths)('/onboarding') as OnboardingPaths, ONBOARDING: prependRoutes(onboardingPaths)('/onboarding') as OnboardingPaths,
} as const } as const

View File

@ -1,7 +1,7 @@
import { ActionFunction, LoaderFunction, redirect } from 'react-router-dom' import { ActionFunction, LoaderFunction, redirect } from 'react-router-dom'
import { FileLoaderData, HomeLoaderData, IndexLoaderData } from './types' import { FileLoaderData, HomeLoaderData, IndexLoaderData } from './types'
import { isTauri } from './isTauri' import { isTauri } from './isTauri'
import { getProjectMetaByRouteId, paths } from './paths' import { getProjectMetaByRouteId, PATHS } from './paths'
import { BROWSER_PATH } from 'lib/paths' import { BROWSER_PATH } from 'lib/paths'
import { import {
BROWSER_FILE_NAME, BROWSER_FILE_NAME,
@ -54,7 +54,7 @@ export const onboardingRedirectLoader: ActionFunction = async (args) => {
const { settings } = await loadAndValidateSettings() const { settings } = await loadAndValidateSettings()
const onboardingStatus = settings.app.onboardingStatus.current || '' const onboardingStatus = settings.app.onboardingStatus.current || ''
const notEnRouteToOnboarding = !args.request.url.includes( const notEnRouteToOnboarding = !args.request.url.includes(
paths.ONBOARDING.INDEX PATHS.ONBOARDING.INDEX
) )
// '' is the initial state, 'done' and 'dismissed' are the final states // '' is the initial state, 'done' and 'dismissed' are the final states
const hasValidOnboardingStatus = const hasValidOnboardingStatus =
@ -65,7 +65,7 @@ export const onboardingRedirectLoader: ActionFunction = async (args) => {
if (shouldRedirectToOnboarding) { if (shouldRedirectToOnboarding) {
return redirect( return redirect(
makeUrlPathRelative(paths.ONBOARDING.INDEX) + onboardingStatus.slice(1) makeUrlPathRelative(PATHS.ONBOARDING.INDEX) + onboardingStatus.slice(1)
) )
} }
@ -89,7 +89,7 @@ export const fileLoader: LoaderFunction = async ({
if (!current_file_name || !current_file_path || !project_name) { if (!current_file_name || !current_file_path || !project_name) {
return redirect( return redirect(
`${paths.FILE}/${encodeURIComponent( `${PATHS.FILE}/${encodeURIComponent(
`${params.id}${isTauri() ? sep() : '/'}${PROJECT_ENTRYPOINT}` `${params.id}${isTauri() ? sep() : '/'}${PROJECT_ENTRYPOINT}`
)}` )}`
) )
@ -158,7 +158,7 @@ export const homeLoader: LoaderFunction = async (): Promise<
HomeLoaderData | Response HomeLoaderData | Response
> => { > => {
if (!isTauri()) { if (!isTauri()) {
return redirect(paths.FILE + '/%2F' + BROWSER_PROJECT_NAME) return redirect(PATHS.FILE + '/%2F' + BROWSER_PROJECT_NAME)
} }
const { configuration } = await loadAndValidateSettings() const { configuration } = await loadAndValidateSettings()

View File

@ -30,9 +30,11 @@ import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
import { PathToNodeMap } from 'lang/std/sketchcombos' import { PathToNodeMap } from 'lang/std/sketchcombos'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { import {
ExtrudeEdge,
getArtifactOfTypes, getArtifactOfTypes,
getArtifactsOfTypes, getArtifactsOfTypes,
getCapCodeRef, getCapCodeRef,
getExtrudeEdgeCodeRef,
getSolid2dCodeRef, getSolid2dCodeRef,
getWallCodeRef, getWallCodeRef,
} from 'lang/std/artifactGraph' } from 'lang/std/artifactGraph'
@ -56,6 +58,8 @@ export type Selection = {
| 'line' | 'line'
| 'arc' | 'arc'
| 'all' | 'all'
| 'opposite-edge'
| 'adjacent-edge'
range: SourceRange range: SourceRange
} }
export type Selections = { export type Selections = {
@ -85,6 +89,7 @@ export async function getEventForSelectWithPoint({
} }
} }
let _artifact = engineCommandManager.artifactGraph.get(data.entity_id) let _artifact = engineCommandManager.artifactGraph.get(data.entity_id)
console.log('entity id', data.entity_id)
if (!_artifact) if (!_artifact)
return { return {
type: 'Set selection', type: 'Set selection',
@ -141,6 +146,27 @@ export async function getEventForSelectWithPoint({
}, },
} }
} }
if (_artifact.type === 'extrudeEdge') {
const codeRef = getExtrudeEdgeCodeRef(
_artifact,
engineCommandManager.artifactGraph
)
console.log('codeRef', codeRef)
if (err(codeRef)) return null
return {
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: {
range: codeRef.range,
type:
_artifact.subType === 'adjacent'
? 'adjacent-edge'
: 'opposite-edge',
},
},
}
}
return null return null
} }
@ -531,6 +557,25 @@ function codeToIdSelections(
bestCandidate = entry bestCandidate = entry
return return
} }
if (
(type === 'opposite-edge' || type === 'adjacent-edge') &&
entry.artifact.type === 'segment'
) {
const tweakedType: ExtrudeEdge['subType'] =
type === 'opposite-edge' ? 'opposite' : 'adjacent'
const edgeArtifact = [
...getArtifactsOfTypes(
{ keys: entry.artifact.edgeIds, types: ['extrudeEdge'] },
engineCommandManager.artifactGraph
),
].find(([_, edge]) => edge.subType === tweakedType)
if (!edgeArtifact) return
bestCandidate = {
artifact: edgeArtifact[1],
selection: { type, range, ...rest },
id: edgeArtifact[0],
}
}
if (type === 'solid2D' && entry.artifact.type === 'path') { if (type === 'solid2D' && entry.artifact.type === 'path') {
const solid = engineCommandManager.artifactGraph.get( const solid = engineCommandManager.artifactGraph.get(
entry.artifact.solid2dId || '' entry.artifact.solid2dId || ''

View File

@ -51,4 +51,39 @@ if (typeof window !== 'undefined') {
padding: 0.2, // padding around the objects padding: 0.2, // padding around the objects
}, },
}) })
;(window as any).getEdgesForAndAskEngineForType = async (faceId: string) => {
// Kurt - Debugging tool used help to show edges aren't stable after further 3d operations
// if this was added more than a few months ago, it probably can be removed.
const face = engineCommandManager.artifactGraph.get(faceId)
if (face?.type !== 'wall') {
console.log('was expecting a wall, you gave me a ', face?.type)
return
}
const extrusion = engineCommandManager.artifactGraph.get(face.extrusionId)
if (extrusion?.type !== 'extrusion') {
console.log('was expecting an extrusion, but got ', extrusion?.type)
return
}
extrusion.edgeIds.forEach(async (edgeId) => {
const result = await engineCommandManager
.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'get_entity_type',
entity_id: edgeId,
},
})
.catch((a) => console.log('error:', a))
if (!result?.success) return
if (result.resp.type !== 'modeling') return
if (result.resp.data.modeling_response.type !== 'get_entity_type') return
console.log(
'result edge is: ',
result.resp.data.modeling_response.data.entity_type,
' id: ',
edgeId
)
})
}
} }

View File

@ -8,7 +8,7 @@ import {
PROJECT_ENTRYPOINT, PROJECT_ENTRYPOINT,
} from 'lib/constants' } from 'lib/constants'
import { bracket } from './exampleKcl' import { bracket } from './exampleKcl'
import { paths } from './paths' import { PATHS } from './paths'
import { import {
createNewProjectDirectory, createNewProjectDirectory,
listProjects, listProjects,
@ -156,8 +156,8 @@ export async function createAndOpenNewProject({
null null
) )
navigate( navigate(
`${paths.FILE}/${encodeURIComponent(newProject.default_file)}${ `${PATHS.FILE}/${encodeURIComponent(newProject.default_file)}${
paths.ONBOARDING.INDEX PATHS.ONBOARDING.INDEX
}` }`
) )
return newProject return newProject

View File

@ -1197,6 +1197,11 @@ export const modelingMachine = createMachine(
ast, ast,
pathToSegmentNode, pathToSegmentNode,
pathToExtrudeNode, pathToExtrudeNode,
selection.codeBasedSelections[0].type === 'opposite-edge'
? 'oppositeEdge'
: selection.codeBasedSelections[0].type === 'adjacent-edge'
? 'adjacentEdge'
: 'default',
'variableName' in radius 'variableName' in radius
? radius.variableIdentifierAst ? radius.variableIdentifierAst
: radius.valueAst : radius.valueAst
@ -1206,7 +1211,8 @@ export const modelingMachine = createMachine(
const { modifiedAst, pathToFilletNode } = addFilletResult const { modifiedAst, pathToFilletNode } = addFilletResult
const updatedAst = await kclManager.updateAst(modifiedAst, true, { const updatedAst = await kclManager.updateAst(modifiedAst, true, {
focusPath: pathToFilletNode, // TODO fix and re-enable
// focusPath: pathToFilletNode,
}) })
if (updatedAst?.selections) { if (updatedAst?.selections) {
editorManager.selectRange(updatedAst?.selections) editorManager.selectRange(updatedAst?.selections)

View File

@ -16,7 +16,7 @@ import Loading from 'components/Loading'
import { useMachine } from '@xstate/react' import { useMachine } from '@xstate/react'
import { homeMachine } from '../machines/homeMachine' import { homeMachine } from '../machines/homeMachine'
import { ContextFrom, EventFrom } from 'xstate' import { ContextFrom, EventFrom } from 'xstate'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { import {
getNextSearchParams, getNextSearchParams,
getSortFunction, getSortFunction,
@ -44,7 +44,7 @@ import { ProjectSearchBar, useProjectSearch } from 'components/ProjectSearchBar'
// This route only opens in the Tauri desktop context for now, // This route only opens in the Tauri desktop context for now,
// as defined in Router.tsx, so we can use the Tauri APIs and types. // as defined in Router.tsx, so we can use the Tauri APIs and types.
const Home = () => { const Home = () => {
useRefreshSettings(paths.HOME + 'SETTINGS') useRefreshSettings(PATHS.HOME + 'SETTINGS')
const { commandBarSend } = useCommandsContext() const { commandBarSend } = useCommandsContext()
const navigate = useNavigate() const navigate = useNavigate()
const { projects: loadedProjects } = useLoaderData() as HomeLoaderData const { projects: loadedProjects } = useLoaderData() as HomeLoaderData
@ -63,7 +63,7 @@ const Home = () => {
}) })
useHotkeys( useHotkeys(
isTauri() ? 'mod+,' : 'shift+mod+,', isTauri() ? 'mod+,' : 'shift+mod+,',
() => navigate(paths.HOME + paths.SETTINGS), () => navigate(PATHS.HOME + PATHS.SETTINGS),
{ {
splitKey: '|', splitKey: '|',
} }
@ -91,7 +91,7 @@ const Home = () => {
null null
) )
commandBarSend({ type: 'Close' }) commandBarSend({ type: 'Close' })
navigate(`${paths.FILE}/${encodeURIComponent(projectPath)}`) navigate(`${PATHS.FILE}/${encodeURIComponent(projectPath)}`)
} }
}, },
toastSuccess: (_, event) => toast.success((event.data || '') + ''), toastSuccess: (_, event) => toast.success((event.data || '') + ''),
@ -276,7 +276,7 @@ const Home = () => {
<p className="my-4 text-sm text-chalkboard-80 dark:text-chalkboard-30"> <p className="my-4 text-sm text-chalkboard-80 dark:text-chalkboard-30">
Loaded from{' '} Loaded from{' '}
<Link <Link
to="settings?tab=user#projectDirectory" to={`${PATHS.SETTINGS_USER}#projectDirectory`}
className="text-chalkboard-90 dark:text-chalkboard-20 underline underline-offset-2" className="text-chalkboard-90 dark:text-chalkboard-20 underline underline-offset-2"
> >
{settings.app.projectDirectory.current} {settings.app.projectDirectory.current}

View File

@ -11,7 +11,7 @@ import { APP_NAME } from 'lib/constants'
import { useState } from 'react' import { useState } from 'react'
import { useLspContext } from 'components/LspProvider' import { useLspContext } from 'components/LspProvider'
import { IndexLoaderData } from 'lib/types' import { IndexLoaderData } from 'lib/types'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { useFileContext } from 'hooks/useFileContext' import { useFileContext } from 'hooks/useFileContext'
/** /**
@ -51,7 +51,7 @@ function OnboardingResetWarning(props: OnboardingResetWarningProps) {
function OnboardingWarningDesktop(props: OnboardingResetWarningProps) { function OnboardingWarningDesktop(props: OnboardingResetWarningProps) {
const navigate = useNavigate() const navigate = useNavigate()
const dismiss = useDismiss() const dismiss = useDismiss()
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const { context: fileContext } = useFileContext() const { context: fileContext } = useFileContext()
const { onProjectClose, onProjectOpen } = useLspContext() const { onProjectClose, onProjectOpen } = useLspContext()

View File

@ -15,7 +15,7 @@ import UserMenu from './UserMenu'
import ProjectMenu from './ProjectMenu' import ProjectMenu from './ProjectMenu'
import Export from './Export' import Export from './Export'
import FutureWork from './FutureWork' import FutureWork from './FutureWork'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
import { onboardingPaths } from 'routes/Onboarding/paths' import { onboardingPaths } from 'routes/Onboarding/paths'
@ -103,7 +103,7 @@ export function useNextClick(newStatus: string) {
type: 'set.app.onboardingStatus', type: 'set.app.onboardingStatus',
data: { level: 'user', value: newStatus }, data: { level: 'user', value: newStatus },
}) })
navigate(filePath + paths.ONBOARDING.INDEX.slice(0, -1) + newStatus) navigate(filePath + PATHS.ONBOARDING.INDEX.slice(0, -1) + newStatus)
}, [filePath, newStatus, send, navigate]) }, [filePath, newStatus, send, navigate])
} }

View File

@ -1,7 +1,7 @@
import { SettingsLevel } from 'lib/settings/settingsTypes' import { SettingsLevel } from 'lib/settings/settingsTypes'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom' import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { useDotDotSlash } from 'hooks/useDotDotSlash' import { useDotDotSlash } from 'hooks/useDotDotSlash'
import { Fragment, useEffect, useRef } from 'react' import { Fragment, useEffect, useRef } from 'react'
import { Dialog, Transition } from '@headlessui/react' import { Dialog, Transition } from '@headlessui/react'
@ -21,9 +21,9 @@ export const APP_VERSION = isTauri()
export const Settings = () => { export const Settings = () => {
const navigate = useNavigate() const navigate = useNavigate()
const [searchParams, setSearchParams] = useSearchParams() const [searchParams, setSearchParams] = useSearchParams()
const close = () => navigate(location.pathname.replace(paths.SETTINGS, '')) const close = () => navigate(location.pathname.replace(PATHS.SETTINGS, ''))
const location = useLocation() const location = useLocation()
const isFileSettings = location.pathname.includes(paths.FILE) const isFileSettings = location.pathname.includes(PATHS.FILE)
const searchParamTab = const searchParamTab =
(searchParams.get('tab') as SettingsLevel | 'keybindings') ?? (searchParams.get('tab') as SettingsLevel | 'keybindings') ??
(isFileSettings ? 'project' : 'user') (isFileSettings ? 'project' : 'user')

View File

@ -2,7 +2,7 @@ import { ActionButton } from '../components/ActionButton'
import { isTauri } from '../lib/isTauri' import { isTauri } from '../lib/isTauri'
import { VITE_KC_SITE_BASE_URL, VITE_KC_API_BASE_URL } from '../env' import { VITE_KC_SITE_BASE_URL, VITE_KC_API_BASE_URL } from '../env'
import { Themes, getSystemTheme } from '../lib/theme' import { Themes, getSystemTheme } from '../lib/theme'
import { paths } from 'lib/paths' import { PATHS } from 'lib/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext' import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { APP_NAME } from 'lib/constants' import { APP_NAME } from 'lib/constants'
import { login } from 'lib/tauri' import { login } from 'lib/tauri'
@ -75,7 +75,7 @@ const SignIn = () => {
<ActionButton <ActionButton
Element="link" Element="link"
to={`${VITE_KC_SITE_BASE_URL}${ to={`${VITE_KC_SITE_BASE_URL}${
paths.SIGN_IN PATHS.SIGN_IN
}?callbackUrl=${encodeURIComponent( }?callbackUrl=${encodeURIComponent(
typeof window !== 'undefined' && typeof window !== 'undefined' &&
window.location.href.replace('signin', '') window.location.href.replace('signin', '')

41
src/test-utils.test.ts Normal file
View File

@ -0,0 +1,41 @@
import { normaliseKclNumbers } from '../e2e/playwright/test-utils'
test('normaliseKclNumbers', () => {
expect(
normaliseKclNumbers(`const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-15, sketch001)`)
).toBe(`const sketch001 = startSketchOn('XY')
|> startProfileAt([-12.34, 12.34], %)
|> line([12.34, 0], %)
|> line([0, -12.34], %)
|> line([-12.34, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-12.34, sketch001)`)
expect(
normaliseKclNumbers(
`const sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-15, sketch001)`,
false
)
).toBe(`const sketch001 = startSketchOn('XY')
|> startProfileAt([-12.34, 12.34], %)
|> line([12.34, 12.34], %)
|> line([12.34, -12.34], %)
|> line([-12.34, 12.34], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
const extrude001 = extrude(-12.34, sketch001)`)
})

View File

@ -181,6 +181,48 @@ pub(crate) async fn do_post_extrude(
vec![] vec![]
}; };
for face_info in face_infos.iter() {
if face_info.cap == kittycad::types::ExtrusionFaceCapType::None
&& face_info.face_id.is_some()
&& face_info.curve_id.is_some()
{
match args
.batch_modeling_cmd(
uuid::Uuid::new_v4(),
kittycad::types::ModelingCmd::Solid3DGetOppositeEdge {
edge_id: face_info.curve_id.unwrap(),
object_id: sketch_group.id,
face_id: face_info.face_id.unwrap_or_default(),
},
)
.await
{
Ok(info) => info,
Err(e) => {
eprintln!("Error fetching opposite edge: {:?}", e);
continue;
}
};
match args
.batch_modeling_cmd(
uuid::Uuid::new_v4(),
kittycad::types::ModelingCmd::Solid3DGetPrevAdjacentEdge {
edge_id: face_info.curve_id.unwrap(),
object_id: sketch_group.id,
face_id: face_info.face_id.unwrap(),
},
)
.await
{
Ok(info) => info,
Err(e) => {
eprintln!("Error fetching adjacent edge: {:?}", e);
continue;
}
};
}
}
// Create a hashmap for quick id lookup // Create a hashmap for quick id lookup
let mut face_id_map = std::collections::HashMap::new(); let mut face_id_map = std::collections::HashMap::new();
// creating fake ids for start and end caps is to make extrudes mock-execute safe // creating fake ids for start and end caps is to make extrudes mock-execute safe