Compare commits
14 Commits
achalmers/
...
v0.24.2
Author | SHA1 | Date | |
---|---|---|---|
60e187bd3e | |||
c64175425b | |||
36464e6984 | |||
2f0002e53c | |||
482833c88f | |||
d9d0a72306 | |||
65cd9fab64 | |||
5e41e382ce | |||
1e3cb00092 | |||
d1a2bd01ca | |||
aca13d087b | |||
fcdde3e482 | |||
a1df3d0ffc | |||
1852e6167b |
13
README.md
@ -135,9 +135,18 @@ After it runs you should just need to push the push the branch and open a PR (it
|
||||
|
||||
The PR may serve as a place to discuss the human-readable changelog and extra QA.
|
||||
|
||||
2. Merge the PR
|
||||
2. Smoke test the artifact from the above PR
|
||||
We don't have a strict process, but click around and check for anything obvious
|
||||
One of the artifacts is called updater-test, because we don't have a way to test this fully automated, we have a semi-automated process.
|
||||
|
||||
Download updater-test zip file, install the app, run it, expect an updater prompt to v0.99.99, install it and check that the app comes back at that version (on both macOS and Windows).
|
||||
|
||||
3. Merge the PR
|
||||
|
||||
|
||||
4. Profit (A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions if the PR was correctly named)
|
||||
|
||||
|
||||
3. Profit (A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions if the PR was correctly named)
|
||||
|
||||
## Fuzzing the parser
|
||||
|
||||
|
@ -3099,6 +3099,49 @@ const sketch002 = startSketchOn(extrude001, $seg01)
|
||||
).not.toBeDisabled()
|
||||
})
|
||||
|
||||
test('Fillet button states test', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const selectSegment = () => page.getByText(`line([10, 0], %)`).click()
|
||||
const selectClose = () => page.getByText(`close(%)`).click()
|
||||
const clickEmpty = () => page.mouse.click(950, 100)
|
||||
|
||||
// expect fillet button without any bodies in the scene
|
||||
await selectSegment()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
||||
await clickEmpty()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
||||
|
||||
// test fillet button with the body in the scene
|
||||
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||
const extrude001 = extrude(10, sketch001)`
|
||||
await u.codeLocator.fill(codeToAdd)
|
||||
await selectSegment()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||
await selectClose()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeDisabled()
|
||||
await clickEmpty()
|
||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||
})
|
||||
|
||||
const removeAfterFirstParenthesis = (inputString: string) => {
|
||||
const index = inputString.indexOf('(')
|
||||
if (index !== -1) {
|
||||
@ -3500,11 +3543,62 @@ test.describe('Command bar tests', () => {
|
||||
`const extrude001 = extrude(${KCL_DEFAULT_LENGTH}, sketch001)`
|
||||
)
|
||||
})
|
||||
test('Command bar works and can change a setting', async ({ page }) => {
|
||||
|
||||
test('Fillet from command bar', async ({ page }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-5, -5], %)
|
||||
|> line([0, 10], %)
|
||||
|> line([10, 0], %)
|
||||
|> line([0, -10], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(-10, sketch001)`
|
||||
)
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const selectSegment = () => page.getByText(`line([0, -10], %)`).click()
|
||||
|
||||
await selectSegment()
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByRole('button', { name: 'Fillet' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toContainText(
|
||||
`fillet({ radius: ${KCL_DEFAULT_LENGTH}, tags: [seg01] }, %)`
|
||||
)
|
||||
})
|
||||
|
||||
test('Command bar can change a setting, and switch back and forth between arguments', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
const commandBarButton = page.getByRole('button', { name: 'Commands' })
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'theme',
|
||||
exact: false,
|
||||
})
|
||||
const commandLevelArgButton = page.getByRole('button', { name: 'level' })
|
||||
const commandThemeArgButton = page.getByRole('button', { name: 'value' })
|
||||
// This selector changes after we set the setting
|
||||
let commandOptionInput = page.getByPlaceholder('Select an option')
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
@ -3515,23 +3609,17 @@ test.describe('Command bar tests', () => {
|
||||
.or(page.getByRole('button', { name: '⌘K' }))
|
||||
.click()
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await page.keyboard.press('Escape')
|
||||
cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).not.toBeVisible()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('Meta+K')
|
||||
cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
// Try typing in the command bar
|
||||
await page.keyboard.type('theme')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'Settings · app · theme',
|
||||
})
|
||||
await cmdSearchBar.fill('theme')
|
||||
await expect(themeOption).toBeVisible()
|
||||
await themeOption.click()
|
||||
const themeInput = page.getByPlaceholder('Select an option')
|
||||
@ -3553,6 +3641,24 @@ test.describe('Command bar tests', () => {
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
|
||||
commandOptionInput = page.getByPlaceholder('system')
|
||||
|
||||
// Test case for https://github.com/KittyCAD/modeling-app/issues/2882
|
||||
await commandBarButton.click()
|
||||
await cmdSearchBar.focus()
|
||||
await cmdSearchBar.fill('theme')
|
||||
await themeOption.click()
|
||||
await expect(commandThemeArgButton).toBeDisabled()
|
||||
await commandOptionInput.focus()
|
||||
await commandOptionInput.fill('lig')
|
||||
await commandLevelArgButton.click()
|
||||
await expect(commandLevelArgButton).toBeDisabled()
|
||||
|
||||
// Test case for https://github.com/KittyCAD/modeling-app/issues/2881
|
||||
await commandThemeArgButton.click()
|
||||
await expect(commandThemeArgButton).toBeDisabled()
|
||||
await expect(commandLevelArgButton).toHaveText('level: project')
|
||||
})
|
||||
|
||||
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||
@ -3577,7 +3683,7 @@ test.describe('Command bar tests', () => {
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
// Try typing in the command bar
|
||||
await page.keyboard.type('theme')
|
||||
await cmdSearchBar.fill('theme')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'Settings · app · theme',
|
||||
})
|
||||
@ -3648,7 +3754,9 @@ test.describe('Command bar tests', () => {
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
// Assert that we're on the distance step
|
||||
await expect(page.getByRole('button', { name: 'distance' })).toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'distance', exact: false })
|
||||
).toBeDisabled()
|
||||
|
||||
// Assert that the an alternative variable name is chosen,
|
||||
// since the default variable name is already in use (distance)
|
||||
@ -3663,11 +3771,12 @@ test.describe('Command bar tests', () => {
|
||||
|
||||
// Review step and argument hotkeys
|
||||
await expect(submitButton).toBeEnabled()
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(submitButton).toBeFocused()
|
||||
await submitButton.press('Backspace')
|
||||
|
||||
// Assert we're back on the distance step
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Distance 5', exact: false })
|
||||
page.getByRole('button', { name: 'distance', exact: false })
|
||||
).toBeDisabled()
|
||||
|
||||
await continueButton.click()
|
||||
@ -3724,6 +3833,7 @@ const extrude001 = extrude(distance001, sketch001)`.replace(
|
||||
// Click in the scene a couple times to draw a line
|
||||
// so tangential arc is valid
|
||||
await page.mouse.click(700, 200)
|
||||
await page.mouse.move(700, 300, { steps: 5 })
|
||||
await page.mouse.click(700, 300)
|
||||
|
||||
// switch to tangential arc via command bar
|
||||
@ -4682,10 +4792,10 @@ test.describe('Sketch tests', () => {
|
||||
// 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
|
||||
// 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' })
|
||||
page.getByRole('button', { name: 'selection : 1 face', exact: false })
|
||||
).toBeVisible()
|
||||
})
|
||||
test("Existing sketch with bad code delete user's code", async ({ page }) => {
|
||||
|
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.24.1",
|
||||
"version": "0.24.2",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.17.0",
|
||||
|
@ -30,7 +30,7 @@ import { URI } from 'vscode-uri'
|
||||
import { LanguageServerClient } from '../client'
|
||||
import { CompletionItemKindMap } from './autocomplete'
|
||||
import { addToken, SemanticToken } from './semantic-tokens'
|
||||
import { deferExecution, posToOffset, formatMarkdownContents } from './util'
|
||||
import { posToOffset, formatMarkdownContents } from './util'
|
||||
import lspAutocompleteExt from './autocomplete'
|
||||
import lspHoverExt from './hover'
|
||||
import lspFormatExt from './format'
|
||||
|
@ -80,5 +80,5 @@
|
||||
}
|
||||
},
|
||||
"productName": "Zoo Modeling App",
|
||||
"version": "0.24.1"
|
||||
"version": "0.24.2"
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
canRectangleTool,
|
||||
isEditingExistingSketch,
|
||||
} from 'machines/modelingMachine'
|
||||
import { DEV } from 'env'
|
||||
|
||||
export function Toolbar({
|
||||
className = '',
|
||||
@ -118,6 +119,16 @@ export function Toolbar({
|
||||
}),
|
||||
{ enabled: !disableAllButtons, scopes: ['modeling'] }
|
||||
)
|
||||
const disableFillet = !state.can('Fillet') || disableAllButtons
|
||||
useHotkeys(
|
||||
'f',
|
||||
() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Fillet', groupId: 'modeling' },
|
||||
}),
|
||||
{ enabled: !disableFillet, scopes: ['modeling'] }
|
||||
)
|
||||
|
||||
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
|
||||
const span = toolbarButtonsRef.current
|
||||
@ -404,6 +415,36 @@ export function Toolbar({
|
||||
</ActionButton>
|
||||
</li>
|
||||
)}
|
||||
{state.matches('idle') && (DEV || (window as any)._enableFillet) && (
|
||||
<li className="contents">
|
||||
<ActionButton
|
||||
className={buttonClassName}
|
||||
Element="button"
|
||||
onClick={() =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Fillet', groupId: 'modeling' },
|
||||
})
|
||||
}
|
||||
disabled={disableFillet}
|
||||
title={disableFillet ? 'fillet' : "edge can't be filleted"}
|
||||
iconStart={{
|
||||
icon: 'fillet', // todo: add fillet icon
|
||||
iconClassName,
|
||||
bgClassName,
|
||||
}}
|
||||
>
|
||||
Fillet
|
||||
<Tooltip
|
||||
delay={1250}
|
||||
position="bottom"
|
||||
className="!px-2 !text-xs"
|
||||
>
|
||||
Shortcut: F
|
||||
</Tooltip>
|
||||
</ActionButton>
|
||||
</li>
|
||||
)}
|
||||
</ul>
|
||||
</menu>
|
||||
)
|
||||
|
@ -41,6 +41,7 @@ function CommandArgOptionInput({
|
||||
)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const formRef = useRef<HTMLFormElement>(null)
|
||||
const [shouldSubmitOnChange, setShouldSubmitOnChange] = useState(false)
|
||||
const [selectedOption, setSelectedOption] = useState<
|
||||
CommandArgumentOption<unknown>
|
||||
>(currentOption || resolvedOptions[0])
|
||||
@ -82,9 +83,11 @@ function CommandArgOptionInput({
|
||||
// We deal with the whole option object internally
|
||||
setSelectedOption(option)
|
||||
|
||||
// But we only submit the value
|
||||
// But we only submit the value itself
|
||||
if (shouldSubmitOnChange) {
|
||||
onSubmit(option.value)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault()
|
||||
@ -94,7 +97,18 @@ function CommandArgOptionInput({
|
||||
}
|
||||
|
||||
return (
|
||||
<form id="arg-form" onSubmit={handleSubmit} ref={formRef}>
|
||||
<form
|
||||
id="arg-form"
|
||||
onSubmit={handleSubmit}
|
||||
ref={formRef}
|
||||
onKeyDownCapture={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
setShouldSubmitOnChange(true)
|
||||
} else {
|
||||
setShouldSubmitOnChange(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Combobox
|
||||
value={selectedOption}
|
||||
onChange={handleSelectOption}
|
||||
@ -118,6 +132,12 @@ function CommandArgOptionInput({
|
||||
if (event.key === 'Backspace' && !event.currentTarget.value) {
|
||||
stepBack()
|
||||
}
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
setShouldSubmitOnChange(true)
|
||||
} else {
|
||||
setShouldSubmitOnChange(false)
|
||||
}
|
||||
}}
|
||||
value={query}
|
||||
placeholder={
|
||||
@ -136,6 +156,9 @@ function CommandArgOptionInput({
|
||||
<Combobox.Options
|
||||
static
|
||||
className="overflow-y-auto max-h-96 cursor-pointer"
|
||||
onMouseDown={() => {
|
||||
setShouldSubmitOnChange(true)
|
||||
}}
|
||||
>
|
||||
{filteredOptions?.map((option) => (
|
||||
<Combobox.Option
|
||||
|
@ -114,6 +114,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||
>
|
||||
{argName}
|
||||
</span>
|
||||
<span className="sr-only">: </span>
|
||||
{argValue ? (
|
||||
arg.inputType === 'selection' ? (
|
||||
getSelectionTypeDisplayText(argValue as Selections)
|
||||
|
@ -3,6 +3,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { CommandArgument } from 'lib/commandTypes'
|
||||
import {
|
||||
Selection,
|
||||
canSubmitSelectionArg,
|
||||
getSelectionType,
|
||||
getSelectionTypeDisplayText,
|
||||
@ -11,6 +12,25 @@ import { modelingMachine } from 'machines/modelingMachine'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { StateFrom } from 'xstate'
|
||||
|
||||
const semanticEntityNames: { [key: string]: Array<Selection['type']> } = {
|
||||
face: ['extrude-wall', 'start-cap', 'end-cap'],
|
||||
edge: ['edge', 'line', 'arc'],
|
||||
point: ['point', 'line-end', 'line-mid'],
|
||||
}
|
||||
|
||||
function getSemanticSelectionType(selectionType: Array<Selection['type']>) {
|
||||
const semanticSelectionType = new Set()
|
||||
selectionType.forEach((type) => {
|
||||
Object.entries(semanticEntityNames).forEach(([entity, entityTypes]) => {
|
||||
if (entityTypes.includes(type)) {
|
||||
semanticSelectionType.add(entity)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
return Array.from(semanticSelectionType)
|
||||
}
|
||||
|
||||
const selectionSelector = (snapshot: StateFrom<typeof modelingMachine>) =>
|
||||
snapshot.context.selectionRanges
|
||||
|
||||
@ -85,7 +105,9 @@ function CommandBarSelectionInput({
|
||||
>
|
||||
{canSubmitSelection
|
||||
? getSelectionTypeDisplayText(selection) + ' selected'
|
||||
: `Please select ${arg.multiple ? 'one or more faces' : 'one face'}`}
|
||||
: `Please select ${
|
||||
arg.multiple ? 'one or more ' : 'one '
|
||||
}${getSemanticSelectionType(arg.selectionTypes).join(' or ')}`}
|
||||
<input
|
||||
id="selection"
|
||||
name="selection"
|
||||
|
@ -187,6 +187,22 @@ const CustomIconMap = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
fillet: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M8 5H5V15H15V12C15 8.13401 11.866 5 8 5ZM5 4H4V5V15V16H5H15H16V15V12C16 7.58172 12.4183 4 8 4H5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M4.5 3.5H5.5H8.5C12.9183 3.5 16.5 7.08172 16.5 11.5V14.5V15.5H16V12C16 7.58172 12.4182 4 7.99996 4H4.5V3.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
file: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
|
@ -72,6 +72,7 @@ import { uuidv4 } from 'lib/utils'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { modelingMachineEvent } from 'editor/manager'
|
||||
import { hasValidFilletSelection } from 'lang/modifyAst/addFillet'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -164,7 +165,7 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
store.videoElement?.pause()
|
||||
kclManager.executeCode(true).then(() => {
|
||||
if (engineCommandManager.engineConnection?.freezeFrame) return
|
||||
if (engineCommandManager.engineConnection?.idleMode) return
|
||||
|
||||
store.videoElement?.play()
|
||||
})
|
||||
@ -444,6 +445,12 @@ export const ModelingMachineProvider = ({
|
||||
if (selectionRanges.codeBasedSelections.length <= 0) return false
|
||||
return true
|
||||
},
|
||||
'has valid fillet selection': ({ selectionRanges }) =>
|
||||
hasValidFilletSelection({
|
||||
selectionRanges,
|
||||
ast: kclManager.ast,
|
||||
code: codeManager.code,
|
||||
}),
|
||||
'Selection is on face': ({ selectionRanges }, { data }) => {
|
||||
if (data?.forceNewSketch) return false
|
||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
||||
@ -494,7 +501,6 @@ export const ModelingMachineProvider = ({
|
||||
kclManager.ast,
|
||||
data.sketchPathToNode,
|
||||
data.extrudePathToNode,
|
||||
kclManager.programMemory,
|
||||
data.cap
|
||||
)
|
||||
if (trap(sketched)) return Promise.reject(sketched)
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { DEV } from 'env'
|
||||
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
|
||||
import { getNormalisedCoordinates } from '../lib/utils'
|
||||
import Loading from './Loading'
|
||||
@ -11,6 +10,10 @@ import { btnName } from 'lib/cameraControls'
|
||||
import { sendSelectEventToEngine } from 'lib/selections'
|
||||
import { kclManager, engineCommandManager, sceneInfra } from 'lib/singletons'
|
||||
import { useAppStream } from 'AppState'
|
||||
import {
|
||||
EngineConnectionStateType,
|
||||
DisconnectingType,
|
||||
} from 'lang/std/engineConnection'
|
||||
|
||||
export const Stream = () => {
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
@ -20,15 +23,28 @@ export const Stream = () => {
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { state, send, context } = useModelingContext()
|
||||
const { mediaStream } = useAppStream()
|
||||
const { overallState } = useNetworkContext()
|
||||
const { overallState, immediateState } = useNetworkContext()
|
||||
const [isFreezeFrame, setIsFreezeFrame] = useState(false)
|
||||
const [isPaused, setIsPaused] = useState(false)
|
||||
|
||||
const IDLE = true
|
||||
const IDLE = settings.context.app.streamIdleMode.current
|
||||
|
||||
const isNetworkOkay =
|
||||
overallState === NetworkHealthState.Ok ||
|
||||
overallState === NetworkHealthState.Weak
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
immediateState.type === EngineConnectionStateType.Disconnecting &&
|
||||
immediateState.value.type === DisconnectingType.Pause
|
||||
) {
|
||||
setIsPaused(true)
|
||||
}
|
||||
if (immediateState.type === EngineConnectionStateType.Connecting) {
|
||||
setIsPaused(false)
|
||||
}
|
||||
}, [immediateState])
|
||||
|
||||
// Linux has a default behavior to paste text on middle mouse up
|
||||
// This adds a listener to block that pasting if the click target
|
||||
// is not a text input, so users can move in the 3D scene with
|
||||
@ -65,14 +81,11 @@ export const Stream = () => {
|
||||
sceneInfra.modelingSend({ type: 'Cancel' })
|
||||
// Give video time to pause
|
||||
window.requestAnimationFrame(() => {
|
||||
engineCommandManager.tearDown()
|
||||
engineCommandManager.tearDown({ idleMode: true })
|
||||
})
|
||||
}
|
||||
|
||||
// Teardown everything if we go hidden or reconnect
|
||||
if (IDLE && DEV) {
|
||||
if (globalThis?.window?.document) {
|
||||
globalThis.window.document.onvisibilitychange = () => {
|
||||
const onVisibilityChange = () => {
|
||||
if (globalThis.window.document.visibilityState === 'hidden') {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
timeoutIdIdleA = setTimeout(teardown, IDLE_TIME_MS)
|
||||
@ -81,7 +94,13 @@ export const Stream = () => {
|
||||
engineCommandManager.engineConnection?.connect(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Teardown everything if we go hidden or reconnect
|
||||
if (IDLE) {
|
||||
globalThis?.window?.document?.addEventListener(
|
||||
'visibilitychange',
|
||||
onVisibilityChange
|
||||
)
|
||||
}
|
||||
|
||||
let timeoutIdIdleB: ReturnType<typeof setTimeout> | undefined = undefined
|
||||
@ -93,7 +112,7 @@ export const Stream = () => {
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
}
|
||||
|
||||
if (IDLE && DEV) {
|
||||
if (IDLE) {
|
||||
globalThis?.window?.document?.addEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousemove', onAnyInput)
|
||||
globalThis?.window?.document?.addEventListener('mousedown', onAnyInput)
|
||||
@ -101,7 +120,7 @@ export const Stream = () => {
|
||||
globalThis?.window?.document?.addEventListener('touchstart', onAnyInput)
|
||||
}
|
||||
|
||||
if (IDLE && DEV) {
|
||||
if (IDLE) {
|
||||
timeoutIdIdleB = setTimeout(teardown, IDLE_TIME_MS)
|
||||
}
|
||||
|
||||
@ -109,7 +128,14 @@ export const Stream = () => {
|
||||
globalThis?.window?.document?.removeEventListener('paste', handlePaste, {
|
||||
capture: true,
|
||||
})
|
||||
if (IDLE && DEV) {
|
||||
if (IDLE) {
|
||||
clearTimeout(timeoutIdIdleA)
|
||||
clearTimeout(timeoutIdIdleB)
|
||||
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'visibilitychange',
|
||||
onVisibilityChange
|
||||
)
|
||||
globalThis?.window?.document?.removeEventListener('keydown', onAnyInput)
|
||||
globalThis?.window?.document?.removeEventListener(
|
||||
'mousemove',
|
||||
@ -126,7 +152,7 @@ export const Stream = () => {
|
||||
)
|
||||
}
|
||||
}
|
||||
}, [])
|
||||
}, [IDLE])
|
||||
|
||||
useEffect(() => {
|
||||
setIsFirstRender(kclManager.isFirstRender)
|
||||
@ -249,6 +275,32 @@ export const Stream = () => {
|
||||
<ClientSideScene
|
||||
cameraControls={settings.context.modeling.mouseControls.current}
|
||||
/>
|
||||
{isPaused && (
|
||||
<div className="text-center absolute inset-0">
|
||||
<div
|
||||
className="flex flex-col items-center justify-center h-screen"
|
||||
data-testid="paused"
|
||||
>
|
||||
<div className="border-primary border p-2 rounded-sm">
|
||||
<svg
|
||||
width="8"
|
||||
height="12"
|
||||
viewBox="0 0 8 12"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M2 12V0H0V12H2ZM8 12V0H6V12H8Z"
|
||||
fill="var(--primary)"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<p className="text-base mt-2 text-primary bold">Paused</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{(!isNetworkOkay || isLoading || isFirstRender) && !isFreezeFrame && (
|
||||
<div className="text-center absolute inset-0">
|
||||
<Loading>
|
||||
|
@ -1,11 +1,16 @@
|
||||
import { createContext, useContext } from 'react'
|
||||
import {
|
||||
ConnectingTypeGroup,
|
||||
EngineConnectionStateType,
|
||||
EngineConnectionState,
|
||||
initialConnectingTypeGroupState,
|
||||
} from '../lang/std/engineConnection'
|
||||
import { NetworkStatus, NetworkHealthState } from './useNetworkStatus'
|
||||
|
||||
export const NetworkContext = createContext<NetworkStatus>({
|
||||
immediateState: {
|
||||
type: EngineConnectionStateType.Disconnected,
|
||||
} as EngineConnectionState,
|
||||
hasIssues: undefined,
|
||||
overallState: NetworkHealthState.Disconnected,
|
||||
internetConnected: true,
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
EngineCommandManagerEvents,
|
||||
EngineConnectionEvents,
|
||||
EngineConnectionStateType,
|
||||
EngineConnectionState,
|
||||
ErrorType,
|
||||
initialConnectingTypeGroupState,
|
||||
} from '../lang/std/engineConnection'
|
||||
@ -19,6 +20,7 @@ export enum NetworkHealthState {
|
||||
}
|
||||
|
||||
export interface NetworkStatus {
|
||||
immediateState: EngineConnectionState
|
||||
hasIssues: boolean | undefined
|
||||
overallState: NetworkHealthState
|
||||
internetConnected: boolean
|
||||
@ -33,6 +35,9 @@ export interface NetworkStatus {
|
||||
// Must be called from one place in the application.
|
||||
// We've chosen the <Router /> component for this.
|
||||
export function useNetworkStatus() {
|
||||
const [immediateState, setImmediateState] = useState<EngineConnectionState>({
|
||||
type: EngineConnectionStateType.Disconnected,
|
||||
})
|
||||
const [steps, setSteps] = useState(
|
||||
structuredClone(initialConnectingTypeGroupState)
|
||||
)
|
||||
@ -126,6 +131,7 @@ export function useNetworkStatus() {
|
||||
const onConnectionStateChange = ({
|
||||
detail: engineConnectionState,
|
||||
}: CustomEvent) => {
|
||||
setImmediateState(engineConnectionState)
|
||||
setSteps((steps) => {
|
||||
let nextSteps = structuredClone(steps)
|
||||
|
||||
@ -215,6 +221,7 @@ export function useNetworkStatus() {
|
||||
}, [])
|
||||
|
||||
return {
|
||||
immediateState,
|
||||
hasIssues,
|
||||
overallState,
|
||||
internetConnected,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useLayoutEffect, useEffect, useRef, useState } from 'react'
|
||||
import { useLayoutEffect, useEffect, useRef } from 'react'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import { Themes } from 'lib/theme'
|
||||
|
@ -304,7 +304,6 @@ describe('testing sketchOnExtrudedFace', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const segmentSnippet = `line([9.7, 9.19], %)`
|
||||
const segmentRange: [number, number] = [
|
||||
code.indexOf(segmentSnippet),
|
||||
@ -321,8 +320,7 @@ describe('testing sketchOnExtrudedFace', () => {
|
||||
const extruded = sketchOnExtrudedFace(
|
||||
ast,
|
||||
segmentPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory
|
||||
extrudePathToNode
|
||||
)
|
||||
if (err(extruded)) throw extruded
|
||||
const { modifiedAst } = extruded
|
||||
@ -345,7 +343,6 @@ const sketch001 = startSketchOn(part001, seg01)`)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const segmentSnippet = `close(%)`
|
||||
const segmentRange: [number, number] = [
|
||||
code.indexOf(segmentSnippet),
|
||||
@ -362,8 +359,7 @@ const sketch001 = startSketchOn(part001, seg01)`)
|
||||
const extruded = sketchOnExtrudedFace(
|
||||
ast,
|
||||
segmentPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory
|
||||
extrudePathToNode
|
||||
)
|
||||
if (err(extruded)) throw extruded
|
||||
const { modifiedAst } = extruded
|
||||
@ -386,7 +382,6 @@ const sketch001 = startSketchOn(part001, seg01)`)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const sketchSnippet = `startProfileAt([3.58, 2.06], %)`
|
||||
const sketchRange: [number, number] = [
|
||||
code.indexOf(sketchSnippet),
|
||||
@ -404,7 +399,6 @@ const sketch001 = startSketchOn(part001, seg01)`)
|
||||
ast,
|
||||
sketchPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory,
|
||||
'end'
|
||||
)
|
||||
if (err(extruded)) throw extruded
|
||||
@ -436,7 +430,6 @@ const sketch001 = startSketchOn(part001, 'END')`)
|
||||
const part001 = extrude(5 + 7, sketch001)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const segmentSnippet = `line([4.99, -0.46], %)`
|
||||
const segmentRange: [number, number] = [
|
||||
code.indexOf(segmentSnippet),
|
||||
@ -453,8 +446,7 @@ const sketch001 = startSketchOn(part001, 'END')`)
|
||||
const updatedAst = sketchOnExtrudedFace(
|
||||
ast,
|
||||
segmentPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory
|
||||
extrudePathToNode
|
||||
)
|
||||
if (err(updatedAst)) throw updatedAst
|
||||
const newCode = recast(updatedAst.modifiedAst)
|
||||
|
@ -349,7 +349,6 @@ export function sketchOnExtrudedFace(
|
||||
node: Program,
|
||||
sketchPathToNode: PathToNode,
|
||||
extrudePathToNode: PathToNode,
|
||||
programMemory: ProgramMemory,
|
||||
cap: 'none' | 'start' | 'end' = 'none'
|
||||
): { modifiedAst: Program; pathToNode: PathToNode } | Error {
|
||||
let _node = { ...node }
|
||||
@ -388,7 +387,6 @@ export function sketchOnExtrudedFace(
|
||||
if (cap === 'none') {
|
||||
const __tag = addTagForSketchOnFace(
|
||||
{
|
||||
previousProgramMemory: programMemory,
|
||||
pathToNode: sketchPathToNode,
|
||||
node: _node,
|
||||
},
|
||||
|
315
src/lang/modifyAst/addFillet.test.ts
Normal file
@ -0,0 +1,315 @@
|
||||
import {
|
||||
parse,
|
||||
recast,
|
||||
initPromise,
|
||||
PathToNode,
|
||||
Value,
|
||||
Program,
|
||||
CallExpression,
|
||||
} from '../wasm'
|
||||
import { addFillet, isTagUsedInFillet } from './addFillet'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
||||
import { createLiteral } from 'lang/modifyAst'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise // Initialize the WASM environment before running tests
|
||||
})
|
||||
|
||||
const runFilletTest = async (
|
||||
code: string,
|
||||
segmentSnippet: string,
|
||||
extrudeSnippet: string,
|
||||
radius = createLiteral(5) as Value,
|
||||
expectedCode: string
|
||||
) => {
|
||||
const astOrError = parse(code)
|
||||
if (astOrError instanceof Error) {
|
||||
return new Error('AST not found')
|
||||
}
|
||||
|
||||
const ast = astOrError as Program
|
||||
|
||||
const segmentRange: [number, number] = [
|
||||
code.indexOf(segmentSnippet),
|
||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
||||
]
|
||||
const pathToSegmentNode: PathToNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
segmentRange
|
||||
)
|
||||
|
||||
const extrudeRange: [number, number] = [
|
||||
code.indexOf(extrudeSnippet),
|
||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
||||
]
|
||||
|
||||
const pathToExtrudeNode: PathToNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
extrudeRange
|
||||
)
|
||||
if (pathToExtrudeNode instanceof Error) {
|
||||
return new Error('Path to extrude node not found')
|
||||
}
|
||||
|
||||
// const radius = createLiteral(5) as Value
|
||||
|
||||
const result = addFillet(ast, pathToSegmentNode, pathToExtrudeNode, radius)
|
||||
if (result instanceof Error) {
|
||||
return result
|
||||
}
|
||||
const { modifiedAst } = result
|
||||
const newCode = recast(modifiedAst)
|
||||
|
||||
expect(newCode).toContain(expectedCode)
|
||||
}
|
||||
|
||||
describe('Testing addFillet', () => {
|
||||
/**
|
||||
* 1. Ideal Case
|
||||
*/
|
||||
|
||||
it('should add a fillet to a specific segment after extrusion, clean', async () => {
|
||||
const code = `
|
||||
const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
`
|
||||
const segmentSnippet = `line([60.04, -55.72], %)`
|
||||
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
|
||||
const radius = createLiteral(5) as Value
|
||||
const expectedCode = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %, $seg01)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
|> fillet({ radius: 5, tags: [seg01] }, %)`
|
||||
|
||||
await runFilletTest(
|
||||
code,
|
||||
segmentSnippet,
|
||||
extrudeSnippet,
|
||||
radius,
|
||||
expectedCode
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* 2. Case of existing tag in the other line
|
||||
*/
|
||||
|
||||
it('should add a fillet to a specific segment after extrusion with existing tag in any other line', async () => {
|
||||
const code = `
|
||||
const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg01)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
`
|
||||
const segmentSnippet = `line([60.04, -55.72], %)`
|
||||
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
|
||||
const radius = createLiteral(5) as Value
|
||||
const expectedCode = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %, $seg02)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg01)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
|> fillet({ radius: 5, tags: [seg02] }, %)`
|
||||
|
||||
await runFilletTest(
|
||||
code,
|
||||
segmentSnippet,
|
||||
extrudeSnippet,
|
||||
radius,
|
||||
expectedCode
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* 3. Case of existing tag in the fillet line
|
||||
*/
|
||||
|
||||
it('should add a fillet to a specific segment after extrusion with existing tag in that exact line', async () => {
|
||||
const code = `
|
||||
const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg03)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
`
|
||||
const segmentSnippet = `line([-87.24, -47.08], %, $seg03)`
|
||||
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
|
||||
const radius = createLiteral(5) as Value
|
||||
const expectedCode = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg03)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
|> fillet({ radius: 5, tags: [seg03] }, %)`
|
||||
|
||||
await runFilletTest(
|
||||
code,
|
||||
segmentSnippet,
|
||||
extrudeSnippet,
|
||||
radius,
|
||||
expectedCode
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* 4. Case of existing fillet on some other segment
|
||||
*/
|
||||
|
||||
it('should add another fillet after the existing fillet', async () => {
|
||||
const code = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg03)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
|> fillet({ radius: 10, tags: [seg03] }, %)`
|
||||
const segmentSnippet = `line([60.04, -55.72], %)`
|
||||
const extrudeSnippet = `const extrude001 = extrude(50, sketch001)`
|
||||
const radius = createLiteral(5) as Value
|
||||
const expectedCode = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([2.16, 49.67], %)
|
||||
|> line([101.49, 139.93], %)
|
||||
|> line([60.04, -55.72], %, $seg01)
|
||||
|> line([1.29, -115.74], %)
|
||||
|> line([-87.24, -47.08], %, $seg03)
|
||||
|> tangentialArcTo([56.15, -94.58], %)
|
||||
|> tangentialArcTo([14.68, -104.52], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(50, sketch001)
|
||||
|> fillet({ radius: 10, tags: [seg03] }, %)
|
||||
|> fillet({ radius: 5, tags: [seg01] }, %)`
|
||||
|
||||
await runFilletTest(
|
||||
code,
|
||||
segmentSnippet,
|
||||
extrudeSnippet,
|
||||
radius,
|
||||
expectedCode
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Testing isTagUsedInFillet', () => {
|
||||
const code = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([7.72, 4.13], %)
|
||||
|> line([7.11, 3.48], %, $seg01)
|
||||
|> line([-3.29, -13.85], %)
|
||||
|> line([-6.37, 3.88], %, $seg02)
|
||||
|> close(%)
|
||||
const extrude001 = extrude(-5, sketch001)
|
||||
|> fillet({
|
||||
radius: 1.11,
|
||||
tags: [
|
||||
getOppositeEdge(seg01, %),
|
||||
seg01,
|
||||
getPreviousAdjacentEdge(seg02, %)
|
||||
]
|
||||
}, %)
|
||||
`
|
||||
it('should correctly identify getOppositeEdge and baseEdge edges', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return
|
||||
const lineOfInterest = `line([7.11, 3.48], %, $seg01)`
|
||||
const range: [number, number] = [
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(callExp)) return
|
||||
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
|
||||
expect(edges).toEqual(['getOppositeEdge', 'baseEdge'])
|
||||
})
|
||||
it('should correctly identify getPreviousAdjacentEdge edges', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return
|
||||
const lineOfInterest = `line([-6.37, 3.88], %, $seg02)`
|
||||
const range: [number, number] = [
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(callExp)) return
|
||||
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
|
||||
expect(edges).toEqual(['getPreviousAdjacentEdge'])
|
||||
})
|
||||
it('should correctly identify no edges', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return
|
||||
const lineOfInterest = `line([-3.29, -13.85], %)`
|
||||
const range: [number, number] = [
|
||||
code.indexOf(lineOfInterest),
|
||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||
]
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
if (err(pathToNode)) return
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(callExp)) return
|
||||
const edges = isTagUsedInFillet({ ast, callExp: callExp.node })
|
||||
expect(edges).toEqual([])
|
||||
})
|
||||
})
|
404
src/lang/modifyAst/addFillet.ts
Normal file
@ -0,0 +1,404 @@
|
||||
import {
|
||||
ArrayExpression,
|
||||
CallExpression,
|
||||
ObjectExpression,
|
||||
PathToNode,
|
||||
Program,
|
||||
Value,
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
} from '../wasm'
|
||||
import {
|
||||
createCallExpressionStdLib,
|
||||
createLiteral,
|
||||
createPipeSubstitution,
|
||||
createObjectExpression,
|
||||
createArrayExpression,
|
||||
createIdentifier,
|
||||
createPipeExpression,
|
||||
} from '../modifyAst'
|
||||
import {
|
||||
getNodeFromPath,
|
||||
getNodePathFromSourceRange,
|
||||
hasSketchPipeBeenExtruded,
|
||||
traverse,
|
||||
} from '../queryAst'
|
||||
import {
|
||||
addTagForSketchOnFace,
|
||||
getTagFromCallExpression,
|
||||
sketchLineHelperMap,
|
||||
} from '../std/sketch'
|
||||
import { err } from 'lib/trap'
|
||||
import { Selections, canFilletSelection } from 'lib/selections'
|
||||
|
||||
export function addFillet(
|
||||
node: Program,
|
||||
pathToSegmentNode: PathToNode,
|
||||
pathToExtrudeNode: PathToNode,
|
||||
radius = createLiteral(5) as Value
|
||||
// shouldPipe = false, // TODO: Implement this feature
|
||||
): { modifiedAst: Program; pathToFilletNode: PathToNode } | Error {
|
||||
// close ast to make mutations safe
|
||||
let _node: Program = JSON.parse(JSON.stringify(node))
|
||||
|
||||
/**
|
||||
* Add Tag to the Segment Expression
|
||||
*/
|
||||
|
||||
// Find the specific sketch segment to tag with the new tag
|
||||
const sketchSegmentChunk = getNodeFromPath(
|
||||
_node,
|
||||
pathToSegmentNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(sketchSegmentChunk)) return sketchSegmentChunk
|
||||
const { node: sketchSegmentNode } = sketchSegmentChunk as {
|
||||
node: CallExpression
|
||||
}
|
||||
|
||||
// Check whether selection is a valid segment from sketchLineHelpersMap
|
||||
if (!(sketchSegmentNode.callee.name in sketchLineHelperMap)) {
|
||||
return new Error('Selection is not a sketch segment')
|
||||
}
|
||||
|
||||
// Add tag to the sketch segment or use existing tag
|
||||
const taggedSegment = addTagForSketchOnFace(
|
||||
{
|
||||
// previousProgramMemory: programMemory,
|
||||
pathToNode: pathToSegmentNode,
|
||||
node: _node,
|
||||
},
|
||||
sketchSegmentNode.callee.name
|
||||
)
|
||||
if (err(taggedSegment)) return taggedSegment
|
||||
const { tag } = taggedSegment
|
||||
|
||||
/**
|
||||
* Find Extrude Expression automatically
|
||||
*/
|
||||
|
||||
// 1. Get the sketch name
|
||||
|
||||
/**
|
||||
* Add Fillet to the Extrude expression
|
||||
*/
|
||||
|
||||
// Create the fillet call expression in one line
|
||||
const filletCall = createCallExpressionStdLib('fillet', [
|
||||
createObjectExpression({
|
||||
radius: radius,
|
||||
tags: createArrayExpression([createIdentifier(tag)]),
|
||||
}),
|
||||
createPipeSubstitution(),
|
||||
])
|
||||
|
||||
// Locate the extrude call
|
||||
const extrudeChunk = getNodeFromPath<VariableDeclaration>(
|
||||
_node,
|
||||
pathToExtrudeNode,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(extrudeChunk)) return extrudeChunk
|
||||
const { node: extrudeVarDecl } = extrudeChunk
|
||||
|
||||
const extrudeDeclarator = extrudeVarDecl.declarations[0]
|
||||
const extrudeInit = extrudeDeclarator.init
|
||||
|
||||
if (
|
||||
!extrudeDeclarator ||
|
||||
(extrudeInit.type !== 'CallExpression' &&
|
||||
extrudeInit.type !== 'PipeExpression')
|
||||
) {
|
||||
return new Error('Extrude PipeExpression / CallExpression not found.')
|
||||
}
|
||||
|
||||
// determine if extrude is in a PipeExpression or CallExpression
|
||||
|
||||
// CallExpression - no fillet
|
||||
// PipeExpression - fillet exists
|
||||
|
||||
const getPathToNodeOfFilletLiteral = (
|
||||
pathToExtrudeNode: PathToNode,
|
||||
extrudeDeclarator: VariableDeclarator,
|
||||
tag: string
|
||||
): PathToNode => {
|
||||
let pathToFilletObj: any
|
||||
let inFillet = false
|
||||
traverse(extrudeDeclarator.init, {
|
||||
enter(node, path) {
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||
inFillet = true
|
||||
}
|
||||
if (inFillet && node.type === 'ObjectExpression') {
|
||||
const hasTag = node.properties.some((prop) => {
|
||||
const isTagProp = prop.key.name === 'tags'
|
||||
if (isTagProp && prop.value.type === 'ArrayExpression') {
|
||||
return prop.value.elements.some(
|
||||
(element) =>
|
||||
element.type === 'Identifier' && element.name === tag
|
||||
)
|
||||
}
|
||||
return false
|
||||
})
|
||||
if (!hasTag) return false
|
||||
pathToFilletObj = path
|
||||
node.properties.forEach((prop, index) => {
|
||||
if (prop.key.name === 'radius') {
|
||||
pathToFilletObj.push(
|
||||
['properties', 'ObjectExpression'],
|
||||
[index, 'index'],
|
||||
['value', 'Property']
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
leave(node) {
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||
inFillet = false
|
||||
}
|
||||
},
|
||||
})
|
||||
let indexOfPipeExpression = pathToExtrudeNode.findIndex(
|
||||
(path) => path[1] === 'PipeExpression'
|
||||
)
|
||||
indexOfPipeExpression =
|
||||
indexOfPipeExpression === -1
|
||||
? pathToExtrudeNode.length
|
||||
: indexOfPipeExpression
|
||||
|
||||
return [
|
||||
...pathToExtrudeNode.slice(0, indexOfPipeExpression),
|
||||
...pathToFilletObj,
|
||||
]
|
||||
}
|
||||
|
||||
if (extrudeInit.type === 'CallExpression') {
|
||||
// 1. no fillet case
|
||||
extrudeDeclarator.init = createPipeExpression([extrudeInit, filletCall])
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToFilletNode: getPathToNodeOfFilletLiteral(
|
||||
pathToExtrudeNode,
|
||||
extrudeDeclarator,
|
||||
tag
|
||||
),
|
||||
}
|
||||
} else if (extrudeInit.type === 'PipeExpression') {
|
||||
// 2. fillet case
|
||||
|
||||
// there are 2 options here:
|
||||
|
||||
const existingFilletCall = extrudeInit.body.find((node) => {
|
||||
return node.type === 'CallExpression' && node.callee.name === 'fillet'
|
||||
})
|
||||
|
||||
if (!existingFilletCall || existingFilletCall.type !== 'CallExpression') {
|
||||
return new Error('Fillet CallExpression not found.')
|
||||
}
|
||||
|
||||
// check if the existing fillet has the same tag as the new fillet
|
||||
let filletTag = null
|
||||
if (existingFilletCall.arguments[0].type === 'ObjectExpression') {
|
||||
const properties = (existingFilletCall.arguments[0] as ObjectExpression)
|
||||
.properties
|
||||
const tagsProperty = properties.find((prop) => prop.key.name === 'tags')
|
||||
if (tagsProperty && tagsProperty.value.type === 'ArrayExpression') {
|
||||
const elements = (tagsProperty.value as ArrayExpression).elements
|
||||
if (elements.length > 0 && elements[0].type === 'Identifier') {
|
||||
filletTag = elements[0].name
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return new Error('Expected an ObjectExpression node')
|
||||
}
|
||||
|
||||
if (filletTag !== tag) {
|
||||
extrudeInit.body.push(filletCall)
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToFilletNode: getPathToNodeOfFilletLiteral(
|
||||
pathToExtrudeNode,
|
||||
extrudeDeclarator,
|
||||
tag
|
||||
),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return new Error('Unsupported extrude type.')
|
||||
}
|
||||
|
||||
return new Error('Unsupported extrude type.')
|
||||
}
|
||||
|
||||
export const hasValidFilletSelection = ({
|
||||
selectionRanges,
|
||||
ast,
|
||||
code,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
ast: Program
|
||||
code: string
|
||||
}) => {
|
||||
// case 0: check if there is anything filletable in the scene
|
||||
let extrudeExists = false
|
||||
traverse(ast, {
|
||||
enter(node) {
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'extrude') {
|
||||
extrudeExists = true
|
||||
}
|
||||
},
|
||||
})
|
||||
if (!extrudeExists) return false
|
||||
|
||||
// case 1: nothing selected, test whether the extrusion exists
|
||||
if (selectionRanges) {
|
||||
if (selectionRanges.codeBasedSelections.length === 0) {
|
||||
return true
|
||||
}
|
||||
const range0 = selectionRanges.codeBasedSelections[0].range[0]
|
||||
const codeLength = code.length
|
||||
if (range0 === codeLength) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// case 2: sketch segment selected, test whether it is extruded
|
||||
// TODO: add loft / sweep check
|
||||
if (selectionRanges.codeBasedSelections.length > 0) {
|
||||
const isExtruded = hasSketchPipeBeenExtruded(
|
||||
selectionRanges.codeBasedSelections[0],
|
||||
ast
|
||||
)
|
||||
if (isExtruded) {
|
||||
const pathToSelectedNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
selectionRanges.codeBasedSelections[0].range
|
||||
)
|
||||
const segmentNode = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToSelectedNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(segmentNode)) return false
|
||||
if (segmentNode.node.type === 'CallExpression') {
|
||||
const segmentName = segmentNode.node.callee.name
|
||||
if (segmentName in sketchLineHelperMap) {
|
||||
const edges = isTagUsedInFillet({
|
||||
ast,
|
||||
callExp: segmentNode.node,
|
||||
})
|
||||
// edge has already been filleted
|
||||
if (
|
||||
['edge', 'default'].includes(
|
||||
selectionRanges.codeBasedSelections[0].type
|
||||
) &&
|
||||
edges.includes('baseEdge')
|
||||
)
|
||||
return false
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return canFilletSelection(selectionRanges)
|
||||
}
|
||||
|
||||
type EdgeTypes =
|
||||
| 'baseEdge'
|
||||
| 'getNextAdjacentEdge'
|
||||
| 'getPreviousAdjacentEdge'
|
||||
| 'getOppositeEdge'
|
||||
|
||||
export const isTagUsedInFillet = ({
|
||||
ast,
|
||||
callExp,
|
||||
}: {
|
||||
ast: Program
|
||||
callExp: CallExpression
|
||||
}): Array<EdgeTypes> => {
|
||||
const tag = getTagFromCallExpression(callExp)
|
||||
if (err(tag)) return []
|
||||
|
||||
let inFillet = false
|
||||
let inObj = false
|
||||
let inTagHelper: EdgeTypes | '' = ''
|
||||
const edges: Array<EdgeTypes> = []
|
||||
traverse(ast, {
|
||||
enter: (node) => {
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||
inFillet = true
|
||||
}
|
||||
if (inFillet && node.type === 'ObjectExpression') {
|
||||
node.properties.forEach((prop) => {
|
||||
if (
|
||||
prop.key.name === 'tags' &&
|
||||
prop.value.type === 'ArrayExpression'
|
||||
) {
|
||||
inObj = true
|
||||
}
|
||||
})
|
||||
}
|
||||
if (
|
||||
inObj &&
|
||||
inFillet &&
|
||||
node.type === 'CallExpression' &&
|
||||
(node.callee.name === 'getOppositeEdge' ||
|
||||
node.callee.name === 'getNextAdjacentEdge' ||
|
||||
node.callee.name === 'getPreviousAdjacentEdge')
|
||||
) {
|
||||
inTagHelper = node.callee.name
|
||||
}
|
||||
if (
|
||||
inObj &&
|
||||
inFillet &&
|
||||
!inTagHelper &&
|
||||
node.type === 'Identifier' &&
|
||||
node.name === tag
|
||||
) {
|
||||
edges.push('baseEdge')
|
||||
}
|
||||
if (
|
||||
inObj &&
|
||||
inFillet &&
|
||||
inTagHelper &&
|
||||
node.type === 'Identifier' &&
|
||||
node.name === tag
|
||||
) {
|
||||
edges.push(inTagHelper)
|
||||
}
|
||||
},
|
||||
leave: (node) => {
|
||||
if (node.type === 'CallExpression' && node.callee.name === 'fillet') {
|
||||
inFillet = false
|
||||
}
|
||||
if (inFillet && node.type === 'ObjectExpression') {
|
||||
node.properties.forEach((prop) => {
|
||||
if (
|
||||
prop.key.name === 'tags' &&
|
||||
prop.value.type === 'ArrayExpression'
|
||||
) {
|
||||
inObj = true
|
||||
}
|
||||
})
|
||||
}
|
||||
if (
|
||||
inObj &&
|
||||
inFillet &&
|
||||
node.type === 'CallExpression' &&
|
||||
(node.callee.name === 'getOppositeEdge' ||
|
||||
node.callee.name === 'getNextAdjacentEdge' ||
|
||||
node.callee.name === 'getPreviousAdjacentEdge')
|
||||
) {
|
||||
inTagHelper = ''
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
return edges
|
||||
}
|
@ -143,6 +143,7 @@ export enum DisconnectingType {
|
||||
Error = 'error',
|
||||
Timeout = 'timeout',
|
||||
Quit = 'quit',
|
||||
Pause = 'pause',
|
||||
}
|
||||
|
||||
// Sorted by severity
|
||||
@ -200,6 +201,7 @@ export type DisconnectingValue =
|
||||
| State<DisconnectingType.Error, ErrorType>
|
||||
| State<DisconnectingType.Timeout, void>
|
||||
| State<DisconnectingType.Quit, void>
|
||||
| State<DisconnectingType.Pause, void>
|
||||
|
||||
// These are ordered by the expected sequence.
|
||||
export enum ConnectingType {
|
||||
@ -300,7 +302,7 @@ class EngineConnection extends EventTarget {
|
||||
pc?: RTCPeerConnection
|
||||
unreliableDataChannel?: RTCDataChannel
|
||||
mediaStream?: MediaStream
|
||||
freezeFrame: boolean = false
|
||||
idleMode: boolean = false
|
||||
|
||||
onIceCandidate = function (
|
||||
this: RTCPeerConnection,
|
||||
@ -391,10 +393,10 @@ class EngineConnection extends EventTarget {
|
||||
this.pingPongSpan = { ping: undefined, pong: undefined }
|
||||
|
||||
// Without an interval ping, our connection will timeout.
|
||||
// If this.freezeFrame is true we skip this logic so only reconnect
|
||||
// If this.idleMode is true we skip this logic so only reconnect
|
||||
// happens on mouse move
|
||||
this.pingIntervalId = setInterval(() => {
|
||||
if (this.freezeFrame) return
|
||||
if (this.idleMode) return
|
||||
|
||||
switch (this.state.type as EngineConnectionStateType) {
|
||||
case EngineConnectionStateType.ConnectionEstablished:
|
||||
@ -456,8 +458,8 @@ class EngineConnection extends EventTarget {
|
||||
return this.state.type === EngineConnectionStateType.ConnectionEstablished
|
||||
}
|
||||
|
||||
tearDown(opts?: { freeze: boolean }) {
|
||||
this.freezeFrame = opts?.freeze ?? false
|
||||
tearDown(opts?: { idleMode: boolean }) {
|
||||
this.idleMode = opts?.idleMode ?? false
|
||||
this.disconnectAll()
|
||||
clearInterval(this.pingIntervalId)
|
||||
|
||||
@ -497,9 +499,18 @@ class EngineConnection extends EventTarget {
|
||||
this.onNetworkStatusReady
|
||||
)
|
||||
|
||||
this.state = {
|
||||
this.state = opts?.idleMode
|
||||
? {
|
||||
type: EngineConnectionStateType.Disconnecting,
|
||||
value: { type: DisconnectingType.Quit },
|
||||
value: {
|
||||
type: DisconnectingType.Pause,
|
||||
},
|
||||
}
|
||||
: {
|
||||
type: EngineConnectionStateType.Disconnecting,
|
||||
value: {
|
||||
type: DisconnectingType.Quit,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -1099,8 +1110,6 @@ class EngineConnection extends EventTarget {
|
||||
this.unreliableDataChannel?.readyState === 'closed'
|
||||
if (allClosed) {
|
||||
// Do not notify the rest of the program that we have cut off anything.
|
||||
if (this.freezeFrame) return
|
||||
|
||||
this.state = { type: EngineConnectionStateType.Disconnected }
|
||||
}
|
||||
}
|
||||
@ -1738,7 +1747,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
}
|
||||
}
|
||||
}
|
||||
tearDown() {
|
||||
tearDown(opts?: { idleMode: boolean }) {
|
||||
if (this.engineConnection) {
|
||||
this.engineConnection.removeEventListener(
|
||||
EngineConnectionEvents.Opened,
|
||||
@ -1757,7 +1766,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
this.onEngineConnectionNewTrack as EventListener
|
||||
)
|
||||
|
||||
this.engineConnection?.tearDown()
|
||||
this.engineConnection?.tearDown(opts)
|
||||
this.engineConnection = undefined
|
||||
|
||||
// Our window.tearDown assignment causes this case to happen which is
|
||||
@ -1765,7 +1774,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
// @ts-ignore
|
||||
} else if (this.engineCommandManager?.engineConnection) {
|
||||
// @ts-ignore
|
||||
this.engineCommandManager?.engineConnection?.tearDown()
|
||||
this.engineCommandManager?.engineConnection?.tearDown(opts)
|
||||
}
|
||||
}
|
||||
async startNewSession() {
|
||||
|
@ -221,7 +221,7 @@ describe('testing addTagForSketchOnFace', () => {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const sketchOnFaceRetVal = addTagForSketchOnFace(
|
||||
{
|
||||
previousProgramMemory: programMemory,
|
||||
// previousProgramMemory: programMemory, // redundant?
|
||||
pathToNode,
|
||||
node: ast,
|
||||
},
|
||||
|
@ -28,7 +28,6 @@ import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
||||
|
||||
import {
|
||||
SketchLineHelper,
|
||||
ModifyAstBase,
|
||||
TransformCallback,
|
||||
ConstrainInfo,
|
||||
RawValues,
|
||||
@ -37,6 +36,7 @@ import {
|
||||
SingleValueInput,
|
||||
VarValueKeys,
|
||||
ArrayOrObjItemInput,
|
||||
AddTagInfo,
|
||||
} from 'lang/std/stdTypes'
|
||||
|
||||
import {
|
||||
@ -308,6 +308,18 @@ function singleRawValueHelper(
|
||||
]
|
||||
}
|
||||
|
||||
function getTag(index = 2): SketchLineHelper['getTag'] {
|
||||
return (callExp: CallExpression) => {
|
||||
if (callExp.type !== 'CallExpression')
|
||||
return new Error('Not a CallExpression')
|
||||
const arg = callExp.arguments?.[index]
|
||||
if (!arg) return new Error('No argument')
|
||||
if (arg.type !== 'TagDeclarator')
|
||||
return new Error('Tag not a TagDeclarator')
|
||||
return arg.value
|
||||
}
|
||||
}
|
||||
|
||||
export const lineTo: SketchLineHelper = {
|
||||
add: ({
|
||||
node,
|
||||
@ -377,6 +389,7 @@ export const lineTo: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -503,6 +516,7 @@ export const line: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -563,6 +577,7 @@ export const xLineTo: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
horzVertConstraintInfoHelper(
|
||||
@ -623,6 +638,7 @@ export const yLineTo: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
horzVertConstraintInfoHelper(
|
||||
@ -682,6 +698,7 @@ export const xLine: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
horzVertConstraintInfoHelper(
|
||||
@ -738,6 +755,7 @@ export const yLine: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
horzVertConstraintInfoHelper(
|
||||
@ -830,6 +848,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp: CallExpression, code, pathToNode) => {
|
||||
if (callExp.type !== 'CallExpression') return []
|
||||
@ -948,6 +967,7 @@ export const angledLine: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -1044,6 +1064,7 @@ export const angledLineOfXLength: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -1140,6 +1161,7 @@ export const angledLineOfYLength: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -1227,6 +1249,7 @@ export const angledLineToX: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -1316,6 +1339,7 @@ export const angledLineToY: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp, ...args) =>
|
||||
commonConstraintInfoHelper(
|
||||
@ -1440,6 +1464,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
pathToNode,
|
||||
}
|
||||
},
|
||||
getTag: getTag(),
|
||||
addTag: addTag(),
|
||||
getConstraintInfo: (callExp: CallExpression, code, pathToNode) => {
|
||||
if (callExp.type !== 'CallExpression') return []
|
||||
@ -1792,10 +1817,7 @@ export function replaceSketchLine({
|
||||
return { modifiedAst, valueUsedInTransform, pathToNode }
|
||||
}
|
||||
|
||||
export function addTagForSketchOnFace(
|
||||
a: ModifyAstBase,
|
||||
expressionName: string
|
||||
) {
|
||||
export function addTagForSketchOnFace(a: AddTagInfo, expressionName: string) {
|
||||
if (expressionName === 'close') {
|
||||
return addTag(1)(a)
|
||||
}
|
||||
@ -1806,6 +1828,17 @@ export function addTagForSketchOnFace(
|
||||
return new Error(`"${expressionName}" is not a sketch line helper`)
|
||||
}
|
||||
|
||||
export function getTagFromCallExpression(
|
||||
callExp: CallExpression
|
||||
): string | Error {
|
||||
if (callExp.callee.name === 'close') return getTag(1)(callExp)
|
||||
if (callExp.callee.name in sketchLineHelperMap) {
|
||||
const { getTag } = sketchLineHelperMap[callExp.callee.name]
|
||||
return getTag(callExp)
|
||||
}
|
||||
return new Error(`"${callExp.callee.name}" is not a sketch line helper`)
|
||||
}
|
||||
|
||||
function isAngleLiteral(lineArugement: Value): boolean {
|
||||
return lineArugement?.type === 'ArrayExpression'
|
||||
? isLiteralArrayOrStatic(lineArugement.elements[0])
|
||||
@ -1816,9 +1849,7 @@ function isAngleLiteral(lineArugement: Value): boolean {
|
||||
: false
|
||||
}
|
||||
|
||||
type addTagFn = (
|
||||
a: ModifyAstBase
|
||||
) => { modifiedAst: Program; tag: string } | Error
|
||||
type addTagFn = (a: AddTagInfo) => { modifiedAst: Program; tag: string } | Error
|
||||
|
||||
function addTag(tagIndex = 2): addTagFn {
|
||||
return ({ node, pathToNode }) => {
|
||||
|
@ -32,6 +32,11 @@ export interface ModifyAstBase {
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
|
||||
export interface AddTagInfo {
|
||||
node: Program
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
|
||||
interface addCall extends ModifyAstBase {
|
||||
to: [number, number]
|
||||
from: [number, number]
|
||||
@ -127,7 +132,8 @@ export interface SketchLineHelper {
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
| Error
|
||||
addTag: (a: ModifyAstBase) =>
|
||||
getTag: (a: CallExpression) => string | Error
|
||||
addTag: (a: AddTagInfo) =>
|
||||
| {
|
||||
modifiedAst: Program
|
||||
tag: string
|
||||
|
@ -27,6 +27,11 @@ export type ModelingCommandSchema = {
|
||||
// result: (typeof EXTRUSION_RESULTS)[number]
|
||||
distance: KclCommandValue
|
||||
}
|
||||
Fillet: {
|
||||
// todo
|
||||
selection: Selections
|
||||
radius: KclCommandValue
|
||||
}
|
||||
'change tool': {
|
||||
tool: SketchTool
|
||||
}
|
||||
@ -185,4 +190,36 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
},
|
||||
},
|
||||
},
|
||||
Fillet: {
|
||||
// todo
|
||||
description: 'Fillet edge',
|
||||
icon: 'fillet',
|
||||
needsReview: true,
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: [
|
||||
'default',
|
||||
'line-end',
|
||||
'line-mid',
|
||||
'extrude-wall', // to fix: accespts only this selection type
|
||||
'start-cap',
|
||||
'end-cap',
|
||||
'point',
|
||||
'edge',
|
||||
'line',
|
||||
'arc',
|
||||
'all',
|
||||
],
|
||||
multiple: true, // TODO: multiple selection like in extrude command
|
||||
required: true,
|
||||
skip: true,
|
||||
},
|
||||
radius: {
|
||||
inputType: 'kcl',
|
||||
defaultValue: KCL_DEFAULT_LENGTH,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
|
||||
export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
||||
SKETCH: 'sketch',
|
||||
EXTRUDE: 'extrude',
|
||||
SEGMENT: 'seg',
|
||||
} as const
|
||||
/** The default KCL length expression */
|
||||
export const KCL_DEFAULT_LENGTH = `5`
|
||||
|
@ -406,6 +406,17 @@ export function canExtrudeSelection(selection: Selections) {
|
||||
)
|
||||
}
|
||||
|
||||
export function canFilletSelection(selection: Selections) {
|
||||
const commonNodes = selection.codeBasedSelections.map((_, i) =>
|
||||
buildCommonNodeFromSelection(selection, i)
|
||||
) // TODO FILLET DUMMY PLACEHOLDER
|
||||
return (
|
||||
!!isSketchPipe(selection) &&
|
||||
commonNodes.every((n) => nodeHasClose(n)) &&
|
||||
commonNodes.every((n) => !nodeHasExtrude(n))
|
||||
)
|
||||
}
|
||||
|
||||
function canExtrudeSelectionItem(selection: Selections, i: number) {
|
||||
const commonNode = buildCommonNodeFromSelection(selection, i)
|
||||
|
||||
|
@ -163,6 +163,17 @@ export function createSettings() {
|
||||
validate: (v) => typeof v === 'boolean',
|
||||
hideOnPlatform: 'both', //for now
|
||||
}),
|
||||
/**
|
||||
* Stream resource saving behavior toggle
|
||||
*/
|
||||
streamIdleMode: new Setting<boolean>({
|
||||
defaultValue: false,
|
||||
description: 'Toggle stream idling, saving bandwidth and battery',
|
||||
validate: (v) => typeof v === 'boolean',
|
||||
commandConfig: {
|
||||
inputType: 'boolean',
|
||||
},
|
||||
}),
|
||||
onboardingStatus: new Setting<string>({
|
||||
defaultValue: '',
|
||||
validate: (v) => typeof v === 'string',
|
||||
|
@ -38,6 +38,7 @@ function configurationToSettingsPayload(
|
||||
: undefined,
|
||||
onboardingStatus: configuration?.settings?.app?.onboarding_status,
|
||||
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
|
||||
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
||||
projectDirectory: configuration?.settings?.project?.directory,
|
||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||
},
|
||||
@ -75,6 +76,7 @@ function projectConfigurationToSettingsPayload(
|
||||
: undefined,
|
||||
onboardingStatus: configuration?.settings?.app?.onboarding_status,
|
||||
dismissWebBanner: configuration?.settings?.app?.dismiss_web_banner,
|
||||
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||
},
|
||||
modeling: {
|
||||
|
@ -37,4 +37,7 @@ if (typeof window !== 'undefined') {
|
||||
document.addEventListener('mousemove', (e) =>
|
||||
console.log(`await page.mouse.click(${e.clientX}, ${e.clientY})`)
|
||||
)
|
||||
;(window as any).enableFillet = () => {
|
||||
;(window as any)._enableFillet = true
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
|
||||
import { Value, parse } from 'lang/wasm'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { executeAst } from 'lang/langHelpers'
|
||||
import { trap } from 'lib/trap'
|
||||
import { err, trap } from 'lib/trap'
|
||||
|
||||
const isValidVariableName = (name: string) =>
|
||||
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)
|
||||
@ -86,6 +86,7 @@ export function useCalculateKclExpression({
|
||||
const execAstAndSetResult = async () => {
|
||||
const _code = `const __result__ = ${value}`
|
||||
const ast = parse(_code)
|
||||
if (err(ast)) return
|
||||
if (trap(ast, { suppress: true })) return
|
||||
|
||||
const _programMem: any = { root: {}, return: null }
|
||||
|
@ -203,7 +203,7 @@ export const commandBarMachine = createMachine(
|
||||
'Change current argument': {
|
||||
target: 'Gathering arguments',
|
||||
internal: true,
|
||||
actions: ['Remove current argument and set a new one'],
|
||||
actions: ['Set current argument'],
|
||||
},
|
||||
|
||||
'Deselect command': {
|
||||
@ -359,29 +359,13 @@ export const commandBarMachine = createMachine(
|
||||
switch (event.type) {
|
||||
case 'Edit argument':
|
||||
return event.data.arg
|
||||
case 'Change current argument':
|
||||
return Object.values(event.data)[0]
|
||||
default:
|
||||
return context.currentArgument
|
||||
}
|
||||
},
|
||||
}),
|
||||
'Remove current argument and set a new one': assign({
|
||||
argumentsToSubmit: (context, event) => {
|
||||
if (
|
||||
event.type !== 'Change current argument' ||
|
||||
!context.currentArgument
|
||||
)
|
||||
return context.argumentsToSubmit
|
||||
const { name } = context.currentArgument
|
||||
|
||||
const { [name]: _, ...rest } = context.argumentsToSubmit
|
||||
return rest
|
||||
},
|
||||
currentArgument: (context, event) => {
|
||||
if (event.type !== 'Change current argument')
|
||||
return context.currentArgument
|
||||
return Object.values(event.data)[0]
|
||||
},
|
||||
}),
|
||||
'Clear argument data': assign({
|
||||
selectedCommand: undefined,
|
||||
currentArgument: undefined,
|
||||
|
@ -63,6 +63,12 @@ export const settingsMachine = createMachine(
|
||||
],
|
||||
},
|
||||
|
||||
'set.app.streamIdleMode': {
|
||||
target: 'persisting settings',
|
||||
|
||||
actions: ['setSettingAtLevel', 'toastSuccess'],
|
||||
},
|
||||
|
||||
'set.modeling.highlightEdges': {
|
||||
target: 'persisting settings',
|
||||
|
||||
|
72
src/wasm-lib/Cargo.lock
generated
@ -169,7 +169,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -180,7 +180,7 @@ checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -191,7 +191,7 @@ checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -431,7 +431,7 @@ dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -631,7 +631,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim 0.10.0",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -642,7 +642,7 @@ checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -697,7 +697,7 @@ checksum = "4078275de501a61ceb9e759d37bdd3d7210e654dbc167ac1a3678ef4435ed57b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@ -726,7 +726,7 @@ dependencies = [
|
||||
"rustfmt-wrapper",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -737,7 +737,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -764,7 +764,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -936,7 +936,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1026,7 +1026,7 @@ dependencies = [
|
||||
"inflections",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1448,7 +1448,7 @@ dependencies = [
|
||||
"pretty_assertions",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1824,7 +1824,7 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-syntax 0.8.3",
|
||||
"structmeta",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1877,7 +1877,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2041,7 +2041,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2054,7 +2054,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-build-config",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2516,7 +2516,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_derive_internals",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2590,7 +2590,7 @@ checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2601,7 +2601,7 @@ checksum = "330f01ce65a3a5fe59a60c82f3c9a024b573b8a6e875bd233fe5f934e71d54e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2624,7 +2624,7 @@ checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2645,7 +2645,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2782,7 +2782,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2793,7 +2793,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2837,9 +2837,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.70"
|
||||
version = "2.0.71"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0209b68b3613b093e0ec905354eccaedcfe83b8cb37cbdeae64026c3064c16"
|
||||
checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2860,7 +2860,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2943,7 +2943,7 @@ checksum = "d20468752b09f49e909e55a5d338caa8bedf615594e9d80bc4c565d30faf798c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3039,7 +3039,7 @@ checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3192,7 +3192,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3220,7 +3220,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3297,7 +3297,7 @@ checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@ -3455,7 +3455,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3516,7 +3516,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -3551,7 +3551,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -3876,7 +3876,7 @@ checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.70",
|
||||
"syn 2.0.71",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -20,7 +20,7 @@ quote = "1"
|
||||
regex = "1.10"
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
serde_tokenstream = "0.2"
|
||||
syn = { version = "2.0.70", features = ["full"] }
|
||||
syn = { version = "2.0.71", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.86"
|
||||
|
@ -837,7 +837,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
||||
|
||||
// Read the output file.
|
||||
let actual = image::io::Reader::open(output_file).unwrap().decode().unwrap();
|
||||
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #test_name_str), &actual, 1.0);
|
||||
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #test_name_str), &actual, 0.99);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ mod test_examples_someFn {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ mod test_examples_someFn {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ mod test_examples_show {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ mod test_examples_show {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show1"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ mod test_examples_show {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ mod test_examples_my_func {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_my_func0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
|
||||
@ -178,7 +178,7 @@ mod test_examples_my_func {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_my_func1"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ mod test_examples_line_to {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_line_to0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
|
||||
@ -178,7 +178,7 @@ mod test_examples_line_to {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_line_to1"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ mod test_examples_min {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_min0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
|
||||
@ -176,7 +176,7 @@ mod test_examples_min {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_min1"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ mod test_examples_show {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ mod test_examples_import {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ mod test_examples_import {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ mod test_examples_import {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -86,7 +86,7 @@ mod test_examples_show {
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&actual,
|
||||
1.0,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ databake = "0.1.8"
|
||||
kcl-lib = { path = "../kcl" }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "2.0.70", features = ["full"] }
|
||||
syn = { version = "2.0.71", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.0"
|
||||
|
@ -98,6 +98,10 @@ harness = false
|
||||
name = "compiler_benchmark_iai"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "digest_benchmark"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "lsp_semantic_tokens_benchmark_criterion"
|
||||
harness = false
|
||||
|
31
src/wasm-lib/kcl/benches/digest_benchmark.rs
Normal file
@ -0,0 +1,31 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
pub fn bench_digest(c: &mut Criterion) {
|
||||
for (name, file) in [
|
||||
("pipes_on_pipes", PIPES_PROGRAM),
|
||||
("big_kitt", KITT_PROGRAM),
|
||||
("cube", CUBE_PROGRAM),
|
||||
("math", MATH_PROGRAM),
|
||||
("mike_stress_test", MIKE_STRESS_TEST_PROGRAM),
|
||||
] {
|
||||
let tokens = kcl_lib::token::lexer(file).unwrap();
|
||||
let prog = kcl_lib::parser::Parser::new(tokens).ast().unwrap();
|
||||
c.bench_function(&format!("digest_{name}"), move |b| {
|
||||
let prog = prog.clone();
|
||||
|
||||
b.iter(move || {
|
||||
let mut prog = prog.clone();
|
||||
prog.compute_digest();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_digest);
|
||||
criterion_main!(benches);
|
||||
|
||||
const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl");
|
||||
const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
|
||||
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
|
||||
const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl");
|
||||
const MIKE_STRESS_TEST_PROGRAM: &str = include_str!("../../tests/executor/inputs/mike_stress_test.kcl");
|
@ -100,18 +100,6 @@ impl ProgramMemory {
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Get all TagDeclarators and TagIdentifiers in the memory.
|
||||
pub fn get_tags(&self) -> HashMap<String, MemoryItem> {
|
||||
self.root
|
||||
.values()
|
||||
.filter_map(|item| match item {
|
||||
MemoryItem::TagDeclarator(t) => Some((t.name.to_string(), item.clone())),
|
||||
MemoryItem::TagIdentifier(t) => Some((t.value.to_string(), item.clone())),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<HashMap<String, MemoryItem>>()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ProgramMemory {
|
||||
|
@ -232,7 +232,7 @@ impl crate::lsp::backend::Backend for Backend {
|
||||
// Lets update the ast.
|
||||
let parser = crate::parser::Parser::new(tokens.clone());
|
||||
let result = parser.ast();
|
||||
let ast = match result {
|
||||
let mut ast = match result {
|
||||
Ok(ast) => ast,
|
||||
Err(err) => {
|
||||
self.add_to_diagnostics(¶ms, &[err], true).await;
|
||||
@ -243,6 +243,11 @@ impl crate::lsp::backend::Backend for Backend {
|
||||
}
|
||||
};
|
||||
|
||||
// Here we will want to store the digest and compare, but for now
|
||||
// we're doing this in a non-load-bearing capacity so we can remove
|
||||
// this if it backfires and only hork the LSP.
|
||||
ast.compute_digest();
|
||||
|
||||
// Check if the ast changed.
|
||||
let ast_changed = match self.ast_map.get(&filename) {
|
||||
Some(old_ast) => {
|
||||
|
@ -2371,9 +2371,12 @@ async fn serial_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
|
||||
})
|
||||
.await;
|
||||
|
||||
let mut default_hashed = crate::ast::types::Program::default();
|
||||
default_hashed.compute_digest();
|
||||
|
||||
// Get the ast.
|
||||
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
|
||||
assert_eq!(ast, crate::ast::types::Program::default());
|
||||
assert_eq!(ast, default_hashed);
|
||||
// Get the memory.
|
||||
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
|
||||
assert_eq!(memory, ProgramMemory::default());
|
||||
@ -2835,9 +2838,12 @@ async fn serial_test_kcl_lsp_cant_execute_set() {
|
||||
let units = server.executor_ctx().await.clone().unwrap().settings.units;
|
||||
assert_eq!(units, crate::settings::types::UnitLength::Mm);
|
||||
|
||||
let mut default_hashed = crate::ast::types::Program::default();
|
||||
default_hashed.compute_digest();
|
||||
|
||||
// Get the ast.
|
||||
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
|
||||
assert!(ast != crate::ast::types::Program::default());
|
||||
assert!(ast != default_hashed);
|
||||
// Get the memory.
|
||||
let memory = server.memory_map.get("file:///test.kcl").unwrap().clone();
|
||||
// Now it should be the default memory.
|
||||
|
@ -234,6 +234,9 @@ pub struct AppSettings {
|
||||
/// This setting only applies to the web app. And is temporary until we have Linux support.
|
||||
#[serde(default, alias = "dismissWebBanner", skip_serializing_if = "is_default")]
|
||||
pub dismiss_web_banner: bool,
|
||||
/// When the user is idle, and this is true, the stream will be torn down.
|
||||
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
|
||||
stream_idle_mode: bool,
|
||||
}
|
||||
|
||||
// TODO: When we remove backwards compatibility with the old settings file, we can remove this.
|
||||
@ -651,6 +654,7 @@ textWrapping = true
|
||||
theme_color: None,
|
||||
dismiss_web_banner: false,
|
||||
enable_ssao: None,
|
||||
stream_idle_mode: false,
|
||||
},
|
||||
modeling: ModelingSettings {
|
||||
base_unit: UnitLength::In,
|
||||
@ -710,6 +714,7 @@ includeSettings = false
|
||||
theme_color: None,
|
||||
dismiss_web_banner: false,
|
||||
enable_ssao: None,
|
||||
stream_idle_mode: false,
|
||||
},
|
||||
modeling: ModelingSettings {
|
||||
base_unit: UnitLength::Yd,
|
||||
@ -774,6 +779,7 @@ defaultProjectName = "projects-$nnn"
|
||||
theme_color: None,
|
||||
dismiss_web_banner: false,
|
||||
enable_ssao: None,
|
||||
stream_idle_mode: false,
|
||||
},
|
||||
modeling: ModelingSettings {
|
||||
base_unit: UnitLength::Yd,
|
||||
@ -850,6 +856,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
|
||||
theme_color: None,
|
||||
dismiss_web_banner: false,
|
||||
enable_ssao: None,
|
||||
stream_idle_mode: false,
|
||||
},
|
||||
modeling: ModelingSettings {
|
||||
base_unit: UnitLength::Mm,
|
||||
|
@ -123,6 +123,7 @@ includeSettings = false
|
||||
theme_color: None,
|
||||
dismiss_web_banner: false,
|
||||
enable_ssao: None,
|
||||
stream_idle_mode: false,
|
||||
},
|
||||
modeling: ModelingSettings {
|
||||
base_unit: UnitLength::Yd,
|
||||
|
1013
src/wasm-lib/kcl/src/std/args.rs
Normal file
@ -4,6 +4,10 @@ use kcl_lib::{
|
||||
settings::types::UnitLength,
|
||||
};
|
||||
|
||||
/// The minimum permissible difference between asserted twenty-twenty images.
|
||||
/// i.e. how different the current model snapshot can be from the previous saved one.
|
||||
const MIN_DIFF: f64 = 0.99;
|
||||
|
||||
// mod server;
|
||||
|
||||
async fn new_context(units: UnitLength) -> Result<ExecutorContext> {
|
||||
@ -83,35 +87,35 @@ const part002 = startSketchOn(part001, here)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_riddle_small() {
|
||||
let code = include_str!("inputs/riddle_small.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/riddle_small.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/riddle_small.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_lego() {
|
||||
let code = include_str!("inputs/lego.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/lego.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/lego.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_pipe_as_arg() {
|
||||
let code = include_str!("inputs/pipe_as_arg.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pipe_as_arg.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pipe_as_arg.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_pentagon_fillet_sugar() {
|
||||
let code = include_str!("inputs/pentagon_fillet_sugar.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Cm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pentagon_fillet_sugar.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pentagon_fillet_sugar.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -139,14 +143,14 @@ const part002 = startSketchOn(part001, "start")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_start.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_start.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_mike_stress_lines() {
|
||||
let code = include_str!("inputs/mike_stress_test.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/mike_stress_test.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/mike_stress_test.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -174,7 +178,7 @@ const part002 = startSketchOn(part001, "END")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_end.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_end.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -205,7 +209,7 @@ const part002 = startSketchOn(part001, "END")
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/sketch_on_face_end_negative_extrude.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -242,7 +246,7 @@ async fn serial_test_basic_fillet_cube_start() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_fillet_cube_start.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_fillet_cube_start.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -259,7 +263,7 @@ async fn serial_test_basic_fillet_cube_end() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_fillet_cube_end.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_fillet_cube_end.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -279,7 +283,7 @@ async fn serial_test_basic_fillet_cube_close_opposite() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/basic_fillet_cube_close_opposite.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -299,7 +303,7 @@ async fn serial_test_basic_fillet_cube_next_adjacent() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/basic_fillet_cube_next_adjacent.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -319,7 +323,7 @@ async fn serial_test_basic_fillet_cube_previous_adjacent() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/basic_fillet_cube_previous_adjacent.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -341,7 +345,7 @@ const fnBox = box(3, 6, 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/function_sketch.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/function_sketch.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -364,7 +368,7 @@ const thing = box([0,0], 3, 6, 10)"#;
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/function_sketch_with_position.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -382,7 +386,7 @@ async fn serial_test_execute_with_angled_line() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/angled_line.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/angled_line.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -408,7 +412,7 @@ const bracket = startSketchOn('XY')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/parametric.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/parametric.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -442,7 +446,7 @@ const bracket = startSketchAt([0, 0])
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/parametric_with_tan_arc.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/parametric_with_tan_arc.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -470,7 +474,7 @@ async fn serial_test_execute_i_shape() {
|
||||
let code = include_str!("inputs/i_shape.kcl");
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/i_shape.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/i_shape.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -479,7 +483,7 @@ async fn serial_test_execute_pipes_on_pipes() {
|
||||
let code = include_str!("inputs/pipes_on_pipes.kcl");
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pipes_on_pipes.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pipes_on_pipes.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -487,7 +491,7 @@ async fn serial_test_execute_cylinder() {
|
||||
let code = include_str!("inputs/cylinder.kcl");
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cylinder.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cylinder.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -495,7 +499,7 @@ async fn serial_test_execute_kittycad_svg() {
|
||||
let code = include_str!("inputs/kittycad_svg.kcl");
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/kittycad_svg.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/kittycad_svg.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -523,7 +527,7 @@ const pt2 = b2.value[0]
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/member_expression_sketch_group.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -536,7 +540,7 @@ async fn serial_test_helix_defaults() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_defaults.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_defaults.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -551,7 +555,7 @@ async fn serial_test_helix_defaults_negative_extrude() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/helix_defaults_negative_extrude.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -564,7 +568,7 @@ async fn serial_test_helix_ccw() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_ccw.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_ccw.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -576,7 +580,7 @@ async fn serial_test_helix_with_length() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_with_length.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/helix_with_length.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -591,7 +595,7 @@ async fn serial_test_dimensions_match() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/dimensions_match.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/dimensions_match.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -608,7 +612,7 @@ const body = startSketchOn('XY')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/close_arc.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/close_arc.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -634,7 +638,7 @@ let thing = box(-12, -15, 10)
|
||||
box(-20, -5, 10)"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/negative_args.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/negative_args.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -647,7 +651,7 @@ async fn serial_test_basic_tangential_arc() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/tangential_arc.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/tangential_arc.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -660,7 +664,11 @@ async fn serial_test_basic_tangential_arc_with_point() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/tangential_arc_with_point.png", &result, 0.999);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/tangential_arc_with_point.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -673,7 +681,7 @@ async fn serial_test_basic_tangential_arc_to() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/tangential_arc_to.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/tangential_arc_to.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -703,7 +711,7 @@ box(-20, -5, 10, 'xy')"#;
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/different_planes_same_drawing.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -762,7 +770,7 @@ const part004 = startSketchOn('YZ')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/lots_of_planes.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/lots_of_planes.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -779,7 +787,7 @@ async fn serial_test_holes() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/holes.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/holes.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -798,7 +806,7 @@ async fn optional_params() {
|
||||
const thing = other_circle([2, 2], 20)
|
||||
"#;
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/optional_params.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/optional_params.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -834,7 +842,7 @@ const part = roundedRectangle([0, 0], 20, 20, 4)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/rounded_with_holes.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/rounded_with_holes.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -842,7 +850,7 @@ async fn serial_test_top_level_expression() {
|
||||
let code = r#"startSketchOn('XY') |> circle([0,0], 22, %) |> extrude(14, %)"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/top_level_expression.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/top_level_expression.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -859,7 +867,7 @@ const part = startSketchOn('XY')
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_linear_basic_with_math.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -872,7 +880,7 @@ async fn serial_test_patterns_linear_basic() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -888,7 +896,7 @@ async fn serial_test_patterns_linear_basic_3d() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic_3d.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic_3d.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -903,7 +911,7 @@ async fn serial_test_patterns_linear_basic_negative_distance() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_linear_basic_negative_distance.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -919,7 +927,7 @@ async fn serial_test_patterns_linear_basic_negative_axis() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_linear_basic_negative_axis.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -941,7 +949,11 @@ const rectangle = startSketchOn('XY')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_linear_basic_holes.png", &result, 0.999);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_linear_basic_holes.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -953,7 +965,11 @@ async fn serial_test_patterns_circular_basic_2d() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_circular_basic_2d.png", &result, 0.999);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_circular_basic_2d.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -969,7 +985,11 @@ async fn serial_test_patterns_circular_basic_3d() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/patterns_circular_basic_3d.png", &result, 0.999);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_circular_basic_3d.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -988,7 +1008,7 @@ async fn serial_test_patterns_circular_3d_tilted_axis() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/patterns_circular_3d_tilted_axis.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1009,7 +1029,7 @@ async fn serial_test_import_obj_with_mtl() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.obj")"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_obj_with_mtl.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_obj_with_mtl.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1017,7 +1037,11 @@ async fn serial_test_import_obj_with_mtl_units() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.obj", {type: "obj", units: "m"})"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_obj_with_mtl_units.png", &result, 0.999);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/import_obj_with_mtl_units.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1025,7 +1049,7 @@ async fn serial_test_import_gltf_with_bin() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.gltf")"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_gltf_with_bin.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_gltf_with_bin.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1033,7 +1057,7 @@ async fn serial_test_import_gltf_embedded() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube-embedded.gltf")"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_gltf_embedded.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_gltf_embedded.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1041,7 +1065,7 @@ async fn serial_test_import_glb() {
|
||||
let code = r#"const model = import("tests/executor/inputs/cube.glb")"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_glb.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_glb.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1049,7 +1073,7 @@ async fn serial_test_import_glb_no_assign() {
|
||||
let code = r#"import("tests/executor/inputs/cube.glb")"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_glb_no_assign.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/import_glb_no_assign.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1082,7 +1106,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_mm.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_mm.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1103,7 +1127,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Cm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_cm.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_cm.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1124,7 +1148,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::M).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_m.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_m.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1145,7 +1169,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::In).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_in.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_in.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1166,7 +1190,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Ft).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_ft.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_ft.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1187,7 +1211,7 @@ const myCube = cube([0,0], 10)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Yd).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_yd.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/cube_yd.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1256,7 +1280,7 @@ const part003 = startSketchOn(part002, "end")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_of_face.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_of_face.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1301,7 +1325,7 @@ const part002 = startSketchOn(part001, "end")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_circle.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_circle.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1325,7 +1349,11 @@ const part002 = startSketchOn(part001, "end")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_circle_tagged.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/sketch_on_face_circle_tagged.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1391,7 +1419,7 @@ async fn serial_test_big_number_angle_to_match_length_x() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/big_number_angle_to_match_length_x.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1412,7 +1440,7 @@ async fn serial_test_big_number_angle_to_match_length_y() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/big_number_angle_to_match_length_y.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -1433,7 +1461,7 @@ async fn serial_test_simple_revolve() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1453,7 +1481,7 @@ async fn serial_test_simple_revolve_uppercase() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_uppercase.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_uppercase.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1473,7 +1501,7 @@ async fn serial_test_simple_revolve_negative() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_negative.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_negative.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1543,7 +1571,11 @@ async fn serial_test_simple_revolve_custom_angle() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_custom_angle.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/simple_revolve_custom_angle.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1563,7 +1595,11 @@ async fn serial_test_simple_revolve_custom_axis() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_custom_axis.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/simple_revolve_custom_axis.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1587,7 +1623,7 @@ const sketch001 = startSketchOn(box, "end")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_edge.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_edge.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1638,7 +1674,11 @@ const sketch001 = startSketchOn(box, "END")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face_circle_edge.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/revolve_on_face_circle_edge.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1660,7 +1700,7 @@ const sketch001 = startSketchOn(box, "END")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face_circle.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face_circle.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1686,7 +1726,7 @@ const sketch001 = startSketchOn(box, "end")
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/revolve_on_face.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1700,7 +1740,7 @@ async fn serial_test_basic_revolve_circle() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_revolve_circle.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/basic_revolve_circle.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1727,7 +1767,11 @@ const part002 = startSketchOn(part001, 'end')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/simple_revolve_sketch_on_edge.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/simple_revolve_sketch_on_edge.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1788,7 +1832,7 @@ const plumbus1 = make_circle(p, b,$arc_b, [0, 0], 2.5)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/plumbus_fillets.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/plumbus_fillets.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1826,7 +1870,11 @@ capScrew([0, 0.5, 0], 50, 37.5, 50, 25)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/member_expression_in_params.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/member_expression_in_params.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1944,7 +1992,7 @@ async fn serial_test_xz_plane() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/xz_plane.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/xz_plane.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1958,7 +2006,7 @@ async fn serial_test_neg_xz_plane() {
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/neg_xz_plane.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/neg_xz_plane.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -1985,7 +2033,11 @@ const pattn2 = patternLinear3d({
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/linear_pattern3d_a_pattern.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/linear_pattern3d_a_pattern.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2008,7 +2060,11 @@ const pattn2 = patternCircular3d({axis: [0,0, 1], center: [-20, -20, -20], repet
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/circular_pattern3d_a_pattern.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/circular_pattern3d_a_pattern.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2037,7 +2093,7 @@ extrude(10, sketch001)
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/array_of_sketches.png", &result, 1.0);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/array_of_sketches.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2103,7 +2159,7 @@ const sketch001 = startSketchOn(bracket, 'seg01')
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/sketch_on_face_after_fillets_referencing_face.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2140,7 +2196,11 @@ const pattn1 = patternLinear3d({
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pattern3d_array_of_extrudes.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/pattern3d_array_of_extrudes.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2191,7 +2251,7 @@ const baseExtrusion = extrude(width, sketch001)
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/fillets_referencing_other_fillets.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2243,7 +2303,7 @@ const baseExtrusion = extrude(width, sketch001)
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/chamfers_referencing_other_chamfers.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2302,7 +2362,7 @@ const pattn1 = patternLinear3d({
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/linear_pattern3d_filleted_sketch.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2333,7 +2393,7 @@ const pattn2 = patternCircular3d({axis: [0,0, 1], center: [-20, -20, -20], repet
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/circular_pattern3d_filleted_sketch.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2364,7 +2424,7 @@ const pattn2 = patternCircular3d({axis: [0,0, 1], center: [-20, -20, -20], repet
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/circular_pattern3d_chamfered_sketch.png",
|
||||
&result,
|
||||
1.0,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2429,7 +2489,11 @@ const sketch001 = startSketchOn(part001, 'chamfer1')
|
||||
"#;
|
||||
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/sketch_on_face_of_chamfer.png", &result, 1.0);
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/sketch_on_face_of_chamfer.png",
|
||||
&result,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2463,21 +2527,21 @@ let p = triangle(200)
|
||||
async fn serial_test_global_tags() {
|
||||
let code = include_str!("inputs/global-tags.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/global_tags.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/global_tags.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_pattern_vase() {
|
||||
let code = include_str!("inputs/pattern_vase.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pattern_vase.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/pattern_vase.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn serial_test_scoped_tags() {
|
||||
let code = include_str!("inputs/scoped-tags.kcl");
|
||||
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image("tests/executor/outputs/scoped_tags.png", &result, 0.999);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/scoped_tags.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@ -2487,7 +2551,7 @@ async fn serial_test_order_sketch_extrude_in_order() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/order-sketch-extrude-in-order.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2498,7 +2562,7 @@ async fn serial_test_order_sketch_extrude_out_of_order() {
|
||||
twenty_twenty::assert_image(
|
||||
"tests/executor/outputs/order-sketch-extrude-out-of-order.png",
|
||||
&result,
|
||||
0.999,
|
||||
MIN_DIFF,
|
||||
);
|
||||
}
|
||||
|
||||
@ -2506,5 +2570,5 @@ async fn serial_test_order_sketch_extrude_out_of_order() {
|
||||
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);
|
||||
twenty_twenty::assert_image("tests/executor/outputs/extrude-custom-plane.png", &result, MIN_DIFF);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 165 KiB |