Compare commits

...

7 Commits

Author SHA1 Message Date
f1a458f124 Add a trackball camera setting (#4764)
* Add a setting that does nothing

* Make the setting actually change the interaction type

* fmt

* Bump `@kittycad/lib` to get the proper camera drag interaction types

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Fix camera orientation bugs to support proper camera resetting on "camera orbit" setting change (#5031)

* Add a setting that does nothing

* Make the setting actually change the interaction type

* fmt

* fix: up vector bug fix and camera reset fix. Pushing code to cleanup after debugging

* fix: deleting debugging code

* fix: removing debugging code

* fix: removing debugging console log

* fix: removing console log debugs

* fix: adding comment, restoring code from debugging

* fix: removed lookAt when the orientation is already set from the engine.. I do not think we should be recomputing it?

* fix: this fixes the bug because I was pointing to the getter not the value

* Remove unused imports

* Fix lint for unawaited Promise

* Remove pointless change

---------

Co-authored-by: Frank Noirot <frank@kittycad.io>
Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>

* Re-run CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Re-run CI

* Add display attributes to try to fix cargo test

* Remove backwards compat test case

it's failing because I didn't add cameraOrbit to that type and I don't
want to

* Fix test value (prev user value would have been Spherical before Trackball)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
2025-02-01 20:03:04 +00:00
229433126d Feature: Implemented thumbnail.png saving and load. Projects on homepage will have images (#5133)
* feature: implemented saving thumbnail.png to have project thumbnails in the home page

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* bump

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* bump

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* bump

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* bump

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* bump

* Fix the failing test by increasing window height (related to toast covering now-larger project tiles)

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierre@zoo.dev>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
Co-authored-by: Frank Noirot <frank@zoo.dev>
2025-02-01 11:40:02 +00:00
b962b5fcb3 Feature: Release Revolve to all users, added E2E test for revolve in command bar (#5085)
* chore: implemented E2E test for revolve

* fix: revert testing code

* fix: codespell

* fix: added access via the toolbar

* fix: saving off bugging code

* fix: removing error message

* fix: cleaning up testing code

* chore: adding more e2e tests for revolve

---------

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-02-01 06:02:43 -05:00
428d125139 Make point-and-click Sweep generally available (#5159)
* Make point-and-click Sweep generally available
Fixes #5156

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Trigger CI

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Remove replace /segment/face

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Selections change will be done in separate PR #5183

* Toolbar button

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Reset snaps

* Revert screenshot

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-02-01 05:21:25 -05:00
cffeb52b4b test: Add testing the artifact graph when there's an execution error (#5154)
* Add testing the artifact graph snapshots when there's an execution error

* Update output to check artifact graph in error cases

* Rename helper function to be clearer

* Add test that has meaningful output, followed by an error
2025-01-31 18:32:30 -05:00
e0ef10e7bb Bump express from 4.21.0 to 4.21.2 for path-to-regexp fix (#5188) 2025-01-31 22:43:58 +00:00
7095ce2377 Fix the '1 face' mislabelling of selection for sweep segments (#5183)
* Fix the '1 face' mislabelling of selection for sweep segments
Fixes #5182

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Reset snapshots

* A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores)

* Fix lint

* Revert snap

* Fix chamfer and fillet test selection

* Fix other test

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-01-31 16:49:57 -05:00
108 changed files with 2245 additions and 99 deletions

View File

@ -18,6 +18,7 @@ export class ToolbarFixture {
filletButton!: Locator
chamferButton!: Locator
shellButton!: Locator
revolveButton!: Locator
offsetPlaneButton!: Locator
startSketchBtn!: Locator
lineBtn!: Locator
@ -47,6 +48,7 @@ export class ToolbarFixture {
this.filletButton = page.getByTestId('fillet3d')
this.chamferButton = page.getByTestId('chamfer3d')
this.shellButton = page.getByTestId('shell')
this.revolveButton = page.getByTestId('revolve')
this.offsetPlaneButton = page.getByTestId('plane-offset')
this.startSketchBtn = page.getByTestId('sketch')
this.lineBtn = page.getByTestId('line')

View File

@ -1183,7 +1183,7 @@ extrude001 = extrude(-12, sketch001)
currentArgKey: 'radius',
currentArgValue: '5',
headerArguments: {
Selection: '1 face',
Selection: '1 segment',
Radius: '',
},
stage: 'arguments',
@ -1192,7 +1192,7 @@ extrude001 = extrude(-12, sketch001)
await cmdBar.expectState({
commandName: 'Fillet',
headerArguments: {
Selection: '1 face',
Selection: '1 segment',
Radius: '5',
},
stage: 'review',
@ -1398,7 +1398,7 @@ extrude001 = extrude(-12, sketch001)
currentArgKey: 'length',
currentArgValue: '5',
headerArguments: {
Selection: '1 face',
Selection: '1 segment',
Length: '',
},
stage: 'arguments',
@ -1407,7 +1407,7 @@ extrude001 = extrude(-12, sketch001)
await cmdBar.expectState({
commandName: 'Chamfer',
headerArguments: {
Selection: '1 face',
Selection: '1 segment',
Length: '5',
},
stage: 'review',
@ -1851,3 +1851,171 @@ sweep001 = sweep({ path = sketch002 }, sketch001)
await page.waitForTimeout(1000)
})
})
test.describe('Revolve point and click workflows', () => {
test('Base case workflow, auto spam continue in command bar', async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `
sketch001 = startSketchOn('XZ')
|> startProfileAt([-100.0, 100.0], %)
|> angledLine([0, 200.0], %, $rectangleSegmentA001)
|> angledLine([segAng(rectangleSegmentA001) - 90, 200], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(200, sketch001)
sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
|> startProfileAt([-66.77, 84.81], %)
|> angledLine([180, 27.08], %, $rectangleSegmentA002)
|> angledLine([
segAng(rectangleSegmentA002) - 90,
27.8
], %, $rectangleSegmentB002)
|> angledLine([
segAng(rectangleSegmentA002),
-segLen(rectangleSegmentA002)
], %, $rectangleSegmentC002)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// select line of code
const codeToSelecton = `segAng(rectangleSegmentA002) - 90,`
// revolve
await page.getByText(codeToSelecton).click()
await toolbar.revolveButton.click()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve({ angle = 360, axis = 'X' }, sketch002)`
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
})
test('revolve surface around edge from an extruded solid2d', async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `
sketch001 = startSketchOn('XZ')
|> startProfileAt([-102.57, 101.72], %)
|> angledLine([0, 202.6], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
202.6
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(50, sketch001)
sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
|> circle({
center = [-11.34, 10.0],
radius = 8.69
}, %)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// select line of code
const codeToSelecton = `center = [-11.34, 10.0]`
// revolve
await page.getByText(codeToSelecton).click()
await toolbar.revolveButton.click()
await page.getByText('Edge', { exact: true }).click()
const lineCodeToSelection = `|> angledLine([0, 202.6], %, $rectangleSegmentA001)`
await page.getByText(lineCodeToSelection).click()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve({angle = 360, axis = getOppositeEdge(rectangleSegmentA001)}, sketch002) `
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
})
test('revolve sketch circle around line segment from startProfileAt sketch', async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `
sketch002 = startSketchOn('XY')
|> startProfileAt([-2.02, 1.79], %)
|> xLine(2.6, %)
sketch001 = startSketchOn('-XY')
|> startProfileAt([-0.48, 1.25], %)
|> angledLine([0, 2.38], %, $rectangleSegmentA001)
|> angledLine([segAng(rectangleSegmentA001) - 90, 2.4], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
extrude001 = extrude(5, sketch001)
sketch003 = startSketchOn(extrude001, 'START')
|> circle({
center = [-0.69, 0.56],
radius = 0.28
}, %)
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
// select line of code
const codeToSelecton = `center = [-0.69, 0.56]`
// revolve
await page.getByText(codeToSelecton).click()
await toolbar.revolveButton.click()
await page.getByText('Edge', { exact: true }).click()
const lineCodeToSelection = `|> xLine(2.6, %)`
await page.getByText(lineCodeToSelection).click()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve({ angle = 360, axis = seg01 }, sketch003)`
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
})
})

View File

@ -572,7 +572,7 @@ test(
fs.utimesSync(`${dir}/lego/main.kcl`, _1995, _1995)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await page.setBodyDimensions({ width: 1200, height: 600 })
page.on('console', console.log)

View File

@ -886,7 +886,7 @@ test.describe('Sketch tests', () => {
// 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', exact: false })
page.getByRole('button', { name: 'selection : 1 segment', exact: false })
).toBeVisible({
timeout: 10_000,
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

View File

@ -1,4 +1,4 @@
import { useEffect, useMemo, useRef } from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { useHotKeyListener } from './hooks/useHotKeyListener'
import { Stream } from './components/Stream'
import { AppHeader } from './components/AppHeader'
@ -24,6 +24,10 @@ import { UnitsMenu } from 'components/UnitsMenu'
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
import { maybeWriteToDisk } from 'lib/telemetry'
import { takeScreenshotOfVideoStreamCanvas } from 'lib/screenshot'
import { writeProjectThumbnailFile } from 'lib/desktop'
import { useRouteLoaderData } from 'react-router-dom'
import { useEngineCommands } from 'components/EngineCommands'
import { commandBarActor } from 'machines/commandBarMachine'
import { useToken } from 'machines/appMachine'
maybeWriteToDisk()
@ -55,6 +59,12 @@ export function App() {
const projectName = project?.name || null
const projectPath = project?.path || null
const [commands] = useEngineCommands()
const [capturedCanvas, setCapturedCanvas] = useState(false)
const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const lastCommandType = commands[commands.length - 1]?.type
useEffect(() => {
onProjectOpen({ name: projectName, path: projectPath }, file || null)
}, [projectName, projectPath])
@ -92,6 +102,28 @@ export function App() {
useEngineConnectionSubscriptions()
// Generate thumbnail.png when loading the app
useEffect(() => {
if (!capturedCanvas && lastCommandType === 'execution-done') {
setTimeout(() => {
const projectDirectoryWithoutEndingSlash = loaderData?.project?.path
if (!projectDirectoryWithoutEndingSlash) {
return
}
const dataUrl: string = takeScreenshotOfVideoStreamCanvas()
// zoom to fit command does not wait, wait 500ms to see if zoom to fit finishes
writeProjectThumbnailFile(dataUrl, projectDirectoryWithoutEndingSlash)
.then(() => {})
.catch((e) => {
console.error(
`Failed to generate thumbnail for ${projectDirectoryWithoutEndingSlash}`
)
console.error(e)
})
}, 500)
}
}, [lastCommandType])
return (
<div className="relative h-full flex flex-col" ref={ref}>
<AppHeader

View File

@ -29,6 +29,7 @@ import * as TWEEN from '@tweenjs/tween.js'
import { isQuaternionVertical } from './helpers'
import { reportRejection } from 'lib/trap'
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
const ORTHOGRAPHIC_CAMERA_SIZE = 20
const FRAMES_TO_ANIMATE_IN = 30
@ -406,7 +407,7 @@ export class CameraControls {
.sub(this.mouseDownPosition)
this.mouseDownPosition.copy(this.mouseNewPosition)
const interaction = this.getInteractionType(event)
let interaction = this.getInteractionType(event)
if (interaction === 'none') return
// If there's a valid interaction and the mouse is moving,
@ -753,8 +754,6 @@ export class CameraControls {
didChange = true
}
this.safeLookAtTarget(this.camera.up)
// Update the camera's matrices
this.camera.updateMatrixWorld()
if (didChange || forceUpdate) {
@ -1189,14 +1188,24 @@ export class CameraControls {
this.deferReactUpdate(this.reactCameraProperties)
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
}
getInteractionType = (event: MouseEvent) =>
_getInteractionType(
getInteractionType = (
event: MouseEvent
): CameraDragInteractionType_type | 'none' => {
const initialInteractionType = _getInteractionType(
this.interactionGuards,
event,
this.enablePan,
this.enableRotate,
this.enableZoom
)
if (
initialInteractionType === 'rotate' &&
this.engineCommandManager.settings.cameraOrbit === 'trackball'
) {
return 'rotatetrackball'
}
return initialInteractionType
}
}
// Pure function helpers

View File

@ -1,10 +1,8 @@
import { useEngineCommands } from './EngineCommands'
import { Spinner } from './Spinner'
import { CustomIcon } from './CustomIcon'
export const ModelStateIndicator = () => {
const [commands] = useEngineCommands()
const lastCommandType = commands[commands.length - 1]?.type
let className = 'w-6 h-6 '

View File

@ -119,6 +119,7 @@ export const ModelingMachineProvider = ({
cameraProjection,
highlightEdges,
showScaleGrid,
cameraOrbit,
},
},
},
@ -1154,6 +1155,7 @@ export const ModelingMachineProvider = ({
enableSSAO: enableSSAO.current,
showScaleGrid: showScaleGrid.current,
cameraProjection: cameraProjection.current,
cameraOrbit: cameraOrbit.current,
},
token
)
@ -1183,6 +1185,13 @@ export const ModelingMachineProvider = ({
editorManager.selectionRanges = modelingState.context.selectionRanges
}, [modelingState.context.selectionRanges])
// When changing camera modes reset the camera to the default orientation to correct
// the up vector otherwise the conconical orientation for the camera modes will be
// wrong
useEffect(() => {
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
}, [cameraOrbit.current])
useEffect(() => {
const onConnectionStateChanged = ({ detail }: CustomEvent) => {
// If we are in sketch mode we need to exit it.

View File

@ -2,7 +2,7 @@ import { FormEvent, useEffect, useRef, useState } from 'react'
import { PATHS } from 'lib/paths'
import { Link } from 'react-router-dom'
import { ActionButton } from '../ActionButton'
import { FILE_EXT } from 'lib/constants'
import { FILE_EXT, PROJECT_IMAGE_NAME } from 'lib/constants'
import { useHotkeys } from 'react-hotkeys-hook'
import Tooltip from '../Tooltip'
import { DeleteConfirmationDialog } from './DeleteProjectDialog'
@ -29,7 +29,7 @@ function ProjectCard({
const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
const [numberOfFiles, setNumberOfFiles] = useState(1)
const [numberOfFolders, setNumberOfFolders] = useState(0)
// const [imageUrl, setImageUrl] = useState('')
const [imageUrl, setImageUrl] = useState('')
let inputRef = useRef<HTMLInputElement>(null)
@ -53,18 +53,21 @@ function ProjectCard({
setNumberOfFolders(project.directory_count)
}
// async function setupImageUrl() {
// const projectImagePath = await join(project.file.path, PROJECT_IMAGE_NAME)
// if (await exists(projectImagePath)) {
// const imageData = await readFile(projectImagePath)
// const blob = new Blob([imageData], { type: 'image/jpg' })
// const imageUrl = URL.createObjectURL(blob)
// setImageUrl(imageUrl)
// }
// }
async function setupImageUrl() {
const projectImagePath = window.electron.path.join(
project.path,
PROJECT_IMAGE_NAME
)
if (await window.electron.exists(projectImagePath)) {
const imageData = await window.electron.readFile(projectImagePath)
const blob = new Blob([imageData], { type: 'image/png' })
const imageUrl = URL.createObjectURL(blob)
setImageUrl(imageUrl)
}
}
void getNumberOfFiles()
// void setupImageUrl()
void setupImageUrl()
}, [project.kcl_file_count, project.directory_count])
useEffect(() => {
@ -84,7 +87,7 @@ function ProjectCard({
to={`${PATHS.FILE}/${encodeURIComponent(project.default_file)}`}
className="flex flex-col flex-1 !no-underline !text-chalkboard-110 dark:!text-chalkboard-10 group-hover:!hue-rotate-0 min-h-[5em] divide-y divide-primary/40 dark:divide-chalkboard-80 group-hover:!divide-primary"
>
{/* <div className="h-36 relative overflow-hidden bg-gradient-to-b from-transparent to-primary/10 rounded-t-sm">
<div className="h-36 relative overflow-hidden bg-gradient-to-b from-transparent to-primary/10 rounded-t-sm">
{imageUrl && (
<img
src={imageUrl}
@ -92,7 +95,7 @@ function ProjectCard({
className="h-full w-full transition-transform group-hover:scale-105 object-cover"
/>
)}
</div> */}
</div>
<div className="pb-2 flex flex-col flex-grow flex-auto gap-2 rounded-b-sm">
{isEditing ? (
<ProjectCardRenameForm

View File

@ -16,14 +16,15 @@ export function useSetupEngineManager(
streamRef: React.RefObject<HTMLDivElement>,
modelingSend: ReturnType<typeof useModelingContext>['send'],
modelingContext: ReturnType<typeof useModelingContext>['context'],
settings = {
settings: SettingsViaQueryString = {
pool: null,
theme: Themes.System,
highlightEdges: true,
enableSSAO: true,
showScaleGrid: false,
cameraProjection: 'perspective',
} as SettingsViaQueryString,
cameraOrbit: 'spherical',
},
token?: string
) {
const networkContext = useNetworkContext()

View File

@ -1389,6 +1389,7 @@ export class EngineCommandManager extends EventTarget {
enableSSAO: true,
showScaleGrid: false,
cameraProjection: 'perspective',
cameraOrbit: 'spherical',
}
}
@ -1437,6 +1438,7 @@ export class EngineCommandManager extends EventTarget {
enableSSAO: true,
showScaleGrid: false,
cameraProjection: 'orthographic',
cameraOrbit: 'spherical',
},
// When passed, use a completely separate connecting code path that simply
// opens a websocket and this is a function that is called when connected.

View File

@ -308,7 +308,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
description:
'Create a 3D body by moving a sketch region along an arbitrary path.',
icon: 'sweep',
status: 'development',
needsReview: false,
args: {
target: {
@ -317,8 +316,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
required: true,
skip: true,
multiple: false,
warningMessage:
'The sweep workflow is new and under tested. Please break it and report issues.',
},
trajectory: {
inputType: 'selection',
@ -368,7 +365,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
Revolve: {
description: 'Create a 3D body by rotating a sketch region about an axis.',
icon: 'revolve',
status: 'development',
needsReview: true,
args: {
selection: {
@ -377,8 +373,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
multiple: false, // TODO: multiple selection
required: true,
skip: true,
warningMessage:
'The revolve workflow is new and under tested. Please break it and report issues.',
},
axisOrEdge: {
inputType: 'options',

View File

@ -107,7 +107,8 @@ export const revolveAxisValidator = async ({
angle: angleInDegrees,
edge_id: edgeSelection,
target: sketchSelection,
tolerance: 0.0001,
// Gotcha: Playwright will fail with larger tolerances, need to use a smaller one.
tolerance: 1e-7,
},
})
}

View File

@ -26,7 +26,7 @@ export const FILE_EXT = '.kcl'
/** Default file to open when a project is opened */
export const PROJECT_ENTRYPOINT = `main${FILE_EXT}` as const
/** Thumbnail file name */
export const PROJECT_IMAGE_NAME = `main.jpg` as const
export const PROJECT_IMAGE_NAME = `thumbnail.png` as const
/** The localStorage key for last-opened projects */
export const FILE_PERSIST_KEY = `${PROJECT_FOLDER}-last-opened` as const
/** The default name given to new kcl files in a project */

View File

@ -10,6 +10,7 @@ import {
import {
PROJECT_ENTRYPOINT,
PROJECT_FOLDER,
PROJECT_IMAGE_NAME,
PROJECT_SETTINGS_FILE_NAME,
SETTINGS_FILE_NAME,
TELEMETRY_FILE_NAME,
@ -625,3 +626,19 @@ export const getUser = async (
}
return Promise.reject(new Error('unreachable'))
}
export const writeProjectThumbnailFile = async (
dataUrl: string,
projectDirectoryPath: string
) => {
const filePath = window.electron.path.join(
projectDirectoryPath,
PROJECT_IMAGE_NAME
)
const data = atob(dataUrl.substring('data:image/png;base64,'.length))
const asArray = new Uint8Array(data.length)
for (let i = 0, len = data.length; i < len; ++i) {
asArray[i] = data.charCodeAt(i)
}
return window.electron.writeFile(filePath, asArray)
}

View File

@ -1,4 +1,4 @@
function takeScreenshotOfVideoStreamCanvas() {
export function takeScreenshotOfVideoStreamCanvas() {
const canvas = document.querySelector('[data-engine]')
const video = document.getElementById('video-stream')
if (

View File

@ -577,10 +577,9 @@ export function getSelectionTypeDisplayText(
.map(
// Hack for showing "face" instead of "extrude-wall" in command bar text
([type, count]) =>
`${count} ${type
.replace('wall', 'face')
.replace('solid2d', 'face')
.replace('segment', 'face')}${count > 1 ? 's' : ''}`
`${count} ${type.replace('wall', 'face').replace('solid2d', 'face')}${
count > 1 ? 's' : ''
}`
)
.toArray()
.join(', ')

View File

@ -20,6 +20,7 @@ import { toSync } from 'lib/utils'
import { reportRejection } from 'lib/trap'
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType'
/**
* A setting that can be set at the user or project level
@ -380,6 +381,30 @@ export function createSettings() {
})),
},
}),
/**
* What methodology to use for orbiting the camera
*/
cameraOrbit: new Setting<CameraOrbitType>({
defaultValue: 'spherical',
hideOnLevel: 'project',
description: 'What methodology to use for orbiting the camera',
validate: (v) => ['spherical', 'trackball'].includes(v),
commandConfig: {
inputType: 'options',
defaultValueFromContext: (context) =>
context.modeling.cameraOrbit.current,
options: (cmdContext, settingsContext) =>
(['spherical', 'trackball'] as const).map((v) => ({
name: v.charAt(0).toUpperCase() + v.slice(1),
value: v,
isCurrent:
settingsContext.modeling.cameraOrbit.shouldShowCurrentLabel(
cmdContext.argumentsToSubmit.level as SettingsLevel,
v
),
})),
},
}),
/**
* Whether to highlight edges of 3D objects
*/

View File

@ -4,6 +4,7 @@ import { AtLeast, PathValue, Paths } from 'lib/types'
import { CommandArgumentConfig } from 'lib/commandTypes'
import { Themes } from 'lib/theme'
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType'
export interface SettingsViaQueryString {
pool: string | null
@ -12,6 +13,7 @@ export interface SettingsViaQueryString {
enableSSAO: boolean
showScaleGrid: boolean
cameraProjection: CameraProjectionType
cameraOrbit: CameraOrbitType
}
export enum UnitSystem {

View File

@ -49,6 +49,7 @@ export function configurationToSettingsPayload(
modeling: {
defaultUnit: configuration?.settings?.modeling?.base_unit,
cameraProjection: configuration?.settings?.modeling?.camera_projection,
cameraOrbit: configuration?.settings?.modeling?.camera_orbit,
mouseControls: mouseControlsToCameraSystem(
configuration?.settings?.modeling?.mouse_controls
),

View File

@ -103,7 +103,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
data: { name: 'Revolve', groupId: 'modeling' },
}),
icon: 'revolve',
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
status: 'available',
title: 'Revolve',
hotkey: 'R',
description:
@ -124,7 +124,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
data: { name: 'Sweep', groupId: 'modeling' },
}),
icon: 'sweep',
status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
status: 'available',
title: 'Sweep',
hotkey: 'W',
description:

View File

@ -259,6 +259,9 @@ pub struct ModelingSettings {
/// The projection mode the camera should use while modeling.
#[serde(default, alias = "cameraProjection", skip_serializing_if = "is_default")]
pub camera_projection: CameraProjectionType,
/// The methodology the camera should use to orbit around the model.
#[serde(default, alias = "cameraOrbit", skip_serializing_if = "is_default")]
pub camera_orbit: CameraOrbitType,
/// The controls for how to navigate the 3D view.
#[serde(default, alias = "mouseControls", skip_serializing_if = "is_default")]
pub mouse_controls: MouseControlType,
@ -415,6 +418,21 @@ pub enum CameraProjectionType {
Orthographic,
}
/// The types of camera orbit methods.
#[derive(Debug, Default, Eq, PartialEq, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, Display, FromStr)]
#[ts(export)]
#[serde(rename_all = "snake_case")]
#[display(style = "snake_case")]
pub enum CameraOrbitType {
/// Orbit using a spherical camera movement.
#[default]
#[display("spherical")]
Spherical,
/// Orbit using a trackball camera movement.
#[display("trackball")]
Trackball,
}
/// Settings that affect the behavior of the KCL text editor.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Eq, Validate)]
#[serde(rename_all = "snake_case")]
@ -543,6 +561,8 @@ mod tests {
use pretty_assertions::assert_eq;
use validator::Validate;
use crate::settings::types::CameraOrbitType;
use super::{
AppColor, AppSettings, AppTheme, AppearanceSettings, CameraProjectionType, CommandBarSettings, Configuration,
ModelingSettings, OnboardingStatus, ProjectSettings, Settings, TextEditorSettings, UnitLength,
@ -594,6 +614,7 @@ textWrapping = true
modeling: ModelingSettings {
base_unit: UnitLength::In,
camera_projection: CameraProjectionType::Orthographic,
camera_orbit: Default::default(),
mouse_controls: Default::default(),
highlight_edges: Default::default(),
show_debug_panel: true,
@ -656,6 +677,7 @@ includeSettings = false
modeling: ModelingSettings {
base_unit: UnitLength::Yd,
camera_projection: Default::default(),
camera_orbit: Default::default(),
mouse_controls: Default::default(),
highlight_edges: Default::default(),
show_debug_panel: true,
@ -723,6 +745,7 @@ defaultProjectName = "projects-$nnn"
modeling: ModelingSettings {
base_unit: UnitLength::Yd,
camera_projection: Default::default(),
camera_orbit: CameraOrbitType::Spherical,
mouse_controls: Default::default(),
highlight_edges: Default::default(),
show_debug_panel: true,
@ -802,6 +825,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
modeling: ModelingSettings {
base_unit: UnitLength::Mm,
camera_projection: Default::default(),
camera_orbit: Default::default(),
mouse_controls: Default::default(),
highlight_edges: true.into(),
show_debug_panel: false,

View File

@ -129,6 +129,7 @@ includeSettings = false
modeling: ModelingSettings {
base_unit: UnitLength::Yd,
camera_projection: Default::default(),
camera_orbit: Default::default(),
mouse_controls: Default::default(),
highlight_edges: Default::default(),
show_debug_panel: true,

View File

@ -4,6 +4,8 @@ use insta::rounded_redaction;
use crate::{
errors::KclError,
exec::ArtifactCommand,
execution::{ArtifactGraph, Operation},
parsing::ast::types::{Node, Program},
source_range::ModuleId,
};
@ -104,36 +106,12 @@ async fn execute(test_name: &str, render_to_png: bool) {
".environments[].**[].z[]" => rounded_redaction(4),
});
});
assert_snapshot(test_name, "Operations executed", || {
insta::assert_json_snapshot!("ops", exec_state.mod_local.operations);
});
assert_snapshot(test_name, "Artifact commands", || {
insta::assert_json_snapshot!("artifact_commands", exec_state.global.artifact_commands, {
"[].command.segment.*.x" => rounded_redaction(4),
"[].command.segment.*.y" => rounded_redaction(4),
"[].command.segment.*.z" => rounded_redaction(4),
});
});
assert_snapshot(test_name, "Artifact graph flowchart", || {
let flowchart = exec_state
.global
.artifact_graph
.to_mermaid_flowchart()
.unwrap_or_else(|e| format!("Failed to convert artifact graph to flowchart: {e}"));
// Change the snapshot suffix so that it is rendered as a
// Markdown file in GitHub.
insta::assert_binary_snapshot!("artifact_graph_flowchart.md", flowchart.as_bytes().to_owned());
});
assert_snapshot(test_name, "Artifact graph mind map", || {
let mind_map = exec_state
.global
.artifact_graph
.to_mermaid_mind_map()
.unwrap_or_else(|e| format!("Failed to convert artifact graph to mind map: {e}"));
// Change the snapshot suffix so that it is rendered as a
// Markdown file in GitHub.
insta::assert_binary_snapshot!("artifact_graph_mind_map.md", mind_map.as_bytes().to_owned());
});
assert_common_snapshots(
test_name,
exec_state.mod_local.operations,
exec_state.global.artifact_commands,
exec_state.global.artifact_graph,
);
}
Err(e) => {
match e.error {
@ -153,17 +131,12 @@ async fn execute(test_name: &str, render_to_png: bool) {
insta::assert_snapshot!("execution_error", report);
});
assert_snapshot(test_name, "Operations executed", || {
insta::assert_json_snapshot!("ops", error.operations);
});
assert_snapshot(test_name, "Artifact commands", || {
insta::assert_json_snapshot!("artifact_commands", error.artifact_commands, {
"[].command.segment.*.x" => rounded_redaction(4),
"[].command.segment.*.y" => rounded_redaction(4),
"[].command.segment.*.z" => rounded_redaction(4),
});
});
assert_common_snapshots(
test_name,
error.operations,
error.artifact_commands,
error.artifact_graph,
);
}
e => {
// These kinds of errors aren't expected to occur. We don't
@ -176,6 +149,42 @@ async fn execute(test_name: &str, render_to_png: bool) {
}
}
/// Assert snapshots that should happen both when KCL execution succeeds and
/// when it results in an error.
fn assert_common_snapshots(
test_name: &str,
operations: Vec<Operation>,
artifact_commands: Vec<ArtifactCommand>,
artifact_graph: ArtifactGraph,
) {
assert_snapshot(test_name, "Operations executed", || {
insta::assert_json_snapshot!("ops", operations);
});
assert_snapshot(test_name, "Artifact commands", || {
insta::assert_json_snapshot!("artifact_commands", artifact_commands, {
"[].command.segment.*.x" => rounded_redaction(4),
"[].command.segment.*.y" => rounded_redaction(4),
"[].command.segment.*.z" => rounded_redaction(4),
});
});
assert_snapshot(test_name, "Artifact graph flowchart", || {
let flowchart = artifact_graph
.to_mermaid_flowchart()
.unwrap_or_else(|e| format!("Failed to convert artifact graph to flowchart: {e}"));
// Change the snapshot suffix so that it is rendered as a Markdown file
// in GitHub.
insta::assert_binary_snapshot!("artifact_graph_flowchart.md", flowchart.as_bytes().to_owned());
});
assert_snapshot(test_name, "Artifact graph mind map", || {
let mind_map = artifact_graph
.to_mermaid_mind_map()
.unwrap_or_else(|e| format!("Failed to convert artifact graph to mind map: {e}"));
// Change the snapshot suffix so that it is rendered as a Markdown file
// in GitHub.
insta::assert_binary_snapshot!("artifact_graph_mind_map.md", mind_map.as_bytes().to_owned());
});
}
mod cube {
const TEST_NAME: &str = "cube";
@ -197,6 +206,27 @@ mod cube {
super::execute(TEST_NAME, true).await
}
}
mod cube_with_error {
const TEST_NAME: &str = "cube_with_error";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[test]
fn unparse() {
super::unparse(TEST_NAME)
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}
mod artifact_graph_example_code1 {
const TEST_NAME: &str = "artifact_graph_example_code1";

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart argument_error.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map argument_error.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart array_elem_pop_empty_fail.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map array_elem_pop_empty_fail.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart array_elem_pop_fail.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map array_elem_pop_fail.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart array_elem_push_fail.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map array_elem_push_fail.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart array_index_oob.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map array_index_oob.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart comparisons_multiple.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map comparisons_multiple.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,538 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact commands cube_with_error.kcl
---
[
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "plane_set_color",
"plane_id": "[uuid]",
"color": {
"r": 0.7,
"g": 0.28,
"b": 0.28,
"a": 0.4
}
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "plane_set_color",
"plane_id": "[uuid]",
"color": {
"r": 0.28,
"g": 0.7,
"b": 0.28,
"a": 0.4
}
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "plane_set_color",
"plane_id": "[uuid]",
"color": {
"r": 0.28,
"g": 0.28,
"b": 0.7,
"a": 0.4
}
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": -1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 0.0,
"y": -1.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": -1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 0.0,
"z": 1.0
},
"size": 100.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "edge_lines_visible",
"hidden": false
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "set_scene_units",
"unit": "mm"
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
},
{
"cmdId": "[uuid]",
"range": [
0,
0,
0
],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
},
{
"cmdId": "[uuid]",
"range": [
177,
194,
0
],
"command": {
"type": "make_plane",
"origin": {
"x": 0.0,
"y": 0.0,
"z": 0.0
},
"x_axis": {
"x": 1.0,
"y": 0.0,
"z": 0.0
},
"y_axis": {
"x": 0.0,
"y": 1.0,
"z": 0.0
},
"size": 60.0,
"clobber": false,
"hide": true
}
},
{
"cmdId": "[uuid]",
"range": [
177,
194,
0
],
"command": {
"type": "enable_sketch_mode",
"entity_id": "[uuid]",
"ortho": false,
"animated": false,
"adjust_camera": false,
"planar_normal": {
"x": 0.0,
"y": 0.0,
"z": 1.0
}
}
},
{
"cmdId": "[uuid]",
"range": [
177,
194,
0
],
"command": {
"type": "start_path"
}
},
{
"cmdId": "[uuid]",
"range": [
177,
194,
0
],
"command": {
"type": "move_path_pen",
"path": "[uuid]",
"to": {
"x": -20.0,
"y": -20.0,
"z": 0.0
}
}
},
{
"cmdId": "[uuid]",
"range": [
202,
215,
0
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": -20.0,
"y": 20.0,
"z": 0.0
},
"relative": false
}
}
},
{
"cmdId": "[uuid]",
"range": [
223,
236,
0
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 20.0,
"y": 20.0,
"z": 0.0
},
"relative": false
}
}
},
{
"cmdId": "[uuid]",
"range": [
244,
257,
0
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": 20.0,
"y": -20.0,
"z": 0.0
},
"relative": false
}
}
},
{
"cmdId": "[uuid]",
"range": [
265,
278,
0
],
"command": {
"type": "extend_path",
"path": "[uuid]",
"segment": {
"type": "line",
"end": {
"x": -20.0,
"y": -20.0,
"z": 0.0
},
"relative": false
}
}
},
{
"cmdId": "[uuid]",
"range": [
286,
294,
0
],
"command": {
"type": "close_path",
"path_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
286,
294,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
302,
320,
0
],
"command": {
"type": "enable_sketch_mode",
"entity_id": "[uuid]",
"ortho": false,
"animated": false,
"adjust_camera": false,
"planar_normal": {
"x": 0.0,
"y": 0.0,
"z": 1.0
}
}
},
{
"cmdId": "[uuid]",
"range": [
302,
320,
0
],
"command": {
"type": "extrude",
"target": "[uuid]",
"distance": 40.0,
"faces": null
}
},
{
"cmdId": "[uuid]",
"range": [
302,
320,
0
],
"command": {
"type": "sketch_mode_disable"
}
},
{
"cmdId": "[uuid]",
"range": [
302,
320,
0
],
"command": {
"type": "object_bring_to_front",
"object_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [
302,
320,
0
],
"command": {
"type": "solid3d_get_extrusion_face_info",
"object_id": "[uuid]",
"edge_id": "[uuid]"
}
}
]

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart cube_with_error.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,38 @@
```mermaid
flowchart LR
subgraph path2 [Path]
2["Path<br>[177, 194, 0]"]
3["Segment<br>[202, 215, 0]"]
4["Segment<br>[223, 236, 0]"]
5["Segment<br>[244, 257, 0]"]
6["Segment<br>[265, 278, 0]"]
7["Segment<br>[286, 294, 0]"]
8[Solid2d]
end
1["Plane<br>[177, 194, 0]"]
9["Sweep Extrusion<br>[302, 320, 0]"]
10[Wall]
11[Wall]
12[Wall]
13[Wall]
14["Cap Start"]
15["Cap End"]
1 --- 2
2 --- 3
2 --- 4
2 --- 5
2 --- 6
2 --- 7
2 ---- 9
2 --- 8
3 --- 13
4 --- 12
5 --- 11
6 --- 10
9 --- 10
9 --- 11
9 --- 12
9 --- 13
9 --- 14
9 --- 15
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map cube_with_error.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,23 @@
```mermaid
mindmap
root
Plane
Path
Segment
Wall
Segment
Wall
Segment
Wall
Segment
Wall
Segment
Sweep Extrusion
Wall
Wall
Wall
Wall
Cap Start
Cap End
Solid2d
```

View File

@ -0,0 +1,809 @@
---
source: kcl/src/simulation_tests.rs
description: Result of parsing cube_with_error.kcl
---
{
"Ok": {
"body": [
{
"declaration": {
"end": 322,
"id": {
"end": 7,
"name": "cube",
"start": 3,
"type": "Identifier"
},
"init": {
"body": {
"body": [
{
"declaration": {
"end": 42,
"id": {
"end": 29,
"name": "l",
"start": 28,
"type": "Identifier"
},
"init": {
"end": 42,
"left": {
"end": 38,
"name": "length",
"start": 32,
"type": "Identifier",
"type": "Identifier"
},
"operator": "/",
"right": {
"end": 42,
"raw": "2",
"start": 41,
"type": "Literal",
"type": "Literal",
"value": {
"value": 2.0,
"suffix": "None"
}
},
"start": 32,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 28,
"type": "VariableDeclarator"
},
"end": 42,
"kind": "const",
"start": 28,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 58,
"id": {
"end": 46,
"name": "x",
"start": 45,
"type": "Identifier"
},
"init": {
"computed": false,
"end": 58,
"object": {
"end": 55,
"name": "center",
"start": 49,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 57,
"raw": "0",
"start": 56,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
"start": 49,
"type": "MemberExpression",
"type": "MemberExpression"
},
"start": 45,
"type": "VariableDeclarator"
},
"end": 58,
"kind": "const",
"start": 45,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 74,
"id": {
"end": 62,
"name": "y",
"start": 61,
"type": "Identifier"
},
"init": {
"computed": false,
"end": 74,
"object": {
"end": 71,
"name": "center",
"start": 65,
"type": "Identifier",
"type": "Identifier"
},
"property": {
"end": 73,
"raw": "1",
"start": 72,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
},
"start": 65,
"type": "MemberExpression",
"type": "MemberExpression"
},
"start": 61,
"type": "VariableDeclarator"
},
"end": 74,
"kind": "const",
"start": 61,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 98,
"id": {
"end": 79,
"name": "p0",
"start": 77,
"type": "Identifier"
},
"init": {
"elements": [
{
"end": 89,
"left": {
"argument": {
"end": 85,
"name": "l",
"start": 84,
"type": "Identifier",
"type": "Identifier"
},
"end": 85,
"operator": "-",
"start": 83,
"type": "UnaryExpression",
"type": "UnaryExpression"
},
"operator": "+",
"right": {
"end": 89,
"name": "x",
"start": 88,
"type": "Identifier",
"type": "Identifier"
},
"start": 83,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 97,
"left": {
"argument": {
"end": 93,
"name": "l",
"start": 92,
"type": "Identifier",
"type": "Identifier"
},
"end": 93,
"operator": "-",
"start": 91,
"type": "UnaryExpression",
"type": "UnaryExpression"
},
"operator": "+",
"right": {
"end": 97,
"name": "y",
"start": 96,
"type": "Identifier",
"type": "Identifier"
},
"start": 91,
"type": "BinaryExpression",
"type": "BinaryExpression"
}
],
"end": 98,
"start": 82,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"start": 77,
"type": "VariableDeclarator"
},
"end": 98,
"kind": "const",
"start": 77,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 121,
"id": {
"end": 103,
"name": "p1",
"start": 101,
"type": "Identifier"
},
"init": {
"elements": [
{
"end": 113,
"left": {
"argument": {
"end": 109,
"name": "l",
"start": 108,
"type": "Identifier",
"type": "Identifier"
},
"end": 109,
"operator": "-",
"start": 107,
"type": "UnaryExpression",
"type": "UnaryExpression"
},
"operator": "+",
"right": {
"end": 113,
"name": "x",
"start": 112,
"type": "Identifier",
"type": "Identifier"
},
"start": 107,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 120,
"left": {
"end": 116,
"name": "l",
"start": 115,
"type": "Identifier",
"type": "Identifier"
},
"operator": "+",
"right": {
"end": 120,
"name": "y",
"start": 119,
"type": "Identifier",
"type": "Identifier"
},
"start": 115,
"type": "BinaryExpression",
"type": "BinaryExpression"
}
],
"end": 121,
"start": 106,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"start": 101,
"type": "VariableDeclarator"
},
"end": 121,
"kind": "const",
"start": 101,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 143,
"id": {
"end": 126,
"name": "p2",
"start": 124,
"type": "Identifier"
},
"init": {
"elements": [
{
"end": 135,
"left": {
"end": 131,
"name": "l",
"start": 130,
"type": "Identifier",
"type": "Identifier"
},
"operator": "+",
"right": {
"end": 135,
"name": "x",
"start": 134,
"type": "Identifier",
"type": "Identifier"
},
"start": 130,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 142,
"left": {
"end": 138,
"name": "l",
"start": 137,
"type": "Identifier",
"type": "Identifier"
},
"operator": "+",
"right": {
"end": 142,
"name": "y",
"start": 141,
"type": "Identifier",
"type": "Identifier"
},
"start": 137,
"type": "BinaryExpression",
"type": "BinaryExpression"
}
],
"end": 143,
"start": 129,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"start": 124,
"type": "VariableDeclarator"
},
"end": 143,
"kind": "const",
"start": 124,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 166,
"id": {
"end": 148,
"name": "p3",
"start": 146,
"type": "Identifier"
},
"init": {
"elements": [
{
"end": 157,
"left": {
"end": 153,
"name": "l",
"start": 152,
"type": "Identifier",
"type": "Identifier"
},
"operator": "+",
"right": {
"end": 157,
"name": "x",
"start": 156,
"type": "Identifier",
"type": "Identifier"
},
"start": 152,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
{
"end": 165,
"left": {
"argument": {
"end": 161,
"name": "l",
"start": 160,
"type": "Identifier",
"type": "Identifier"
},
"end": 161,
"operator": "-",
"start": 159,
"type": "UnaryExpression",
"type": "UnaryExpression"
},
"operator": "+",
"right": {
"end": 165,
"name": "y",
"start": 164,
"type": "Identifier",
"type": "Identifier"
},
"start": 159,
"type": "BinaryExpression",
"type": "BinaryExpression"
}
],
"end": 166,
"start": 151,
"type": "ArrayExpression",
"type": "ArrayExpression"
},
"start": 146,
"type": "VariableDeclarator"
},
"end": 166,
"kind": "const",
"start": 146,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"argument": {
"body": [
{
"arguments": [
{
"end": 193,
"name": "p0",
"start": 191,
"type": "Identifier",
"type": "Identifier"
}
],
"callee": {
"end": 190,
"name": "startSketchAt",
"start": 177,
"type": "Identifier"
},
"end": 194,
"start": 177,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 211,
"name": "p1",
"start": 209,
"type": "Identifier",
"type": "Identifier"
},
{
"end": 214,
"start": 213,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 208,
"name": "lineTo",
"start": 202,
"type": "Identifier"
},
"end": 215,
"start": 202,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 232,
"name": "p2",
"start": 230,
"type": "Identifier",
"type": "Identifier"
},
{
"end": 235,
"start": 234,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 229,
"name": "lineTo",
"start": 223,
"type": "Identifier"
},
"end": 236,
"start": 223,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 253,
"name": "p3",
"start": 251,
"type": "Identifier",
"type": "Identifier"
},
{
"end": 256,
"start": 255,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 250,
"name": "lineTo",
"start": 244,
"type": "Identifier"
},
"end": 257,
"start": 244,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 274,
"name": "p0",
"start": 272,
"type": "Identifier",
"type": "Identifier"
},
{
"end": 277,
"start": 276,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 271,
"name": "lineTo",
"start": 265,
"type": "Identifier"
},
"end": 278,
"start": 265,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 293,
"start": 292,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 291,
"name": "close",
"start": 286,
"type": "Identifier"
},
"end": 294,
"start": 286,
"type": "CallExpression",
"type": "CallExpression"
},
{
"arguments": [
{
"end": 316,
"name": "length",
"start": 310,
"type": "Identifier",
"type": "Identifier"
},
{
"end": 319,
"start": 318,
"type": "PipeSubstitution",
"type": "PipeSubstitution"
}
],
"callee": {
"end": 309,
"name": "extrude",
"start": 302,
"type": "Identifier"
},
"end": 320,
"start": 302,
"type": "CallExpression",
"type": "CallExpression"
}
],
"end": 320,
"start": 177,
"type": "PipeExpression",
"type": "PipeExpression"
},
"end": 320,
"start": 170,
"type": "ReturnStatement",
"type": "ReturnStatement"
}
],
"end": 322,
"nonCodeMeta": {
"nonCodeNodes": {
"6": [
{
"end": 170,
"start": 166,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 24
},
"end": 322,
"params": [
{
"type": "Parameter",
"identifier": {
"end": 14,
"name": "length",
"start": 8,
"type": "Identifier"
}
},
{
"type": "Parameter",
"identifier": {
"end": 22,
"name": "center",
"start": 16,
"type": "Identifier"
}
}
],
"start": 7,
"type": "FunctionExpression",
"type": "FunctionExpression"
},
"start": 3,
"type": "VariableDeclarator"
},
"end": 322,
"kind": "fn",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"declaration": {
"end": 349,
"id": {
"end": 330,
"name": "myCube",
"start": 324,
"type": "Identifier"
},
"init": {
"arguments": [
{
"end": 340,
"raw": "40",
"start": 338,
"type": "Literal",
"type": "Literal",
"value": {
"value": 40.0,
"suffix": "None"
}
},
{
"elements": [
{
"end": 344,
"raw": "0",
"start": 343,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
},
{
"end": 347,
"raw": "0",
"start": 346,
"type": "Literal",
"type": "Literal",
"value": {
"value": 0.0,
"suffix": "None"
}
}
],
"end": 348,
"start": 342,
"type": "ArrayExpression",
"type": "ArrayExpression"
}
],
"callee": {
"end": 337,
"name": "cube",
"start": 333,
"type": "Identifier"
},
"end": 349,
"start": 333,
"type": "CallExpression",
"type": "CallExpression"
},
"start": 324,
"type": "VariableDeclarator"
},
"end": 349,
"kind": "const",
"start": 324,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"end": 398,
"expression": {
"end": 398,
"name": "foo",
"start": 395,
"type": "Identifier",
"type": "Identifier"
},
"start": 395,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"end": 399,
"nonCodeMeta": {
"nonCodeNodes": {
"0": [
{
"end": 324,
"start": 322,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
],
"1": [
{
"end": 394,
"start": 349,
"type": "NonCodeNode",
"value": {
"type": "newLineBlockComment",
"value": "Error, after creating meaningful output.",
"style": "line"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -0,0 +1,12 @@
---
source: kcl/src/simulation_tests.rs
description: Error from executing cube_with_error.kcl
---
KCL UndefinedValue error
× undefined value: memory item key `foo` is not defined
╭─[22:1]
21 │ // Error, after creating meaningful output.
22 │ foo
· ───
╰────

View File

@ -0,0 +1,22 @@
fn cube(length, center) {
l = length / 2
x = center[0]
y = center[1]
p0 = [-l + x, -l + y]
p1 = [-l + x, l + y]
p2 = [l + x, l + y]
p3 = [l + x, -l + y]
return startSketchAt(p0)
|> lineTo(p1, %)
|> lineTo(p2, %)
|> lineTo(p3, %)
|> lineTo(p0, %)
|> close(%)
|> extrude(length, %)
}
myCube = cube(40, [0, 0])
// Error, after creating meaningful output.
foo

View File

@ -0,0 +1,51 @@
---
source: kcl/src/simulation_tests.rs
description: Operations executed cube_with_error.kcl
---
[
{
"type": "UserDefinedFunctionCall",
"name": "cube",
"functionSourceRange": [
7,
322,
0
],
"unlabeledArg": null,
"labeledArgs": {},
"sourceRange": [
333,
349,
0
]
},
{
"labeledArgs": {
"length": {
"sourceRange": [
310,
316,
0
]
},
"sketch_set": {
"sourceRange": [
318,
319,
0
]
}
},
"name": "extrude",
"sourceRange": [
302,
320,
0
],
"type": "StdLibCall",
"unlabeledArg": null
},
{
"type": "UserDefinedFunctionReturn"
}
]

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart import_cycle1.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map import_cycle1.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart invalid_index_fractional.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map invalid_index_fractional.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart invalid_index_negative.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map invalid_index_negative.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart invalid_index_str.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map invalid_index_str.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart invalid_member_object.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map invalid_member_object.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart invalid_member_object_prop.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map invalid_member_object_prop.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart kw_fn_too_few_args.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map kw_fn_too_few_args.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart kw_fn_unlabeled_but_has_label.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map kw_fn_unlabeled_but_has_label.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart non_string_key_of_object.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph mind map non_string_key_of_object.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,4 @@
```mermaid
mindmap
root
```

View File

@ -0,0 +1,6 @@
---
source: kcl/src/simulation_tests.rs
description: Artifact graph flowchart object_prop_not_found.kcl
extension: md
snapshot_kind: binary
---

Some files were not shown because too many files have changed in this diff Show More