Compare commits

...

13 Commits

Author SHA1 Message Date
33822b5a19 Bump to v0.6.1 (#465) 2023-09-13 06:33:11 -04:00
a2a4daebe3 fix move cmd order (#462) 2023-09-13 07:42:42 +00:00
a17ede50bd Build endpoint for download page on website (#451) 2023-09-13 03:03:13 +00:00
2d452f80d1 ts-rs changes (#450)
* initial changes

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

* updates

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

* fix tests

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

* fixes

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

* updates

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

* bust cache

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

* add dumb shit

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

* Revert "add dumb shit"

This reverts commit 638e9cf72f75e1ad08fb6b22d2a7b143ab7e06e5.

* Revert "bust cache"

This reverts commit fd6f53ba0757d635190aa82d4b055a83755f3027.

* fix

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-13 11:10:27 +10:00
cf39c08428 Bump to v0.6.0 (#442) 2023-09-12 18:48:12 -04:00
2f25564fcc Fix PathError on Linux builds (#441)
* Path Error on Linux builds
Fixes #438

* Add fallback to homeDir
2023-09-12 18:46:35 -04:00
fd2ed8acbd Sketch on plane WIP (#309)
create default planes wip
2023-09-12 22:36:47 +00:00
5f3e1cfb6c ability to test stuff against a real executor (#448)
* ability to test stuff against a real executor

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

* add another regression test

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

* fix clippy

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

* updates

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

* figure out why hanging

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

* ficxes

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

* updates

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-12 14:59:40 -07:00
ee767afc3f Franknoirot/online indicator (#443)
* Add network health indicator to AppHeader

* Add tests for network detection responsiveness

* Format test file
2023-09-12 14:58:59 -04:00
8071eb6f8a Fix: allow the error from getUser to really be thrown (#437)
We have been catching the error thrown by the getUser fetch,
but since our state machine needs to fire onError we should
actually *not* wrap it in a try/catch statement.

Signed-off-by: Frank Noirot <frank@kittycad.io>
2023-09-12 00:37:54 +00:00
11f789e980 fixes angledLine (#436)
* fixes

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

* tests

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

* fix other tests

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-11 17:14:41 -07:00
3f82522fe9 dont set globally that we are not in a pipe (#432)
* dont set globally that we are not in a pipe

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

* updates

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

* fix 0-l case as well

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

* updates

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

* cleanup

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

* fix unary expression as well, add test

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

* fix array expressions in functions

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

* more options

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

* updates

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

* u[dates

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

* updates

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

* updates

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

* add a test

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

* updates

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

* Revert "updates"

This reverts commit 3cf06388db.

* fix tets

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-09-11 15:15:37 -07:00
c5cb0e2fd4 Add "Trackpad Friendly" camera control setting inspired by Blender (#431)
* Refactor: rename CADProgram to CameraSystem

* Fix buttonDownInStream always set to 0
This is problematic because the left mouse
button ID is actually 0. If no button is
pressed we should set back to undefined.

* Fix: middle mouse button ID is 1, not 3

* Add "Trackpad Friendly" camera system setting

Signed off by Frank Noirot <frank@kittycad.io>

* Allow camera configs to be lenient on first click
2023-09-11 16:21:23 -04:00
46 changed files with 2101 additions and 432 deletions

View File

@ -1,6 +1,6 @@
VITE_KC_API_WS_MODELING_URL=wss://api.dev.kittycad.io/ws/modeling/commands VITE_KC_API_WS_MODELING_URL=wss://api.kittycad.io/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.dev.kittycad.io VITE_KC_API_BASE_URL=https://api.kittycad.io
VITE_KC_SITE_BASE_URL=https://dev.kittycad.io VITE_KC_SITE_BASE_URL=https://kittycad.io
VITE_KC_SKIP_AUTH=false VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000 VITE_KC_CONNECTION_TIMEOUT_MS=15000
VITE_KC_SENTRY_DSN= VITE_KC_SENTRY_DSN=

View File

@ -40,6 +40,17 @@ jobs:
- name: Rust Cache - name: Rust Cache
uses: Swatinem/rust-cache@v2.6.1 uses: Swatinem/rust-cache@v2.6.1
- name: Install ffmpeg
run: |
sudo apt update
sudo apt install \
ffmpeg \
libavformat-dev \
libavutil-dev \
libclang-dev \
libswscale-dev \
--no-install-recommends
- name: Run clippy - name: Run clippy
run: | run: |
cd "${{ matrix.dir }}" cd "${{ matrix.dir }}"

View File

@ -41,6 +41,16 @@ jobs:
- uses: taiki-e/install-action@nextest - uses: taiki-e/install-action@nextest
- name: Rust Cache - name: Rust Cache
uses: Swatinem/rust-cache@v2.6.1 uses: Swatinem/rust-cache@v2.6.1
- name: Install ffmpeg
run: |
sudo apt update
sudo apt install \
ffmpeg \
libavformat-dev \
libavutil-dev \
libclang-dev \
libswscale-dev \
--no-install-recommends
- name: cargo test - name: cargo test
shell: bash shell: bash
run: |- run: |-

View File

@ -153,6 +153,8 @@ jobs:
needs: [build-test-web, build-apps] needs: [build-test-web, build-apps]
env: env:
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }} VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
PUB_DATE: ${{ github.event.release.created_at }}
NOTES: ${{ github.event.release.body }}
steps: steps:
- uses: actions/download-artifact@v3 - uses: actions/download-artifact@v3
@ -166,6 +168,8 @@ jobs:
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V} RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
jq --null-input \ jq --null-input \
--arg version "v${VERSION_NO_V}" \ --arg version "v${VERSION_NO_V}" \
--arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \
--arg darwin_sig "$DARWIN_SIG" \ --arg darwin_sig "$DARWIN_SIG" \
--arg darwin_url "$RELEASE_DIR/macos/KittyCAD%20Modeling.app.tar.gz" \ --arg darwin_url "$RELEASE_DIR/macos/KittyCAD%20Modeling.app.tar.gz" \
--arg linux_sig "$LINUX_SIG" \ --arg linux_sig "$LINUX_SIG" \
@ -174,6 +178,8 @@ jobs:
--arg windows_url "$RELEASE_DIR/nsis/KittyCAD%20Modeling_${VERSION_NO_V}_x64-setup.nsis.zip" \ --arg windows_url "$RELEASE_DIR/nsis/KittyCAD%20Modeling_${VERSION_NO_V}_x64-setup.nsis.zip" \
'{ '{
"version": $version, "version": $version,
"pub_date": $pub_date,
"notes": $notes,
"platforms": { "platforms": {
"darwin-x86_64": { "darwin-x86_64": {
"signature": $darwin_sig, "signature": $darwin_sig,
@ -195,6 +201,34 @@ jobs:
}' > last_update.json }' > last_update.json
cat last_update.json cat last_update.json
- name: Generate the download static endpoint
run: |
RELEASE_DIR=https://dl.kittycad.io/releases/modeling-app/v${VERSION_NO_V}
jq --null-input \
--arg version "v${VERSION_NO_V}" \
--arg pub_date "${PUB_DATE}" \
--arg notes "${NOTES}" \
--arg darwin_url "$RELEASE_DIR/dmg/KittyCAD%20Modeling_${VERSION_NO_V}_universal.dmg" \
--arg linux_url "$RELEASE_DIR/appimage/kittycad-modeling_${VERSION_NO_V}_amd64.AppImage" \
--arg windows_url "$RELEASE_DIR/msi/KittyCAD%20Modeling_${VERSION_NO_V}_x64_en-US.msi.zip" \
'{
"version": $version,
"pub_date": $pub_date,
"notes": $notes,
"platforms": {
"dmg-universal": {
"url": $darwin_url
},
"appimage-x86_64": {
"url": $linux_url
},
"msi-x86_64": {
"url": $windows_url
}
}
}' > last_download.json
cat last_download.json
- name: Authenticate to Google Cloud - name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v1.1.1' uses: 'google-github-actions/auth@v1.1.1'
with: with:
@ -219,6 +253,12 @@ jobs:
path: last_update.json path: last_update.json
destination: dl.kittycad.io/releases/modeling-app destination: dl.kittycad.io/releases/modeling-app
- name: Upload download endpoint to public bucket
uses: google-github-actions/upload-cloud-storage@v1.0.3
with:
path: last_download.json
destination: dl.kittycad.io/releases/modeling-app
- name: Upload release files to Github - name: Upload release files to Github
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v1
with: with:

View File

@ -1,6 +1,6 @@
{ {
"name": "untitled-app", "name": "untitled-app",
"version": "0.5.0", "version": "0.6.1",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@codemirror/autocomplete": "^6.9.0", "@codemirror/autocomplete": "^6.9.0",

View File

@ -8,7 +8,7 @@
}, },
"package": { "package": {
"productName": "kittycad-modeling", "productName": "kittycad-modeling",
"version": "0.5.0" "version": "0.6.1"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@ -73,7 +73,9 @@ export function App() {
streamDimensions, streamDimensions,
setIsExecuting, setIsExecuting,
defferedCode, defferedCode,
guiMode,
} = useStore((s) => ({ } = useStore((s) => ({
guiMode: s.guiMode,
addLog: s.addLog, addLog: s.addLog,
defferedCode: s.defferedCode, defferedCode: s.defferedCode,
setCode: s.setCode, setCode: s.setCode,
@ -181,7 +183,18 @@ export function App() {
const asyncWrap = async () => { const asyncWrap = async () => {
try { try {
if (!defferedCode) { if (!defferedCode) {
setAst(null) setAst({
start: 0,
end: 0,
body: [],
nonCodeMeta: {
noneCodeNodes: {},
start: null,
},
})
setProgramMemory({ root: {}, return: null })
engineCommandManager.endSession()
engineCommandManager.startNewSession()
return return
} }
const _ast = await asyncParser(defferedCode) const _ast = await asyncParser(defferedCode)
@ -196,26 +209,27 @@ export function App() {
{ {
root: { root: {
_0: { _0: {
type: 'userVal', type: 'UserVal',
value: 0, value: 0,
__meta: [], __meta: [],
}, },
_90: { _90: {
type: 'userVal', type: 'UserVal',
value: 90, value: 90,
__meta: [], __meta: [],
}, },
_180: { _180: {
type: 'userVal', type: 'UserVal',
value: 180, value: 180,
__meta: [], __meta: [],
}, },
_270: { _270: {
type: 'userVal', type: 'UserVal',
value: 270, value: 270,
__meta: [], __meta: [],
}, },
}, },
return: null,
}, },
engineCommandManager engineCommandManager
) )
@ -223,6 +237,9 @@ export function App() {
const { artifactMap, sourceRangeMap } = const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands() await engineCommandManager.waitForAllCommands()
setIsExecuting(false) setIsExecuting(false)
if (programMemory !== undefined) {
setProgramMemory(programMemory)
}
setArtifactMap({ artifactMap, sourceRangeMap }) setArtifactMap({ artifactMap, sourceRangeMap })
const unSubHover = engineCommandManager.subscribeToUnreliable({ const unSubHover = engineCommandManager.subscribeToUnreliable({
@ -251,9 +268,6 @@ export function App() {
}, },
}) })
unsubFn.push(unSubHover, unSubClick) unsubFn.push(unSubHover, unSubClick)
if (programMemory !== undefined) {
setProgramMemory(programMemory)
}
setError() setError()
} catch (e: any) { } catch (e: any) {
@ -287,8 +301,41 @@ export function App() {
}) })
const newCmdId = uuidv4() const newCmdId = uuidv4()
if (buttonDownInStream === undefined) {
if (buttonDownInStream) { if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any)
) {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd_id: newCmdId,
cmd: {
type: 'mouse_move',
window: { x, y },
},
})
} else {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd: {
type: 'highlight_set_entity',
selected_at_window: { x, y },
},
cmd_id: newCmdId,
})
}
} else {
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd_id: newCmdId,
cmd: {
type: 'handle_mouse_drag_move',
window: { x, y },
},
})
return
}
const interactionGuards = cameraMouseDragGuards[cameraControls] const interactionGuards = cameraMouseDragGuards[cameraControls]
let interaction: CameraDragInteractionType_type let interaction: CameraDragInteractionType_type
@ -301,8 +348,10 @@ export function App() {
} else if (interactionGuards.zoom.dragCallback(eWithButton)) { } else if (interactionGuards.zoom.dragCallback(eWithButton)) {
interaction = 'zoom' interaction = 'zoom'
} else { } else {
console.log('none')
return return
} }
debounceSocketSend({ debounceSocketSend({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd: { cmd: {
@ -312,15 +361,6 @@ export function App() {
}, },
cmd_id: newCmdId, cmd_id: newCmdId,
}) })
} else {
debounceSocketSend({
type: 'modeling_cmd_req',
cmd: {
type: 'highlight_set_entity',
selected_at_window: { x, y },
},
cmd_id: newCmdId,
})
} }
} }
@ -349,11 +389,11 @@ export function App() {
paneOpacity paneOpacity
} }
defaultSize={{ defaultSize={{
width: '400px', width: '550px',
height: 'auto', height: 'auto',
}} }}
minWidth={200} minWidth={200}
maxWidth={600} maxWidth={800}
minHeight={'auto'} minHeight={'auto'}
maxHeight={'auto'} maxHeight={'auto'}
handleClasses={{ handleClasses={{

View File

@ -1,4 +1,4 @@
import { useStore, toolTips } from './useStore' import { useStore, toolTips, Selections } from './useStore'
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst' import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
import { getNodePathFromSourceRange } from './lang/queryAst' import { getNodePathFromSourceRange } from './lang/queryAst'
import { HorzVert } from './components/Toolbar/HorzVert' import { HorzVert } from './components/Toolbar/HorzVert'
@ -15,6 +15,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons' import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
import { Popover, Transition } from '@headlessui/react' import { Popover, Transition } from '@headlessui/react'
import styles from './Toolbar.module.css' import styles from './Toolbar.module.css'
import { v4 as uuidv4 } from 'uuid'
import { useAppMode } from 'hooks/useAppMode'
export const Toolbar = () => { export const Toolbar = () => {
const { const {
@ -24,6 +26,7 @@ export const Toolbar = () => {
ast, ast,
updateAst, updateAst,
programMemory, programMemory,
engineCommandManager,
} = useStore((s) => ({ } = useStore((s) => ({
guiMode: s.guiMode, guiMode: s.guiMode,
setGuiMode: s.setGuiMode, setGuiMode: s.setGuiMode,
@ -31,7 +34,9 @@ export const Toolbar = () => {
ast: s.ast, ast: s.ast,
updateAst: s.updateAst, updateAst: s.updateAst,
programMemory: s.programMemory, programMemory: s.programMemory,
engineCommandManager: s.engineCommandManager,
})) }))
useAppMode()
useEffect(() => { useEffect(() => {
console.log('guiMode', guiMode) console.log('guiMode', guiMode)
@ -39,7 +44,7 @@ export const Toolbar = () => {
function ToolbarButtons() { function ToolbarButtons() {
return ( return (
<> <span className="overflow-x-auto">
{guiMode.mode === 'default' && ( {guiMode.mode === 'default' && (
<button <button
onClick={() => { onClick={() => {
@ -71,9 +76,18 @@ export const Toolbar = () => {
SketchOnFace SketchOnFace
</button> </button>
)} )}
{(guiMode.mode === 'canEditSketch' || false) && ( {guiMode.mode === 'canEditSketch' && (
<button <button
onClick={() => { onClick={() => {
console.log('guiMode.pathId', guiMode.pathId)
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: guiMode.pathId,
},
})
setGuiMode({ setGuiMode({
mode: 'sketch', mode: 'sketch',
sketchMode: 'sketchEdit', sketchMode: 'sketchEdit',
@ -125,14 +139,23 @@ export const Toolbar = () => {
)} )}
{guiMode.mode === 'sketch' && ( {guiMode.mode === 'sketch' && (
<button onClick={() => setGuiMode({ mode: 'default' })}> <button
onClick={() => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
setGuiMode({ mode: 'default' })
}}
>
Exit sketch Exit sketch
</button> </button>
)} )}
{toolTips {toolTips
.filter( .filter(
// (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName) // (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName)
(sketchFnName) => ['line'].includes(sketchFnName) (sketchFnName) => ['sketch_line', 'move'].includes(sketchFnName)
) )
.map((sketchFnName) => { .map((sketchFnName) => {
if ( if (
@ -143,7 +166,18 @@ export const Toolbar = () => {
return ( return (
<button <button
key={sketchFnName} key={sketchFnName}
onClick={() => onClick={() => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool:
guiMode.sketchMode === sketchFnName
? 'select'
: (sketchFnName as any),
},
})
setGuiMode({ setGuiMode({
...guiMode, ...guiMode,
...(guiMode.sketchMode === sketchFnName ...(guiMode.sketchMode === sketchFnName
@ -153,10 +187,11 @@ export const Toolbar = () => {
} }
: { : {
sketchMode: sketchFnName, sketchMode: sketchFnName,
waitingFirstClick: true,
isTooltip: true, isTooltip: true,
}), }),
}) })
} }}
> >
{sketchFnName} {sketchFnName}
{guiMode.sketchMode === sketchFnName && '✅'} {guiMode.sketchMode === sketchFnName && '✅'}
@ -180,7 +215,7 @@ export const Toolbar = () => {
<Intersect /> <Intersect />
<RemoveConstrainingValues /> <RemoveConstrainingValues />
<SetAngleBetween /> <SetAngleBetween />
</> </span>
) )
} }

View File

@ -4,6 +4,7 @@ import { ProjectWithEntryPointMetadata } from '../Router'
import ProjectSidebarMenu from './ProjectSidebarMenu' import ProjectSidebarMenu from './ProjectSidebarMenu'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import styles from './AppHeader.module.css' import styles from './AppHeader.module.css'
import { NetworkHealthIndicator } from './NetworkHealthIndicator'
interface AppHeaderProps extends React.PropsWithChildren { interface AppHeaderProps extends React.PropsWithChildren {
showToolbar?: boolean showToolbar?: boolean
@ -43,7 +44,8 @@ export const AppHeader = ({
)} )}
{/* If there are children, show them, otherwise show User menu */} {/* If there are children, show them, otherwise show User menu */}
{children || ( {children || (
<div className="ml-auto"> <div className="ml-auto flex items-center gap-1">
<NetworkHealthIndicator />
<UserSidebarMenu user={user} /> <UserSidebarMenu user={user} />
</div> </div>
)} )}

View File

@ -144,7 +144,7 @@ export function useCalc({
try { try {
const code = `const __result__ = ${value}\nshow(__result__)` const code = `const __result__ = ${value}\nshow(__result__)`
const ast = parser_wasm(code) const ast = parser_wasm(code)
const _programMem: any = { root: {} } const _programMem: any = { root: {}, return: null }
availableVarInfo.variables.forEach(({ key, value }) => { availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] } _programMem.root[key] = { type: 'userVal', value, __meta: [] }
}) })

View File

@ -29,6 +29,7 @@ describe('processMemory', () => {
const ast = parser_wasm(code) const ast = parser_wasm(code)
const programMemory = await enginelessExecutor(ast, { const programMemory = await enginelessExecutor(ast, {
root: {}, root: {},
return: null,
}) })
const output = processMemory(programMemory) const output = processMemory(programMemory)
expect(output.myVar).toEqual(5) expect(output.myVar).toEqual(5)

View File

@ -2,7 +2,7 @@ import ReactJson from 'react-json-view'
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel' import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
import { useStore } from '../useStore' import { useStore } from '../useStore'
import { useMemo } from 'react' import { useMemo } from 'react'
import { ProgramMemory } from '../lang/executor' import { ProgramMemory, Path, ExtrudeSurface } from '../lang/executor'
import { Themes } from '../lib/theme' import { Themes } from '../lib/theme'
interface MemoryPanelProps extends CollapsiblePanelProps { interface MemoryPanelProps extends CollapsiblePanelProps {
@ -49,8 +49,12 @@ export const processMemory = (programMemory: ProgramMemory) => {
Object.keys(programMemory.root).forEach((key) => { Object.keys(programMemory.root).forEach((key) => {
const val = programMemory.root[key] const val = programMemory.root[key]
if (typeof val.value !== 'function') { if (typeof val.value !== 'function') {
if (val.type === 'sketchGroup' || val.type === 'extrudeGroup') { if (val.type === 'SketchGroup') {
processedMemory[key] = val.value.map(({ __geoMeta, ...rest }) => { processedMemory[key] = val.value.map(({ __geoMeta, ...rest }: Path) => {
return rest
})
} else if (val.type === 'ExtrudeGroup') {
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
return rest return rest
}) })
} else { } else {

View File

@ -0,0 +1,51 @@
import { fireEvent, render, screen } from '@testing-library/react'
import UserSidebarMenu from './UserSidebarMenu'
import { BrowserRouter } from 'react-router-dom'
import { GlobalStateProvider } from './GlobalStateProvider'
import CommandBarProvider from './CommandBar'
import {
NETWORK_CONTENT,
NetworkHealthIndicator,
} from './NetworkHealthIndicator'
function TestWrap({ children }: { children: React.ReactNode }) {
// wrap in router and xState context
return (
<BrowserRouter>
<CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
)
}
describe('NetworkHealthIndicator tests', () => {
test('Renders the network indicator', () => {
render(
<TestWrap>
<NetworkHealthIndicator />
</TestWrap>
)
fireEvent.click(screen.getByTestId('network-toggle'))
expect(screen.getByTestId('network-good')).toHaveTextContent(
NETWORK_CONTENT.good
)
})
test('Responds to network changes', () => {
render(
<TestWrap>
<NetworkHealthIndicator />
</TestWrap>
)
fireEvent.offline(window)
fireEvent.click(screen.getByTestId('network-toggle'))
expect(screen.getByTestId('network-bad')).toHaveTextContent(
NETWORK_CONTENT.bad
)
})
})

View File

@ -0,0 +1,112 @@
import {
faCheck,
faExclamation,
faWifi,
} from '@fortawesome/free-solid-svg-icons'
import { Popover } from '@headlessui/react'
import { useEffect, useState } from 'react'
import { ActionIcon } from './ActionIcon'
export const NETWORK_CONTENT = {
good: 'Network health is good',
bad: 'Network issue',
}
const NETWORK_MESSAGES = {
offline: 'You are offline',
}
export const NetworkHealthIndicator = () => {
const [networkIssues, setNetworkIssues] = useState<string[]>([])
const hasIssues = [...networkIssues.values()].length > 0
useEffect(() => {
const offlineListener = () =>
setNetworkIssues((issues) => {
return [
...issues.filter((issue) => issue !== NETWORK_MESSAGES.offline),
NETWORK_MESSAGES.offline,
]
})
window.addEventListener('offline', offlineListener)
const onlineListener = () =>
setNetworkIssues((issues) => {
return [...issues.filter((issue) => issue !== NETWORK_MESSAGES.offline)]
})
window.addEventListener('online', onlineListener)
return () => {
window.removeEventListener('offline', offlineListener)
window.removeEventListener('online', onlineListener)
}
}, [])
return (
<Popover className="relative">
<Popover.Button
className={
'p-0 border-none relative ' +
(hasIssues
? 'focus-visible:outline-destroy-80'
: 'focus-visible:outline-succeed-80')
}
data-testid="network-toggle"
>
<span className="sr-only">Network Health</span>
<ActionIcon
icon={faWifi}
iconClassName={
hasIssues
? 'text-destroy-80 dark:text-destroy-30'
: 'text-succeed-80 dark:text-succeed-30'
}
bgClassName={
hasIssues
? 'hover:bg-destroy-10/50 hover:dark:bg-destroy-80/50 rounded'
: 'hover:bg-succeed-10/50 hover:dark:bg-succeed-80/50 rounded'
}
/>
</Popover.Button>
<Popover.Panel className="absolute right-0 left-auto top-full mt-1 w-56 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch py-2 bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm">
{!hasIssues ? (
<span
className="flex items-center justify-center gap-1 px-4"
data-testid="network-good"
>
<ActionIcon
icon={faCheck}
bgClassName={'bg-succeed-10/50 dark:bg-succeed-80/50 rounded'}
iconClassName={'text-succeed-80 dark:text-succeed-30'}
/>
{NETWORK_CONTENT.good}
</span>
) : (
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
<span
className="font-bold text-xs uppercase text-destroy-60 dark:text-destroy-50 px-4"
data-testid="network-bad"
>
{NETWORK_CONTENT.bad}
{networkIssues.length > 1 ? 's' : ''}
</span>
{networkIssues.map((issue) => (
<li
key={issue}
className="flex items-center gap-1 py-2 my-2 last:mb-0"
>
<ActionIcon
icon={faExclamation}
bgClassName={'bg-destroy-10/50 dark:bg-destroy-80/50 rounded'}
iconClassName={'text-destroy-80 dark:text-destroy-30'}
className="ml-4"
/>
<p className="flex-1 mr-4">{issue}</p>
</li>
))}
</ul>
)}
</Popover.Panel>
</Popover>
)
}

View File

@ -7,11 +7,14 @@ import {
} from 'react' } from 'react'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore' import { useStore } from '../useStore'
import { getNormalisedCoordinates } from '../lib/utils' import { getNormalisedCoordinates, roundOff } from '../lib/utils'
import Loading from './Loading' import Loading from './Loading'
import { cameraMouseDragGuards } from 'lib/cameraControls' import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models' import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
import { Models } from '@kittycad/lib'
import { addStartSketch } from 'lang/modifyAst'
import { addNewSketchLn } from 'lang/std/sketch'
export const Stream = ({ className = '' }) => { export const Stream = ({ className = '' }) => {
const [isLoading, setIsLoading] = useState(true) const [isLoading, setIsLoading] = useState(true)
@ -25,6 +28,11 @@ export const Stream = ({ className = '' }) => {
setDidDragInStream, setDidDragInStream,
streamDimensions, streamDimensions,
isExecuting, isExecuting,
guiMode,
ast,
updateAst,
setGuiMode,
programMemory,
} = useStore((s) => ({ } = useStore((s) => ({
mediaStream: s.mediaStream, mediaStream: s.mediaStream,
engineCommandManager: s.engineCommandManager, engineCommandManager: s.engineCommandManager,
@ -34,6 +42,11 @@ export const Stream = ({ className = '' }) => {
setDidDragInStream: s.setDidDragInStream, setDidDragInStream: s.setDidDragInStream,
streamDimensions: s.streamDimensions, streamDimensions: s.streamDimensions,
isExecuting: s.isExecuting, isExecuting: s.isExecuting,
guiMode: s.guiMode,
ast: s.ast,
updateAst: s.updateAst,
setGuiMode: s.setGuiMode,
programMemory: s.programMemory,
})) }))
const { const {
settings: { settings: {
@ -64,27 +77,50 @@ export const Stream = ({ className = '' }) => {
const newId = uuidv4() const newId = uuidv4()
const interactionGuards = cameraMouseDragGuards[cameraControls] const interactionGuards = cameraMouseDragGuards[cameraControls]
let interaction: CameraDragInteractionType_type let interaction: CameraDragInteractionType_type = 'rotate'
if (interactionGuards.pan.callback(e)) { if (
interactionGuards.pan.callback(e) ||
interactionGuards.pan.lenientDragStartButton === e.button
) {
interaction = 'pan' interaction = 'pan'
} else if (interactionGuards.rotate.callback(e)) { } else if (
interactionGuards.rotate.callback(e) ||
interactionGuards.rotate.lenientDragStartButton === e.button
) {
interaction = 'rotate' interaction = 'rotate'
} else if (interactionGuards.zoom.dragCallback(e)) { } else if (
interactionGuards.zoom.dragCallback(e) ||
interactionGuards.zoom.lenientDragStartButton === e.button
) {
interaction = 'zoom' interaction = 'zoom'
} else {
return
} }
engineCommandManager?.sendSceneCommand({ if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
type: 'modeling_cmd_req', engineCommandManager?.sendSceneCommand({
cmd: { type: 'modeling_cmd_req',
type: 'camera_drag_start', cmd: {
interaction, type: 'handle_mouse_drag_start',
window: { x, y }, window: { x, y },
}, },
cmd_id: newId, cmd_id: newId,
}) })
} else if (
!(
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any)
)
) {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_start',
interaction,
window: { x, y },
},
cmd_id: newId,
})
}
setButtonDownInStream(e.button) setButtonDownInStream(e.button)
setClickCoords({ x, y }) setClickCoords({ x, y })
@ -93,7 +129,6 @@ export const Stream = ({ className = '' }) => {
const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => { const handleScroll: WheelEventHandler<HTMLVideoElement> = (e) => {
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
e.preventDefault()
engineCommandManager?.sendSceneCommand({ engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd: { cmd: {
@ -110,6 +145,7 @@ export const Stream = ({ className = '' }) => {
ctrlKey, ctrlKey,
}) => { }) => {
if (!videoRef.current) return if (!videoRef.current) return
setButtonDownInStream(undefined)
const { x, y } = getNormalisedCoordinates({ const { x, y } = getNormalisedCoordinates({
clientX, clientX,
clientY, clientY,
@ -120,7 +156,7 @@ export const Stream = ({ className = '' }) => {
const newCmdId = uuidv4() const newCmdId = uuidv4()
const interaction = ctrlKey ? 'pan' : 'rotate' const interaction = ctrlKey ? 'pan' : 'rotate'
engineCommandManager?.sendSceneCommand({ const command: Models['WebSocketRequest_type'] = {
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
cmd: { cmd: {
type: 'camera_drag_end', type: 'camera_drag_end',
@ -128,9 +164,8 @@ export const Stream = ({ className = '' }) => {
window: { x, y }, window: { x, y },
}, },
cmd_id: newCmdId, cmd_id: newCmdId,
}) }
setButtonDownInStream(0)
if (!didDragInStream) { if (!didDragInStream) {
engineCommandManager?.sendSceneCommand({ engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req', type: 'modeling_cmd_req',
@ -142,6 +177,95 @@ export const Stream = ({ className = '' }) => {
cmd_id: uuidv4(), cmd_id: uuidv4(),
}) })
} }
if (!didDragInStream && guiMode.mode === 'default') {
command.cmd = {
type: 'select_with_point',
selection_type: 'add',
selected_at_window: { x, y },
}
} else if (
(!didDragInStream &&
guiMode.mode === 'sketch' &&
['move', 'select'].includes(guiMode.sketchMode)) ||
(guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any))
) {
command.cmd = {
type: 'mouse_click',
window: { x, y },
}
} else if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('move' as any)
) {
command.cmd = {
type: 'handle_mouse_drag_end',
window: { x, y },
}
}
engineCommandManager?.sendSceneCommand(command).then(async ({ data }) => {
if (command.cmd.type !== 'mouse_click' || !ast) return
if (
!(
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any as 'line')
)
)
return
if (data?.data?.entities_modified?.length && guiMode.waitingFirstClick) {
const curve = await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: data?.data?.entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
curve.data.data.control_points
const _addStartSketch = addStartSketch(
ast,
[roundOff(coords[0].x), roundOff(coords[0].y)],
[
roundOff(coords[1].x - coords[0].x),
roundOff(coords[1].y - coords[0].y),
]
)
const _modifiedAst = _addStartSketch.modifiedAst
const _pathToNode = _addStartSketch.pathToNode
setGuiMode({
...guiMode,
pathToNode: _pathToNode,
waitingFirstClick: false,
})
updateAst(_modifiedAst)
} else if (
data?.data?.entities_modified?.length &&
!guiMode.waitingFirstClick
) {
const curve = await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'curve_get_control_points',
curve_id: data?.data?.entities_modified[0],
},
})
const coords: { x: number; y: number }[] =
curve.data.data.control_points
const _modifiedAst = addNewSketchLn({
node: ast,
programMemory,
to: [coords[1].x, coords[1].y],
fnName: 'line',
pathToNode: guiMode.pathToNode,
}).modifiedAst
updateAst(_modifiedAst)
}
})
setDidDragInStream(false) setDidDragInStream(false)
setClickCoords(undefined) setClickCoords(undefined)
} }

View File

@ -62,7 +62,6 @@ export const TextEditor = ({
sourceRangeMap, sourceRangeMap,
} = useStore((s) => ({ } = useStore((s) => ({
code: s.code, code: s.code,
defferedCode: s.defferedCode,
defferedSetCode: s.defferedSetCode, defferedSetCode: s.defferedSetCode,
editorView: s.editorView, editorView: s.editorView,
engineCommandManager: s.engineCommandManager, engineCommandManager: s.engineCommandManager,
@ -70,7 +69,6 @@ export const TextEditor = ({
isLSPServerReady: s.isLSPServerReady, isLSPServerReady: s.isLSPServerReady,
selectionRanges: s.selectionRanges, selectionRanges: s.selectionRanges,
selectionRangeTypeMap: s.selectionRangeTypeMap, selectionRangeTypeMap: s.selectionRangeTypeMap,
setCode: s.setCode,
setEditorView: s.setEditorView, setEditorView: s.setEditorView,
setIsLSPServerReady: s.setIsLSPServerReady, setIsLSPServerReady: s.setIsLSPServerReady,
setSelectionRanges: s.setSelectionRanges, setSelectionRanges: s.setSelectionRanges,

243
src/hooks/useAppMode.ts Normal file
View File

@ -0,0 +1,243 @@
// needed somewhere to dump this logic,
// Once we have xState this should be removed
import { useStore, Selections } from 'useStore'
import { useEffect, useState } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { ArtifactMap, EngineCommandManager } from 'lang/std/engineConnection'
import { Models } from '@kittycad/lib/dist/types/src'
import { isReducedMotion } from 'lang/util'
import { isOverlap } from 'lib/utils'
interface DefaultPlanes {
xy: string
yz: string
xz: string
}
export function useAppMode() {
const {
guiMode,
setGuiMode,
selectionRanges,
engineCommandManager,
selectionRangeTypeMap,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
selectionRanges: s.selectionRanges,
engineCommandManager: s.engineCommandManager,
selectionRangeTypeMap: s.selectionRangeTypeMap,
}))
const [defaultPlanes, setDefaultPlanes] = useState<DefaultPlanes | null>(null)
useEffect(() => {
if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === 'selectFace' &&
engineCommandManager
) {
if (!defaultPlanes) {
const xy = createPlane(engineCommandManager, {
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 1, z: 0 },
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
})
const yz = createPlane(engineCommandManager, {
x_axis: { x: 0, y: 1, z: 0 },
y_axis: { x: 0, y: 0, z: 1 },
color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
})
const xz = createPlane(engineCommandManager, {
x_axis: { x: 1, y: 0, z: 0 },
y_axis: { x: 0, y: 0, z: 1 },
color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
})
setDefaultPlanes({ xy, yz, xz })
} else {
hideDefaultPlanes(engineCommandManager, defaultPlanes)
}
}
if (guiMode.mode !== 'sketch' && defaultPlanes) {
Object.values(defaultPlanes).forEach((planeId) => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'object_visible',
object_id: planeId,
hidden: true,
},
})
})
} else if (guiMode.mode === 'default') {
const pathId =
engineCommandManager &&
isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
if (pathId) {
setGuiMode({
mode: 'canEditSketch',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [],
pathId,
})
}
} else if (guiMode.mode === 'canEditSketch') {
if (
!engineCommandManager ||
!isCursorInSketchCommandRange(
engineCommandManager.artifactMap,
selectionRanges
)
) {
setGuiMode({
mode: 'default',
})
}
}
}, [
guiMode,
guiMode.mode,
engineCommandManager,
selectionRanges,
selectionRangeTypeMap,
])
useEffect(() => {
const unSub = engineCommandManager?.subscribeTo({
event: 'select_with_point',
callback: async ({ data }) => {
if (!data.entity_id) return
if (!defaultPlanes) return
if (!Object.values(defaultPlanes || {}).includes(data.entity_id)) {
// user clicked something else in the scene
return
}
const sketchModeResponse = await engineCommandManager?.sendSceneCommand(
{
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: data.entity_id,
ortho: true,
animated: !isReducedMotion(),
},
}
)
hideDefaultPlanes(engineCommandManager, defaultPlanes)
const sketchUuid = uuidv4()
const proms: any[] = []
proms.push(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: sketchUuid,
cmd: {
type: 'start_path',
},
})
)
proms.push(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: sketchUuid,
},
})
)
const res = await Promise.all(proms)
console.log('res', res)
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [],
})
console.log('sketchModeResponse', sketchModeResponse)
},
})
return unSub
}, [engineCommandManager, defaultPlanes])
}
function createPlane(
engineCommandManager: EngineCommandManager,
{
x_axis,
y_axis,
color,
}: {
x_axis: Models['Point3d_type']
y_axis: Models['Point3d_type']
color: Models['Color_type']
}
) {
const planeId = uuidv4()
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'make_plane',
size: 60,
origin: { x: 0, y: 0, z: 0 },
x_axis,
y_axis,
clobber: false,
},
cmd_id: planeId,
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'plane_set_color',
plane_id: planeId,
color,
},
cmd_id: uuidv4(),
})
return planeId
}
function hideDefaultPlanes(
engineCommandManager: EngineCommandManager,
defaultPlanes: DefaultPlanes
) {
Object.values(defaultPlanes).forEach((planeId) => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'object_visible',
object_id: planeId,
hidden: true,
},
})
})
}
function isCursorInSketchCommandRange(
artifactMap: ArtifactMap,
selectionRanges: Selections
): string | false {
const overlapingEntries = Object.entries(artifactMap || {}).filter(
([id, artifact]) =>
selectionRanges.codeBasedSelections.some(
(selection) =>
Array.isArray(selection.range) &&
Array.isArray(artifact.range) &&
isOverlap(selection.range, artifact.range) &&
(artifact.commandType === 'start_path' ||
artifact.commandType === 'extend_path' ||
'close_path')
)
)
return overlapingEntries.length === 1 && overlapingEntries[0][1].parentId
? overlapingEntries[0][1].parentId
: false
}

View File

@ -21,7 +21,7 @@ show(mySketch001)`
) )
expect(shown).toEqual([ expect(shown).toEqual([
{ {
type: 'sketchGroup', type: 'SketchGroup',
start: { start: {
to: [0, 0], to: [0, 0],
from: [0, 0], from: [0, 0],
@ -77,7 +77,7 @@ show(mySketch001)`
) )
expect(shown).toEqual([ expect(shown).toEqual([
{ {
type: 'extrudeGroup', type: 'ExtrudeGroup',
id: expect.any(String), id: expect.any(String),
value: [], value: [],
height: 2, height: 2,
@ -117,7 +117,7 @@ show(theExtrude, sk2)`
) )
expect(geos).toEqual([ expect(geos).toEqual([
{ {
type: 'extrudeGroup', type: 'ExtrudeGroup',
id: expect.any(String), id: expect.any(String),
value: [], value: [],
height: 2, height: 2,
@ -126,7 +126,7 @@ show(theExtrude, sk2)`
__meta: [{ sourceRange: [13, 34] }], __meta: [{ sourceRange: [13, 34] }],
}, },
{ {
type: 'extrudeGroup', type: 'ExtrudeGroup',
id: expect.any(String), id: expect.any(String),
value: [], value: [],
height: 2, height: 2,

View File

@ -1,7 +1,7 @@
import fs from 'node:fs' import fs from 'node:fs'
import { parser_wasm } from './abstractSyntaxTree' import { parser_wasm } from './abstractSyntaxTree'
import { ProgramMemory } from './executor' import { ProgramMemory, SketchGroup } from './executor'
import { initPromise } from './rust' import { initPromise } from './rust'
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
import { vi } from 'vitest' import { vi } from 'vitest'
@ -117,10 +117,10 @@ show(mySketch)
// ].join('\n') // ].join('\n')
// const { root } = await exe(code) // const { root } = await exe(code)
// expect(root.mySk1.value).toHaveLength(3) // expect(root.mySk1.value).toHaveLength(3)
// expect(root?.rotated?.type).toBe('sketchGroup') // expect(root?.rotated?.type).toBe('SketchGroup')
// if ( // if (
// root?.mySk1?.type !== 'sketchGroup' || // root?.mySk1?.type !== 'SketchGroup' ||
// root?.rotated?.type !== 'sketchGroup' // root?.rotated?.type !== 'SketchGroup'
// ) // )
// throw new Error('not a sketch group') // throw new Error('not a sketch group')
// expect(root.mySk1.rotation).toEqual([0, 0, 0, 1]) // expect(root.mySk1.rotation).toEqual([0, 0, 0, 1])
@ -143,7 +143,7 @@ show(mySketch)
].join('\n') ].join('\n')
const { root } = await exe(code) const { root } = await exe(code)
expect(root.mySk1).toEqual({ expect(root.mySk1).toEqual({
type: 'sketchGroup', type: 'SketchGroup',
start: { start: {
to: [0, 0], to: [0, 0],
from: [0, 0], from: [0, 0],
@ -199,7 +199,7 @@ show(mySketch)
// TODO path to node is probably wrong here, zero indexes are not correct // TODO path to node is probably wrong here, zero indexes are not correct
expect(root).toEqual({ expect(root).toEqual({
three: { three: {
type: 'userVal', type: 'UserVal',
value: 3, value: 3,
__meta: [ __meta: [
{ {
@ -208,7 +208,7 @@ show(mySketch)
], ],
}, },
yo: { yo: {
type: 'userVal', type: 'UserVal',
value: [1, '2', 3, 9], value: [1, '2', 3, 9],
__meta: [ __meta: [
{ {
@ -225,7 +225,7 @@ show(mySketch)
].join('\n') ].join('\n')
const { root } = await exe(code) const { root } = await exe(code)
expect(root.yo).toEqual({ expect(root.yo).toEqual({
type: 'userVal', type: 'UserVal',
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 }, value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
__meta: [ __meta: [
{ {
@ -240,7 +240,7 @@ show(mySketch)
) )
const { root } = await exe(code) const { root } = await exe(code)
expect(root.myVar).toEqual({ expect(root.myVar).toEqual({
type: 'userVal', type: 'UserVal',
value: '123', value: '123',
__meta: [ __meta: [
{ {
@ -338,7 +338,7 @@ describe('testing math operators', () => {
const { root } = await exe(code) const { root } = await exe(code)
const sketch = root.part001 const sketch = root.part001
// result of `-legLen(5, min(3, 999))` should be -4 // result of `-legLen(5, min(3, 999))` should be -4
const yVal = sketch.value?.[0]?.to?.[1] const yVal = (sketch as SketchGroup).value?.[0]?.to?.[1]
expect(yVal).toBe(-4) expect(yVal).toBe(-4)
}) })
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => { it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
@ -356,8 +356,8 @@ describe('testing math operators', () => {
const { root } = await exe(code) const { root } = await exe(code)
const sketch = root.part001 const sketch = root.part001
// expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0 // expect -legLen(segLen('seg01', %), myVar) to equal -4 setting the y value back to 0
expect(sketch.value?.[1]?.from).toEqual([3, 4]) expect((sketch as SketchGroup).value?.[1]?.from).toEqual([3, 4])
expect(sketch.value?.[1]?.to).toEqual([6, 0]) expect((sketch as SketchGroup).value?.[1]?.to).toEqual([6, 0])
const removedUnaryExp = code.replace( const removedUnaryExp = code.replace(
`-legLen(segLen('seg01', %), myVar)`, `-legLen(segLen('seg01', %), myVar)`,
`legLen(segLen('seg01', %), myVar)` `legLen(segLen('seg01', %), myVar)`
@ -366,7 +366,9 @@ describe('testing math operators', () => {
const removedUnaryExpRootSketch = removedUnaryExpRoot.part001 const removedUnaryExpRootSketch = removedUnaryExpRoot.part001
// without the minus sign, the y value should be 8 // without the minus sign, the y value should be 8
expect(removedUnaryExpRootSketch.value?.[1]?.to).toEqual([6, 8]) expect((removedUnaryExpRootSketch as SketchGroup).value?.[1]?.to).toEqual([
6, 8,
])
}) })
it('with nested callExpression and binaryExpression', async () => { it('with nested callExpression and binaryExpression', async () => {
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))' const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
@ -397,7 +399,10 @@ show(theExtrude)`
// helpers // helpers
async function exe(code: string, programMemory: ProgramMemory = { root: {} }) { async function exe(
code: string,
programMemory: ProgramMemory = { root: {}, return: null }
) {
const ast = parser_wasm(code) const ast = parser_wasm(code)
const result = await enginelessExecutor(ast, programMemory) const result = await enginelessExecutor(ast, programMemory)

View File

@ -5,96 +5,21 @@ import {
SourceRangeMap, SourceRangeMap,
} from './std/engineConnection' } from './std/engineConnection'
import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn' import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
import { execute_wasm } from '../wasm-lib/pkg/wasm_lib' import { execute_wasm } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors' import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { rangeTypeFix } from './abstractSyntaxTree' import { rangeTypeFix } from './abstractSyntaxTree'
export type SourceRange = [number, number] export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
export type PathToNode = [string | number, string][] // [pathKey, nodeType][] export type { Position } from '../wasm-lib/kcl/bindings/Position'
export type Metadata = { export type { Rotation } from '../wasm-lib/kcl/bindings/Rotation'
sourceRange: SourceRange export type { Path } from '../wasm-lib/kcl/bindings/Path'
} export type { SketchGroup } from '../wasm-lib/kcl/bindings/SketchGroup'
export type Position = [number, number, number] export type { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
export type Rotation = [number, number, number, number] export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
interface BasePath { export type PathToNode = [string | number, string][]
from: [number, number]
to: [number, number]
name?: string
__geoMeta: {
id: string
sourceRange: SourceRange
}
}
export interface ToPoint extends BasePath {
type: 'toPoint'
}
export interface Base extends BasePath {
type: 'base'
}
export interface HorizontalLineTo extends BasePath {
type: 'horizontalLineTo'
x: number
}
export interface AngledLineTo extends BasePath {
type: 'angledLineTo'
angle: number
x?: number
y?: number
}
interface GeoMeta {
__geoMeta: {
id: string
sourceRange: SourceRange
}
}
export type Path = ToPoint | HorizontalLineTo | AngledLineTo | Base
export interface SketchGroup {
type: 'sketchGroup'
id: string
value: Path[]
start?: Base
position: Position
rotation: Rotation
__meta: Metadata[]
}
interface ExtrudePlane {
type: 'extrudePlane'
position: Position
rotation: Rotation
name?: string
}
export type ExtrudeSurface = GeoMeta &
ExtrudePlane /* | ExtrudeRadius | ExtrudeSpline */
export interface ExtrudeGroup {
type: 'extrudeGroup'
id: string
value: ExtrudeSurface[]
height: number
position: Position
rotation: Rotation
__meta: Metadata[]
}
/** UserVal not produced by one of our internal functions */
export interface UserVal {
type: 'userVal'
value: any
__meta: Metadata[]
}
type MemoryItem = UserVal | SketchGroup | ExtrudeGroup
interface Memory { interface Memory {
[key: string]: MemoryItem [key: string]: MemoryItem
@ -102,12 +27,12 @@ interface Memory {
export interface ProgramMemory { export interface ProgramMemory {
root: Memory root: Memory
return?: ProgramReturn return: ProgramReturn | null
} }
export const executor = async ( export const executor = async (
node: Program, node: Program,
programMemory: ProgramMemory = { root: {} }, programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,
// work around while the gemotry is still be stored on the frontend // work around while the gemotry is still be stored on the frontend
// will be removed when the stream UI is added. // will be removed when the stream UI is added.
@ -132,7 +57,7 @@ export const executor = async (
export const _executor = async ( export const _executor = async (
node: Program, node: Program,
programMemory: ProgramMemory = { root: {} }, programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
): Promise<ProgramMemory> => { ): Promise<ProgramMemory> => {
try { try {

View File

@ -114,7 +114,8 @@ describe('Testing addSketchTo', () => {
expect(str).toBe(`const part001 = startSketchAt('default') expect(str).toBe(`const part001 = startSketchAt('default')
|> ry(90, %) |> ry(90, %)
|> line('default', %) |> line('default', %)
show(part001)`) show(part001)
`)
}) })
}) })
@ -179,7 +180,10 @@ describe('Testing moveValueIntoNewVariable', () => {
return x return x
} }
` `
const code = `${fn('def')}${fn('ghi')}${fn('jkl')}${fn('hmm')} const code = `${fn('def')}${fn('jkl')}${fn('hmm')}
fn ghi = (x) => {
return 2
}
const abc = 3 const abc = 3
const identifierGuy = 5 const identifierGuy = 5
const yo = 5 + 6 const yo = 5 + 6

View File

@ -28,6 +28,46 @@ import {
createFirstArg, createFirstArg,
} from './std/sketch' } from './std/sketch'
export function addStartSketch(
node: Program,
start: [number, number],
end: [number, number]
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
const _node = { ...node }
const _name = findUniqueName(node, 'part')
const startSketchAt = createCallExpression('startSketchAt', [
createArrayExpression([createLiteral(start[0]), createLiteral(start[1])]),
])
const initialLineTo = createCallExpression('line', [
createArrayExpression([createLiteral(end[0]), createLiteral(end[1])]),
createPipeSubstitution(),
])
const pipeBody = [startSketchAt, initialLineTo]
const variableDeclaration = createVariableDeclaration(
_name,
createPipeExpression(pipeBody)
)
_node.body = [...node.body, variableDeclaration]
let pathToNode: PathToNode = [
['body', ''],
['0', 'index'],
['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'],
]
return {
modifiedAst: _node,
id: _name,
pathToNode,
}
}
export function addSketchTo( export function addSketchTo(
node: Program, node: Program,
axis: 'xy' | 'xz' | 'yz', axis: 'xy' | 'xz' | 'yz',

View File

@ -11,26 +11,27 @@ describe('recast', () => {
const code = '1 + 2' const code = '1 + 2'
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted.trim()).toBe(code)
}) })
it('variable declaration', () => { it('variable declaration', () => {
const code = 'const myVar = 5' const code = 'const myVar = 5'
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted.trim()).toBe(code)
}) })
it("variable declaration that's binary with string", () => { it("variable declaration that's binary with string", () => {
const code = "const myVar = 5 + 'yo'" const code = "const myVar = 5 + 'yo'"
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted.trim()).toBe(code)
const codeWithOtherQuotes = 'const myVar = 5 + "yo"' const codeWithOtherQuotes = 'const myVar = 5 + "yo"'
const { ast: ast2 } = code2ast(codeWithOtherQuotes) const { ast: ast2 } = code2ast(codeWithOtherQuotes)
expect(recast(ast2)).toBe(codeWithOtherQuotes) expect(recast(ast2).trim()).toBe(codeWithOtherQuotes)
}) })
it('test assigning two variables, the second summing with the first', () => { it('test assigning two variables, the second summing with the first', () => {
const code = `const myVar = 5 const code = `const myVar = 5
const newVar = myVar + 1` const newVar = myVar + 1
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted).toBe(code)
@ -42,11 +43,12 @@ const newVar = myVar + 1`
) )
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted.trim()).toBe(code.trim())
}) })
it('test with function call', () => { it('test with function call', () => {
const code = `const myVar = "hello" const code = `const myVar = "hello"
log(5, myVar)` log(5, myVar)
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted).toBe(code)
@ -61,7 +63,7 @@ log(5, myVar)`
].join('\n') ].join('\n')
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted.trim()).toBe(code)
}) })
it('recast sketch declaration', () => { it('recast sketch declaration', () => {
let code = `const mySketch = startSketchAt([0, 0]) let code = `const mySketch = startSketchAt([0, 0])
@ -70,10 +72,11 @@ log(5, myVar)`
|> lineTo({ to: [1, 0], tag: "rightPath" }, %) |> lineTo({ to: [1, 0], tag: "rightPath" }, %)
|> close(%) |> close(%)
show(mySketch)` show(mySketch)
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted).toBe(code)
}) })
it('sketch piped into callExpression', () => { it('sketch piped into callExpression', () => {
const code = [ const code = [
@ -85,7 +88,7 @@ show(mySketch)`
].join('\n') ].join('\n')
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted.trim()).toBe(code.trim())
}) })
it('recast BinaryExpression piped into CallExpression', () => { it('recast BinaryExpression piped into CallExpression', () => {
const code = [ const code = [
@ -97,37 +100,37 @@ show(mySketch)`
].join('\n') ].join('\n')
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted.trim()).toBe(code)
}) })
it('recast nested binary expression', () => { it('recast nested binary expression', () => {
const code = ['const myVar = 1 + 2 * 5'].join('\n') const code = ['const myVar = 1 + 2 * 5'].join('\n')
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted.trim()).toBe(code.trim())
}) })
it('recast nested binary expression with parans', () => { it('recast nested binary expression with parans', () => {
const code = ['const myVar = 1 + (1 + 2) * 5'].join('\n') const code = ['const myVar = 1 + (1 + 2) * 5'].join('\n')
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted.trim()).toBe(code.trim())
}) })
it('unnecessary paran wrap will be remove', () => { it('unnecessary paran wrap will be remove', () => {
const code = ['const myVar = 1 + (2 * 5)'].join('\n') const code = ['const myVar = 1 + (2 * 5)'].join('\n')
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.replace('(', '').replace(')', '')) expect(recasted.trim()).toBe(code.replace('(', '').replace(')', ''))
}) })
it('complex nested binary expression', () => { it('complex nested binary expression', () => {
const code = ['1 * ((2 + 3) / 4 + 5)'].join('\n') const code = ['1 * ((2 + 3) / 4 + 5)'].join('\n')
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted.trim()).toBe(code.trim())
}) })
it('multiplied paren expressions', () => { it('multiplied paren expressions', () => {
const code = ['3 + (1 + 2) * (3 + 4)'].join('\n') const code = ['3 + (1 + 2) * (3 + 4)'].join('\n')
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted.trim()).toBe(code.trim())
}) })
it('recast array declaration', () => { it('recast array declaration', () => {
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join( const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
@ -135,7 +138,7 @@ show(mySketch)`
) )
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted.trim()).toBe(code.trim())
}) })
it('recast long array declaration', () => { it('recast long array declaration', () => {
const code = [ const code = [
@ -150,7 +153,7 @@ show(mySketch)`
].join('\n') ].join('\n')
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted.trim()).toBe(code.trim())
}) })
it('recast long object exectution', () => { it('recast long object exectution', () => {
const code = `const three = 3 const code = `const three = 3
@ -159,26 +162,29 @@ const yo = {
anum: 2, anum: 2,
identifier: three, identifier: three,
binExp: 4 + 5 binExp: 4 + 5
}` }
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted).toBe(code)
}) })
it('recast short object exectution', () => { it('recast short object exectution', () => {
const code = `const yo = { key: 'val' }` const code = `const yo = { key: 'val' }
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted).toBe(code)
}) })
it('recast object execution with member expression', () => { it('recast object execution with member expression', () => {
const code = `const yo = { a: { b: { c: '123' } } } const code = `const yo = { a: { b: { c: '123' } } }
const key = 'c' const key = 'c'
const myVar = yo.a['b'][key] const myVar = yo.a['b'][key]
const key2 = 'b' const key2 = 'b'
const myVar2 = yo['a'][key2].c` const myVar2 = yo['a'][key2].c
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code.trim()) expect(recasted).toBe(code)
}) })
}) })
@ -186,7 +192,8 @@ describe('testing recasting with comments and whitespace', () => {
it('code with comments', () => { it('code with comments', () => {
const code = `const yo = { a: { b: { c: '123' } } } const code = `const yo = { a: { b: { c: '123' } } }
// this is a comment // this is a comment
const key = 'c'` const key = 'c'
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
@ -199,7 +206,8 @@ const key = 'c'`
/* this is /* this is
a a
comment */ comment */
const yo = 'bing'` const yo = 'bing'
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted).toBe(code)
@ -209,7 +217,8 @@ const yo = 'bing'`
const yo = { a: { b: { c: '123' } } } const yo = { a: { b: { c: '123' } } }
const key = 'c' const key = 'c'
// this is also a comment` // this is also a comment
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted).toBe(code)
@ -223,7 +232,8 @@ const key = 'c'
comment */ comment */
const key = 'c' const key = 'c'
// this is also a comment // this is also a comment
}` }
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted).toBe(code)
@ -239,7 +249,7 @@ const key = 'c'
].join('\n') ].join('\n')
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted.trim()).toBe(code)
}) })
it('comments sprinkled in all over the place', () => { it('comments sprinkled in all over the place', () => {
const code = ` const code = `
@ -261,7 +271,8 @@ const mySk1 = startSketchAt([0, 0])
|> rx(45, %) |> rx(45, %)
/* /*
one more for good measure one more for good measure
*/` */
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(`// comment at start expect(recasted).toBe(`// comment at start
@ -278,7 +289,8 @@ a comment between pipe expression statements */
// and another with just white space between others below // and another with just white space between others below
|> ry(45, %) |> ry(45, %)
|> rx(45, %) |> rx(45, %)
// one more for good measure`) // one more for good measure
`)
}) })
}) })
@ -287,19 +299,19 @@ describe('testing call Expressions in BinaryExpressions and UnaryExpressions', (
const code = 'const myVar = 2 + min(100, legLen(5, 3))' const code = 'const myVar = 2 + min(100, legLen(5, 3))'
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted.trim()).toBe(code)
}) })
it('nested callExpression in unaryExpression', () => { it('nested callExpression in unaryExpression', () => {
const code = 'const myVar = -min(100, legLen(5, 3))' const code = 'const myVar = -min(100, legLen(5, 3))'
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted.trim()).toBe(code)
}) })
it('with unaryExpression in callExpression', () => { it('with unaryExpression in callExpression', () => {
const code = 'const myVar = min(5, -legLen(5, 4))' const code = 'const myVar = min(5, -legLen(5, 4))'
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted.trim()).toBe(code)
}) })
it('with unaryExpression in sketch situation', () => { it('with unaryExpression in sketch situation', () => {
const code = [ const code = [
@ -308,7 +320,7 @@ describe('testing call Expressions in BinaryExpressions and UnaryExpressions', (
].join('\n') ].join('\n')
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted.trim()).toBe(code)
}) })
}) })
@ -323,7 +335,8 @@ describe('it recasts wrapped object expressions in pipe bodies with correct inde
intersectTag: 'seg01' intersectTag: 'seg01'
}, %) }, %)
|> line([-0.42, -1.72], %) |> line([-0.42, -1.72], %)
show(part001)` show(part001)
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted).toBe(code)
@ -333,7 +346,8 @@ show(part001)`
angle: 201, angle: 201,
offset: -1.35, offset: -1.35,
intersectTag: 'seg01' intersectTag: 'seg01'
}, %)` }, %)
`
const { ast } = code2ast(code) const { ast } = code2ast(code)
const recasted = recast(ast) const recasted = recast(ast)
expect(recasted).toBe(code) expect(recasted).toBe(code)
@ -342,7 +356,8 @@ show(part001)`
describe('it recasts binary expression using brackets where needed', () => { describe('it recasts binary expression using brackets where needed', () => {
it('when there are two minus in a row', () => { it('when there are two minus in a row', () => {
const code = `const part001 = 1 - (def - abc)` const code = `const part001 = 1 - (def - abc)
`
const recasted = recast(code2ast(code).ast) const recasted = recast(code2ast(code).ast)
expect(recasted).toBe(code) expect(recasted).toBe(code)
}) })

View File

@ -6,6 +6,8 @@ import { exportSave } from 'lib/exportSave'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import * as Sentry from '@sentry/react' import * as Sentry from '@sentry/react'
let lastMessage = ''
interface CommandInfo { interface CommandInfo {
commandType: CommandTypes commandType: CommandTypes
range: SourceRange range: SourceRange
@ -754,6 +756,13 @@ export class EngineCommandManager {
}) })
} }
sendSceneCommand(command: EngineCommand): Promise<any> { sendSceneCommand(command: EngineCommand): Promise<any> {
if (
command.type === 'modeling_cmd_req' &&
command.cmd.type !== lastMessage
) {
console.log('sending command', command.cmd.type)
lastMessage = command.cmd.type
}
if (!this.engineConnection?.isReady()) { if (!this.engineConnection?.isReady()) {
console.log('socket not ready') console.log('socket not ready')
return Promise.resolve() return Promise.resolve()
@ -761,7 +770,8 @@ export class EngineCommandManager {
if (command.type !== 'modeling_cmd_req') return Promise.resolve() if (command.type !== 'modeling_cmd_req') return Promise.resolve()
const cmd = command.cmd const cmd = command.cmd
if ( if (
cmd.type === 'camera_drag_move' && (cmd.type === 'camera_drag_move' ||
cmd.type === 'handle_mouse_drag_move') &&
this.engineConnection?.unreliableDataChannel this.engineConnection?.unreliableDataChannel
) { ) {
cmd.sequence = this.outSequence cmd.sequence = this.outSequence

View File

@ -101,7 +101,8 @@ describe('testing changeSketchArguments', () => {
|> ${line} |> ${line}
|> lineTo([0.46, -5.82], %) |> lineTo([0.46, -5.82], %)
// |> rx(45, %) // |> rx(45, %)
show(mySketch001)` show(mySketch001)
`
const code = genCode(lineToChange) const code = genCode(lineToChange)
const expectedCode = genCode(lineAfterChange) const expectedCode = genCode(lineAfterChange)
const ast = parser_wasm(code) const ast = parser_wasm(code)
@ -164,7 +165,8 @@ show(mySketch001)`
|> lineTo([-1.59, -1.54], %) |> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %) |> lineTo([0.46, -5.82], %)
|> lineTo([2, 3], %) |> lineTo([2, 3], %)
show(mySketch001)` show(mySketch001)
`
expect(recast(modifiedAst)).toBe(expectedCode) expect(recast(modifiedAst)).toBe(expectedCode)
}) })
}) })
@ -177,7 +179,8 @@ describe('testing addTagForSketchOnFace', () => {
// |> rx(45, %) // |> rx(45, %)
|> ${line} |> ${line}
|> lineTo([0.46, -5.82], %) |> lineTo([0.46, -5.82], %)
show(mySketch001)` show(mySketch001)
`
const code = genCode(originalLine) const code = genCode(originalLine)
const ast = parser_wasm(code) const ast = parser_wasm(code)
const programMemory = await enginelessExecutor(ast) const programMemory = await enginelessExecutor(ast)

View File

@ -4,6 +4,7 @@ import {
SketchGroup, SketchGroup,
SourceRange, SourceRange,
PathToNode, PathToNode,
MemoryItem,
} from '../executor' } from '../executor'
import { import {
Program, Program,
@ -20,7 +21,7 @@ import {
getNodePathFromSourceRange, getNodePathFromSourceRange,
} from '../queryAst' } from '../queryAst'
import { GuiModes, toolTips, TooTip } from '../../useStore' import { GuiModes, toolTips, TooTip } from '../../useStore'
import { splitPathAtPipeExpression } from '../modifyAst' import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
import { generateUuidFromHashSeed } from '../../lib/uuid' import { generateUuidFromHashSeed } from '../../lib/uuid'
import { SketchLineHelper, ModifyAstBase, TransformCallback } from './stdTypes' import { SketchLineHelper, ModifyAstBase, TransformCallback } from './stdTypes'
@ -185,7 +186,7 @@ export const line: SketchLineHelper = {
createCallback, createCallback,
}) => { }) => {
const _node = { ...node } const _node = { ...node }
const { node: pipe } = getNodeFromPath<PipeExpression>( const { node: pipe } = getNodeFromPath<PipeExpression | CallExpression>(
_node, _node,
pathToNode, pathToNode,
'PipeExpression' 'PipeExpression'
@ -197,12 +198,12 @@ export const line: SketchLineHelper = {
) )
const variableName = varDec.id.name const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName] const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup') if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const newXVal = createLiteral(roundOff(to[0] - from[0], 2)) const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
const newYVal = createLiteral(roundOff(to[1] - from[1], 2)) const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
if (replaceExisting && createCallback) { if (replaceExisting && createCallback && pipe.type !== 'CallExpression') {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode) const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
const { callExp, valueUsedInTransform } = createCallback( const { callExp, valueUsedInTransform } = createCallback(
[newXVal, newYVal], [newXVal, newYVal],
@ -220,7 +221,11 @@ export const line: SketchLineHelper = {
createArrayExpression([newXVal, newYVal]), createArrayExpression([newXVal, newYVal]),
createPipeSubstitution(), createPipeSubstitution(),
]) ])
pipe.body = [...pipe.body, callExp] if (pipe.type === 'PipeExpression') {
pipe.body = [...pipe.body, callExp]
} else {
varDec.init = createPipeExpression([varDec.init, callExp])
}
return { return {
modifiedAst: _node, modifiedAst: _node,
pathToNode, pathToNode,
@ -238,22 +243,10 @@ export const line: SketchLineHelper = {
createLiteral(roundOff(to[1] - from[1], 2)), createLiteral(roundOff(to[1] - from[1], 2)),
]) ])
if ( if (callExpression.arguments?.[0].type === 'ObjectExpression') {
callExpression.arguments?.[0].type === 'Literal' &&
callExpression.arguments?.[0].value === 'default'
) {
callExpression.arguments[0] = toArrExp
} else if (callExpression.arguments?.[0].type === 'ObjectExpression') {
const toProp = callExpression.arguments?.[0].properties?.find( const toProp = callExpression.arguments?.[0].properties?.find(
({ key }) => key.name === 'to' ({ key }) => key.name === 'to'
) )
if (
toProp &&
toProp.value.type === 'Literal' &&
toProp.value.value === 'default'
) {
toProp.value = toArrExp
}
mutateObjExpProp(callExpression.arguments?.[0], toArrExp, 'to') mutateObjExpProp(callExpression.arguments?.[0], toArrExp, 'to')
} else { } else {
mutateArrExp(callExpression.arguments?.[0], toArrExp) mutateArrExp(callExpression.arguments?.[0], toArrExp)
@ -546,7 +539,7 @@ export const angledLineOfXLength: SketchLineHelper = {
) )
const variableName = varDec.id.name const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName] const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup') if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const angle = createLiteral(roundOff(getAngle(from, to), 0)) const angle = createLiteral(roundOff(getAngle(from, to), 0))
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1) const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
const newLine = createCallback const newLine = createCallback
@ -619,7 +612,7 @@ export const angledLineOfYLength: SketchLineHelper = {
) )
const variableName = varDec.id.name const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName] const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup') if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
const angle = createLiteral(roundOff(getAngle(from, to), 0)) const angle = createLiteral(roundOff(getAngle(from, to), 0))
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1) const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
@ -876,7 +869,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
const varName = varDec.declarations[0].id.name const varName = varDec.declarations[0].id.name
const sketchGroup = previousProgramMemory.root[varName] as SketchGroup const sketchGroup = previousProgramMemory.root[varName] as SketchGroup
const intersectPath = sketchGroup.value.find( const intersectPath = sketchGroup.value.find(
({ name }) => name === intersectTagName ({ name }: Path) => name === intersectTagName
) )
let offset = 0 let offset = 0
if (intersectPath) { if (intersectPath) {
@ -968,60 +961,14 @@ export function addNewSketchLn({
pathToNode, pathToNode,
'VariableDeclarator' 'VariableDeclarator'
) )
const { node: pipeExp, shallowPath: pipePath } = const { node: pipeExp, shallowPath: pipePath } = getNodeFromPath<
getNodeFromPath<PipeExpression>(node, pathToNode, 'PipeExpression') PipeExpression | CallExpression
const maybeStartSketchAt = pipeExp.body.find( >(node, pathToNode, 'PipeExpression')
(exp) =>
exp.type === 'CallExpression' &&
exp.callee.name === 'startSketchAt' &&
exp.arguments[0].type === 'Literal' &&
exp.arguments[0].value === 'default'
)
const maybeDefaultLine = pipeExp.body.findIndex(
(exp) =>
exp.type === 'CallExpression' &&
exp.callee.name === 'line' &&
exp.arguments[0].type === 'Literal' &&
exp.arguments[0].value === 'default'
)
const defaultLinePath: PathToNode = [
...pipePath,
['body', ''],
[maybeDefaultLine, ''],
]
const variableName = varDec.id.name const variableName = varDec.id.name
const sketch = previousProgramMemory?.root?.[variableName] const sketch = previousProgramMemory?.root?.[variableName]
if (sketch.type !== 'sketchGroup') throw new Error('not a sketchGroup') if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
if (maybeStartSketchAt) { const last = sketch.value[sketch.value.length - 1] || sketch.start
const startSketchAt = maybeStartSketchAt as any
startSketchAt.arguments[0] = createArrayExpression([
createLiteral(to[0]),
createLiteral(to[1]),
])
return {
modifiedAst: node,
}
}
if (maybeDefaultLine !== -1) {
const defaultLine = getNodeFromPath<CallExpression>(
node,
defaultLinePath
).node
const { from } = getSketchSegmentFromSourceRange(sketch, [
defaultLine.start,
defaultLine.end,
]).segment
return updateArgs({
node,
previousProgramMemory,
pathToNode: defaultLinePath,
to,
from,
})
}
const last = sketch.value[sketch.value.length - 1]
const from = last.to const from = last.to
return add({ return add({
@ -1198,14 +1145,6 @@ function getFirstArgValuesForXYFns(callExpression: CallExpression): {
} { } {
// used for lineTo, line // used for lineTo, line
const firstArg = callExpression.arguments[0] const firstArg = callExpression.arguments[0]
if (firstArg.type === 'Literal' && firstArg.value === 'default') {
return {
val:
callExpression.callee.name === 'startSketchAt'
? [createLiteral(0), createLiteral(0)]
: [createLiteral(1), createLiteral(1)],
}
}
if (firstArg.type === 'ArrayExpression') { if (firstArg.type === 'ArrayExpression') {
return { val: [firstArg.elements[0], firstArg.elements[1]] } return { val: [firstArg.elements[0], firstArg.elements[1]] }
} }
@ -1215,8 +1154,6 @@ function getFirstArgValuesForXYFns(callExpression: CallExpression): {
if (to?.type === 'ArrayExpression') { if (to?.type === 'ArrayExpression') {
const [x, y] = to.elements const [x, y] = to.elements
return { val: [x, y], tag } return { val: [x, y], tag }
} else if (to?.type === 'Literal' && to.value === 'default') {
return { val: [createLiteral(0), createLiteral(0)], tag }
} }
} }
throw new Error('expected ArrayExpression or ObjectExpression') throw new Error('expected ArrayExpression or ObjectExpression')

View File

@ -401,6 +401,11 @@ show(part001)`
programMemory.root['part001'] as SketchGroup, programMemory.root['part001'] as SketchGroup,
[index, index] [index, index]
).segment ).segment
expect(segment).toEqual({ to: [0, 0.04], from: [0, 0.04], name: '' }) expect(segment).toEqual({
to: [0, 0.04],
from: [0, 0.04],
name: '',
type: 'base',
})
}) })
}) })

View File

@ -4,7 +4,7 @@ import {
VariableDeclarator, VariableDeclarator,
CallExpression, CallExpression,
} from '../abstractSyntaxTreeTypes' } from '../abstractSyntaxTreeTypes'
import { SketchGroup, SourceRange } from '../executor' import { SketchGroup, SourceRange, Path } from '../executor'
export function getSketchSegmentFromSourceRange( export function getSketchSegmentFromSourceRange(
sketchGroup: SketchGroup, sketchGroup: SketchGroup,
@ -20,10 +20,10 @@ export function getSketchSegmentFromSourceRange(
startSourceRange[1] >= rangeEnd && startSourceRange[1] >= rangeEnd &&
sketchGroup.start sketchGroup.start
) )
return { segment: sketchGroup.start, index: -1 } return { segment: { ...sketchGroup.start, type: 'base' }, index: -1 }
const lineIndex = sketchGroup.value.findIndex( const lineIndex = sketchGroup.value.findIndex(
({ __geoMeta: { sourceRange } }) => ({ __geoMeta: { sourceRange } }: Path) =>
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
) )
const line = sketchGroup.value[lineIndex] const line = sketchGroup.value[lineIndex]

View File

@ -124,7 +124,8 @@ const part001 = startSketchAt([0, 0])
|> yLine(1.04, %) // ln-yLine-free should sub in segLen |> yLine(1.04, %) // ln-yLine-free should sub in segLen
|> xLineTo(30, %) // ln-xLineTo-free should convert to xLine |> xLineTo(30, %) // ln-xLineTo-free should convert to xLine
|> yLineTo(20, %) // ln-yLineTo-free should convert to yLine |> yLineTo(20, %) // ln-yLineTo-free should convert to yLine
show(part001)` show(part001)
`
const expectModifiedScript = `const myVar = 3 const expectModifiedScript = `const myVar = 3
const myVar2 = 5 const myVar2 = 5
const myVar3 = 6 const myVar3 = 6
@ -195,7 +196,8 @@ const part001 = startSketchAt([0, 0])
|> yLine(segLen('seg01', %), %) // ln-yLine-free should sub in segLen |> yLine(segLen('seg01', %), %) // ln-yLine-free should sub in segLen
|> xLine(segLen('seg01', %), %) // ln-xLineTo-free should convert to xLine |> xLine(segLen('seg01', %), %) // ln-xLineTo-free should convert to xLine
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine |> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
show(part001)` show(part001)
`
it('should transform the ast', async () => { it('should transform the ast', async () => {
const ast = parser_wasm(inputScript) const ast = parser_wasm(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript const selectionRanges: Selections['codeBasedSelections'] = inputScript
@ -254,7 +256,8 @@ const part001 = startSketchAt([0, 0])
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9 |> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10 |> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|> angledLineToY([301, myVar], %) // select for vertical constraint 10 |> angledLineToY([301, myVar], %) // select for vertical constraint 10
show(part001)` show(part001)
`
it('should transform horizontal lines the ast', async () => { it('should transform horizontal lines the ast', async () => {
const expectModifiedScript = `const myVar = 2 const expectModifiedScript = `const myVar = 2
const myVar2 = 12 const myVar2 = 12
@ -281,7 +284,8 @@ const part001 = startSketchAt([0, 0])
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9 |> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|> xLineTo(myVar3, %) // select for horizontal constraint 10 |> xLineTo(myVar3, %) // select for horizontal constraint 10
|> angledLineToY([301, myVar], %) // select for vertical constraint 10 |> angledLineToY([301, myVar], %) // select for vertical constraint 10
show(part001)` show(part001)
`
const ast = parser_wasm(inputScript) const ast = parser_wasm(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n') .split('\n')
@ -338,7 +342,8 @@ const part001 = startSketchAt([0, 0])
|> yLineTo(7.68, %) // select for vertical constraint 9 |> yLineTo(7.68, %) // select for vertical constraint 9
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10 |> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|> yLineTo(myVar, %) // select for vertical constraint 10 |> yLineTo(myVar, %) // select for vertical constraint 10
show(part001)` show(part001)
`
const ast = parser_wasm(inputScript) const ast = parser_wasm(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n') .split('\n')
@ -380,7 +385,8 @@ const part001 = startSketchAt([0, 0])
|> line([0.45, 1.46], %) // free |> line([0.45, 1.46], %) // free
|> line([myVar, 0.01], %) // xRelative |> line([myVar, 0.01], %) // xRelative
|> line([0.7, myVar], %) // yRelative |> line([0.7, myVar], %) // yRelative
show(part001)` show(part001)
`
it('testing for free to horizontal and vertical distance', async () => { it('testing for free to horizontal and vertical distance', async () => {
const expectedHorizontalCode = await helperThing( const expectedHorizontalCode = await helperThing(
inputScript, inputScript,

View File

@ -28,6 +28,7 @@ import { createFirstArg, getFirstArg, replaceSketchLine } from './sketch'
import { PathToNode, ProgramMemory } from '../executor' import { PathToNode, ProgramMemory } from '../executor'
import { getSketchSegmentFromSourceRange } from './sketchConstraints' import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import { getAngle, roundOff, normaliseAngle } from '../../lib/utils' import { getAngle, roundOff, normaliseAngle } from '../../lib/utils'
import { MemoryItem } from 'wasm-lib/kcl/bindings/MemoryItem'
type LineInputsType = type LineInputsType =
| 'xAbsolute' | 'xAbsolute'
@ -1452,7 +1453,7 @@ export function transformAstSketchLines({
const varName = varDec.id.name const varName = varDec.id.name
const sketchGroup = programMemory.root?.[varName] const sketchGroup = programMemory.root?.[varName]
if (!sketchGroup || sketchGroup.type !== 'sketchGroup') if (!sketchGroup || sketchGroup.type !== 'SketchGroup')
throw new Error('not a sketch group') throw new Error('not a sketch group')
const seg = getSketchSegmentFromSourceRange(sketchGroup, range).segment const seg = getSketchSegmentFromSourceRange(sketchGroup, range).segment
const referencedSegment = referencedSegmentRange const referencedSegment = referencedSegmentRange

View File

@ -1,17 +1,19 @@
const noModifiersPressed = (e: React.MouseEvent) => const noModifiersPressed = (e: React.MouseEvent) =>
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey !e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
export type CADProgram = export type CameraSystem =
| 'KittyCAD' | 'KittyCAD'
| 'OnShape' | 'OnShape'
| 'Trackpad Friendly'
| 'Solidworks' | 'Solidworks'
| 'NX' | 'NX'
| 'Creo' | 'Creo'
| 'AutoCAD' | 'AutoCAD'
export const cadPrograms: CADProgram[] = [ export const cameraSystems: CameraSystem[] = [
'KittyCAD', 'KittyCAD',
'OnShape', 'OnShape',
'Trackpad Friendly',
'Solidworks', 'Solidworks',
'NX', 'NX',
'Creo', 'Creo',
@ -21,12 +23,14 @@ export const cadPrograms: CADProgram[] = [
interface MouseGuardHandler { interface MouseGuardHandler {
description: string description: string
callback: (e: React.MouseEvent) => boolean callback: (e: React.MouseEvent) => boolean
lenientDragStartButton?: number
} }
interface MouseGuardZoomHandler { interface MouseGuardZoomHandler {
description: string description: string
dragCallback: (e: React.MouseEvent) => boolean dragCallback: (e: React.MouseEvent) => boolean
scrollCallback: (e: React.MouseEvent) => boolean scrollCallback: (e: React.MouseEvent) => boolean
lenientDragStartButton?: number
} }
interface MouseGuard { interface MouseGuard {
@ -35,12 +39,12 @@ interface MouseGuard {
rotate: MouseGuardHandler rotate: MouseGuardHandler
} }
export const cameraMouseDragGuards: Record<CADProgram, MouseGuard> = { export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {
KittyCAD: { KittyCAD: {
pan: { pan: {
description: 'Right click + Shift + drag or middle click + drag', description: 'Right click + Shift + drag or middle click + drag',
callback: (e) => callback: (e) =>
(e.button === 3 && noModifiersPressed(e)) || (e.button === 1 && noModifiersPressed(e)) ||
(e.button === 2 && e.shiftKey), (e.button === 2 && e.shiftKey),
}, },
zoom: { zoom: {
@ -58,7 +62,7 @@ export const cameraMouseDragGuards: Record<CADProgram, MouseGuard> = {
description: 'Right click + Ctrl + drag or middle click + drag', description: 'Right click + Ctrl + drag or middle click + drag',
callback: (e) => callback: (e) =>
(e.button === 2 && e.ctrlKey) || (e.button === 2 && e.ctrlKey) ||
(e.button === 3 && noModifiersPressed(e)), (e.button === 1 && noModifiersPressed(e)),
}, },
zoom: { zoom: {
description: 'Scroll wheel', description: 'Scroll wheel',
@ -70,55 +74,74 @@ export const cameraMouseDragGuards: Record<CADProgram, MouseGuard> = {
callback: (e) => e.button === 2 && noModifiersPressed(e), callback: (e) => e.button === 2 && noModifiersPressed(e),
}, },
}, },
'Trackpad Friendly': {
pan: {
description: 'Left click + Alt + Shift + drag or middle click + drag',
callback: (e) =>
(e.button === 0 && e.altKey && e.shiftKey && !e.metaKey) ||
(e.button === 1 && noModifiersPressed(e)),
},
zoom: {
description: 'Scroll wheel or Left click + Alt + OS + drag',
dragCallback: (e) => e.button === 0 && e.altKey && e.metaKey,
scrollCallback: () => true,
},
rotate: {
description: 'Left click + Alt + drag',
callback: (e) => e.button === 0 && e.altKey && !e.shiftKey && !e.metaKey,
lenientDragStartButton: 0,
},
},
Solidworks: { Solidworks: {
pan: { pan: {
description: 'Right click + Ctrl + drag', description: 'Right click + Ctrl + drag',
callback: (e) => e.button === 2 && e.ctrlKey, callback: (e) => e.button === 2 && e.ctrlKey,
lenientDragStartButton: 2,
}, },
zoom: { zoom: {
description: 'Scroll wheel or Middle click + Shift + drag', description: 'Scroll wheel or Middle click + Shift + drag',
dragCallback: (e) => e.button === 3 && e.shiftKey, dragCallback: (e) => e.button === 1 && e.shiftKey,
scrollCallback: () => true, scrollCallback: () => true,
}, },
rotate: { rotate: {
description: 'Middle click + drag', description: 'Middle click + drag',
callback: (e) => e.button === 3 && noModifiersPressed(e), callback: (e) => e.button === 1 && noModifiersPressed(e),
}, },
}, },
NX: { NX: {
pan: { pan: {
description: 'Middle click + Shift + drag', description: 'Middle click + Shift + drag',
callback: (e) => e.button === 3 && e.shiftKey, callback: (e) => e.button === 1 && e.shiftKey,
}, },
zoom: { zoom: {
description: 'Scroll wheel or Middle click + Ctrl + drag', description: 'Scroll wheel or Middle click + Ctrl + drag',
dragCallback: (e) => e.button === 3 && e.ctrlKey, dragCallback: (e) => e.button === 1 && e.ctrlKey,
scrollCallback: () => true, scrollCallback: () => true,
}, },
rotate: { rotate: {
description: 'Middle click + drag', description: 'Middle click + drag',
callback: (e) => e.button === 3 && noModifiersPressed(e), callback: (e) => e.button === 1 && noModifiersPressed(e),
}, },
}, },
Creo: { Creo: {
pan: { pan: {
description: 'Middle click + Shift + drag', description: 'Middle click + Shift + drag',
callback: (e) => e.button === 3 && e.shiftKey, callback: (e) => e.button === 1 && e.shiftKey,
}, },
zoom: { zoom: {
description: 'Scroll wheel or Middle click + Ctrl + drag', description: 'Scroll wheel or Middle click + Ctrl + drag',
dragCallback: (e) => e.button === 3 && e.ctrlKey, dragCallback: (e) => e.button === 1 && e.ctrlKey,
scrollCallback: () => true, scrollCallback: () => true,
}, },
rotate: { rotate: {
description: 'Middle click + drag', description: 'Middle click + drag',
callback: (e) => e.button === 3 && noModifiersPressed(e), callback: (e) => e.button === 1 && noModifiersPressed(e),
}, },
}, },
AutoCAD: { AutoCAD: {
pan: { pan: {
description: 'Middle click + drag', description: 'Middle click + drag',
callback: (e) => e.button === 3 && noModifiersPressed(e), callback: (e) => e.button === 1 && noModifiersPressed(e),
}, },
zoom: { zoom: {
description: 'Scroll wheel', description: 'Scroll wheel',
@ -127,7 +150,7 @@ export const cameraMouseDragGuards: Record<CADProgram, MouseGuard> = {
}, },
rotate: { rotate: {
description: 'Middle click + Shift + drag', description: 'Middle click + Shift + drag',
callback: (e) => e.button === 3 && e.shiftKey, callback: (e) => e.button === 1 && e.shiftKey,
}, },
}, },
} }

View File

@ -5,7 +5,7 @@ import {
readDir, readDir,
writeTextFile, writeTextFile,
} from '@tauri-apps/api/fs' } from '@tauri-apps/api/fs'
import { documentDir } from '@tauri-apps/api/path' import { documentDir, homeDir } from '@tauri-apps/api/path'
import { isTauri } from './isTauri' import { isTauri } from './isTauri'
import { ProjectWithEntryPointMetadata } from '../Router' import { ProjectWithEntryPointMetadata } from '../Router'
import { metadata } from 'tauri-plugin-fs-extra-api' import { metadata } from 'tauri-plugin-fs-extra-api'
@ -32,7 +32,13 @@ export async function initializeProjectDirectory(directory: string) {
return directory return directory
} }
const docDirectory = await documentDir() let docDirectory: string
try {
docDirectory = await documentDir()
} catch (e) {
console.log(e)
docDirectory = await homeDir() // seems to work better on Linux
}
const INITIAL_DEFAULT_DIR = docDirectory + PROJECT_FOLDER const INITIAL_DEFAULT_DIR = docDirectory + PROJECT_FOLDER

View File

@ -49,7 +49,7 @@ class MockEngineCommandManager {
export async function enginelessExecutor( export async function enginelessExecutor(
ast: Program, ast: Program,
pm: ProgramMemory = { root: {} } pm: ProgramMemory = { root: {}, return: null }
): Promise<ProgramMemory> { ): Promise<ProgramMemory> {
const mockEngineCommandManager = new MockEngineCommandManager({ const mockEngineCommandManager = new MockEngineCommandManager({
setIsStreamReady: () => {}, setIsStreamReady: () => {},
@ -64,7 +64,7 @@ export async function enginelessExecutor(
export async function executor( export async function executor(
ast: Program, ast: Program,
pm: ProgramMemory = { root: {} } pm: ProgramMemory = { root: {}, return: null }
): Promise<ProgramMemory> { ): Promise<ProgramMemory> {
const engineCommandManager = new EngineCommandManager({ const engineCommandManager = new EngineCommandManager({
setIsStreamReady: () => {}, setIsStreamReady: () => {},

View File

@ -118,16 +118,14 @@ async function getUser(context: UserContext) {
if (!context.token && '__TAURI__' in window) throw 'not log in' if (!context.token && '__TAURI__' in window) throw 'not log in'
if (context.token) headers['Authorization'] = `Bearer ${context.token}` if (context.token) headers['Authorization'] = `Bearer ${context.token}`
if (SKIP_AUTH) return LOCAL_USER if (SKIP_AUTH) return LOCAL_USER
try { const response = await fetch(url, {
const response = await fetch(url, { method: 'GET',
method: 'GET', credentials: 'include',
credentials: 'include', headers,
headers, })
})
const user = await response.json() const user = await response.json()
if ('error_code' in user) throw new Error(user.message) if ('error_code' in user) throw new Error(user.message)
return user
} catch (e) { return user
console.error(e)
}
} }

View File

@ -1,7 +1,7 @@
import { assign, createMachine } from 'xstate' import { assign, createMachine } from 'xstate'
import { CommandBarMeta } from '../lib/commands' import { CommandBarMeta } from '../lib/commands'
import { Themes, getSystemTheme, setThemeClass } from '../lib/theme' import { Themes, getSystemTheme, setThemeClass } from '../lib/theme'
import { CADProgram, cadPrograms } from 'lib/cameraControls' import { CameraSystem, cameraSystems } from 'lib/cameraControls'
export const DEFAULT_PROJECT_NAME = 'project-$nnn' export const DEFAULT_PROJECT_NAME = 'project-$nnn'
@ -42,7 +42,7 @@ export const settingsCommandBarMeta: CommandBarMeta = {
name: 'cameraControls', name: 'cameraControls',
type: 'select', type: 'select',
defaultValue: 'cameraControls', defaultValue: 'cameraControls',
options: Object.values(cadPrograms).map((v) => ({ name: v })), options: Object.values(cameraSystems).map((v) => ({ name: v })),
}, },
], ],
}, },
@ -109,7 +109,7 @@ export const settingsMachine = createMachine(
predictableActionArguments: true, predictableActionArguments: true,
context: { context: {
baseUnit: 'in' as BaseUnit, baseUnit: 'in' as BaseUnit,
cameraControls: 'KittyCAD' as CADProgram, cameraControls: 'KittyCAD' as CameraSystem,
defaultDirectory: '', defaultDirectory: '',
defaultProjectName: DEFAULT_PROJECT_NAME, defaultProjectName: DEFAULT_PROJECT_NAME,
onboardingStatus: '', onboardingStatus: '',
@ -232,7 +232,10 @@ export const settingsMachine = createMachine(
schema: { schema: {
events: {} as events: {} as
| { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } } | { type: 'Set Base Unit'; data: { baseUnit: BaseUnit } }
| { type: 'Set Camera Controls'; data: { cameraControls: CADProgram } } | {
type: 'Set Camera Controls'
data: { cameraControls: CameraSystem }
}
| { type: 'Set Default Directory'; data: { defaultDirectory: string } } | { type: 'Set Default Directory'; data: { defaultDirectory: string } }
| { | {
type: 'Set Default Project Name' type: 'Set Default Project Name'

View File

@ -18,8 +18,8 @@ import { IndexLoaderData, paths } from '../Router'
import { Themes } from '../lib/theme' import { Themes } from '../lib/theme'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { import {
CADProgram, CameraSystem,
cadPrograms, cameraSystems,
cameraMouseDragGuards, cameraMouseDragGuards,
} from 'lib/cameraControls' } from 'lib/cameraControls'
import { UnitSystem } from 'machines/settingsMachine' import { UnitSystem } from 'machines/settingsMachine'
@ -103,11 +103,11 @@ export const Settings = () => {
onChange={(e) => { onChange={(e) => {
send({ send({
type: 'Set Camera Controls', type: 'Set Camera Controls',
data: { cameraControls: e.target.value as CADProgram }, data: { cameraControls: e.target.value as CameraSystem },
}) })
}} }}
> >
{cadPrograms.map((program) => ( {cameraSystems.map((program) => (
<option key={program} value={program}> <option key={program} value={program}>
{program} {program}
</option> </option>

View File

@ -43,9 +43,12 @@ export type TooTip =
| 'yLineTo' | 'yLineTo'
| 'angledLineThatIntersects' | 'angledLineThatIntersects'
export const toolTips: TooTip[] = [ export const toolTips = [
'lineTo', 'sketch_line',
'move',
// original tooltips
'line', 'line',
'lineTo',
'angledLine', 'angledLine',
'angledLineOfXLength', 'angledLineOfXLength',
'angledLineOfYLength', 'angledLineOfYLength',
@ -56,7 +59,7 @@ export const toolTips: TooTip[] = [
'xLineTo', 'xLineTo',
'yLineTo', 'yLineTo',
'angledLineThatIntersects', 'angledLineThatIntersects',
] ] as any as TooTip[]
export type GuiModes = export type GuiModes =
| { | {
@ -66,6 +69,7 @@ export type GuiModes =
mode: 'sketch' mode: 'sketch'
sketchMode: TooTip sketchMode: TooTip
isTooltip: true isTooltip: true
waitingFirstClick: boolean
rotation: Rotation rotation: Rotation
position: Position position: Position
id?: string id?: string
@ -84,6 +88,7 @@ export type GuiModes =
} }
| { | {
mode: 'canEditSketch' mode: 'canEditSketch'
pathId: string
pathToNode: PathToNode pathToNode: PathToNode
rotation: Rotation rotation: Rotation
position: Position position: Position
@ -122,8 +127,8 @@ export interface StoreState {
kclErrors: KCLError[] kclErrors: KCLError[]
addKCLError: (err: KCLError) => void addKCLError: (err: KCLError) => void
resetKCLErrors: () => void resetKCLErrors: () => void
ast: Program | null ast: Program
setAst: (ast: Program | null) => void setAst: (ast: Program) => void
updateAst: ( updateAst: (
ast: Program, ast: Program,
optionalParams?: { optionalParams?: {
@ -160,8 +165,8 @@ export interface StoreState {
setIsStreamReady: (isStreamReady: boolean) => void setIsStreamReady: (isStreamReady: boolean) => void
isLSPServerReady: boolean isLSPServerReady: boolean
setIsLSPServerReady: (isLSPServerReady: boolean) => void setIsLSPServerReady: (isLSPServerReady: boolean) => void
buttonDownInStream: number buttonDownInStream: number | undefined
setButtonDownInStream: (buttonDownInStream: number) => void setButtonDownInStream: (buttonDownInStream: number | undefined) => void
didDragInStream: boolean didDragInStream: boolean
setDidDragInStream: (didDragInStream: boolean) => void setDidDragInStream: (didDragInStream: boolean) => void
fileId: string fileId: string
@ -222,12 +227,13 @@ export const useStore = create<StoreState>()(
} }
}) })
setTimeout(() => { setTimeout(() => {
editorView.dispatch({ ranges.length &&
selection: EditorSelection.create( editorView.dispatch({
ranges, selection: EditorSelection.create(
selections.codeBasedSelections.length - 1 ranges,
), selections.codeBasedSelections.length - 1
}) ),
})
}) })
}, },
setCursor2: (codeSelections) => { setCursor2: (codeSelections) => {
@ -281,7 +287,15 @@ export const useStore = create<StoreState>()(
resetKCLErrors: () => { resetKCLErrors: () => {
set({ kclErrors: [] }) set({ kclErrors: [] })
}, },
ast: null, ast: {
start: 0,
end: 0,
body: [],
nonCodeMeta: {
noneCodeNodes: {},
start: null,
},
},
setAst: (ast) => { setAst: (ast) => {
set({ ast }) set({ ast })
}, },
@ -290,7 +304,11 @@ export const useStore = create<StoreState>()(
const astWithUpdatedSource = parser_wasm(newCode) const astWithUpdatedSource = parser_wasm(newCode)
callBack(astWithUpdatedSource) callBack(astWithUpdatedSource)
set({ ast: astWithUpdatedSource, code: newCode }) set({
ast: astWithUpdatedSource,
code: newCode,
defferedCode: newCode,
})
if (focusPath) { if (focusPath) {
const { node } = getNodeFromPath<any>( const { node } = getNodeFromPath<any>(
astWithUpdatedSource, astWithUpdatedSource,
@ -342,7 +360,7 @@ export const useStore = create<StoreState>()(
setError: (error = '') => { setError: (error = '') => {
set({ errorState: { isError: !!error, error } }) set({ errorState: { isError: !!error, error } })
}, },
programMemory: { root: {}, pendingMemory: {} }, programMemory: { root: {}, return: null },
setProgramMemory: (programMemory) => set({ programMemory }), setProgramMemory: (programMemory) => set({ programMemory }),
isShiftDown: false, isShiftDown: false,
setIsShiftDown: (isShiftDown) => set({ isShiftDown }), setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
@ -356,7 +374,7 @@ export const useStore = create<StoreState>()(
setIsStreamReady: (isStreamReady) => set({ isStreamReady }), setIsStreamReady: (isStreamReady) => set({ isStreamReady }),
isLSPServerReady: false, isLSPServerReady: false,
setIsLSPServerReady: (isLSPServerReady) => set({ isLSPServerReady }), setIsLSPServerReady: (isLSPServerReady) => set({ isLSPServerReady }),
buttonDownInStream: 0, buttonDownInStream: undefined,
setButtonDownInStream: (buttonDownInStream) => { setButtonDownInStream: (buttonDownInStream) => {
set({ buttonDownInStream }) set({ buttonDownInStream })
}, },

652
src/wasm-lib/Cargo.lock generated
View File

@ -229,6 +229,32 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bindgen"
version = "0.64.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4"
dependencies = [
"bitflags 1.3.2",
"cexpr",
"clang-sys",
"lazy_static",
"lazycell",
"peeking_take_while",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn 1.0.109",
]
[[package]]
name = "bit_field"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -290,6 +316,12 @@ version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
[[package]]
name = "bytemuck"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.3" version = "1.4.3"
@ -314,6 +346,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -336,6 +377,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "clang-sys"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "3.2.25" version = "3.2.25"
@ -418,6 +470,12 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961" checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.0" version = "1.0.0"
@ -482,6 +540,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "crossbeam-channel" name = "crossbeam-channel"
version = "0.5.8" version = "0.5.8"
@ -492,6 +559,30 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "crossbeam-deque"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.16" version = "0.8.16"
@ -501,6 +592,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -521,7 +618,7 @@ dependencies = [
"hashbrown 0.14.0", "hashbrown 0.14.0",
"lock_api", "lock_api",
"once_cell", "once_cell",
"parking_lot_core", "parking_lot_core 0.9.8",
] ]
[[package]] [[package]]
@ -667,12 +764,85 @@ dependencies = [
"similar", "similar",
] ]
[[package]]
name = "exr"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18"
dependencies = [
"bit_field",
"flume",
"half",
"lebe",
"miniz_oxide",
"rayon-core",
"smallvec",
"zune-inflate",
]
[[package]] [[package]]
name = "fastrand" name = "fastrand"
version = "2.0.0" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
[[package]]
name = "fdeflate"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10"
dependencies = [
"simd-adler32",
]
[[package]]
name = "ffmpeg-next"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8af03c47ad26832ab3aabc4cdbf210af3d3b878783edd5a7ba044ba33aab7a60"
dependencies = [
"bitflags 1.3.2",
"ffmpeg-sys-next",
"libc",
]
[[package]]
name = "ffmpeg-sys-next"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf650f461ccf130f4eef4927affed703cc387b183bfc4a7dfee86a076c131127"
dependencies = [
"bindgen",
"cc",
"libc",
"num_cpus",
"pkg-config",
"vcpkg",
]
[[package]]
name = "flate2"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "flume"
version = "0.10.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
dependencies = [
"futures-core",
"futures-sink",
"nanorand",
"pin-project",
"spin 0.9.8",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@ -819,12 +989,28 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "gif"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
dependencies = [
"color_quant",
"weezl",
]
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.28.0" version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "gloo-utils" name = "gloo-utils"
version = "0.2.0" version = "0.2.0"
@ -857,6 +1043,15 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "half"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
dependencies = [
"crunchy",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
@ -1010,6 +1205,37 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[package]]
name = "image"
version = "0.24.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
dependencies = [
"bytemuck",
"byteorder",
"color_quant",
"exr",
"gif",
"jpeg-decoder",
"num-rational",
"num-traits",
"png",
"qoi",
"tiff",
]
[[package]]
name = "image-compare"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "419d59423b7202f6a2a95d3b9cf11a8cc9bf83a29cf7dda4d617a90e8c5ccfcf"
dependencies = [
"image",
"itertools 0.10.5",
"rayon",
"thiserror",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.9.3" version = "1.9.3"
@ -1031,6 +1257,18 @@ dependencies = [
"hashbrown 0.14.0", "hashbrown 0.14.0",
] ]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.8.0" version = "2.8.0"
@ -1072,6 +1310,15 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "jpeg-decoder"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e"
dependencies = [
"rayon",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.64" version = "0.3.64"
@ -1131,18 +1378,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9cf962b1e81a0b4eb923a727e761b40672cbacc7f5f0b75e13579d346352bc7" checksum = "d9cf962b1e81a0b4eb923a727e761b40672cbacc7f5f0b75e13579d346352bc7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait",
"base64 0.21.2", "base64 0.21.2",
"bytes", "bytes",
"chrono", "chrono",
"data-encoding", "data-encoding",
"format_serde_error",
"futures",
"http",
"itertools 0.10.5", "itertools 0.10.5",
"log",
"parse-display", "parse-display",
"phonenumber", "phonenumber",
"rand",
"reqwest",
"reqwest-conditional-middleware",
"reqwest-middleware 0.2.3",
"reqwest-retry",
"reqwest-tracing",
"schemars", "schemars",
"serde", "serde",
"serde_bytes", "serde_bytes",
"serde_json", "serde_json",
"serde_urlencoded",
"thiserror", "thiserror",
"tracing",
"url", "url",
"uuid", "uuid",
] ]
@ -1153,12 +1413,34 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lazycell"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "lebe"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.147" version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.5.6" version = "0.5.6"
@ -1212,12 +1494,27 @@ dependencies = [
"url", "url",
] ]
[[package]]
name = "matchit"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "mime" name = "mime"
version = "0.3.17" version = "0.3.17"
@ -1247,6 +1544,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
dependencies = [ dependencies = [
"adler", "adler",
"simd-adler32",
] ]
[[package]] [[package]]
@ -1260,6 +1558,15 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "nanorand"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "newline-converter" name = "newline-converter"
version = "0.3.0" version = "0.3.0"
@ -1300,6 +1607,17 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.16" version = "0.2.16"
@ -1380,7 +1698,7 @@ dependencies = [
"rand", "rand",
"regex", "regex",
"reqwest", "reqwest",
"reqwest-middleware", "reqwest-middleware 0.1.6",
"rustfmt-wrapper", "rustfmt-wrapper",
"schemars", "schemars",
"serde", "serde",
@ -1415,12 +1733,42 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "opentelemetry"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6105e89802af13fdf48c49d7646d3b533a70e536d818aae7e78ba0433d01acb8"
dependencies = [
"async-trait",
"crossbeam-channel",
"futures-channel",
"futures-executor",
"futures-util",
"js-sys",
"lazy_static",
"percent-encoding",
"pin-project",
"rand",
"thiserror",
]
[[package]] [[package]]
name = "os_str_bytes" name = "os_str_bytes"
version = "6.5.1" version = "6.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -1428,7 +1776,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core", "parking_lot_core 0.9.8",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
] ]
[[package]] [[package]]
@ -1470,6 +1832,12 @@ dependencies = [
"syn 2.0.29", "syn 2.0.29",
] ]
[[package]]
name = "peeking_take_while"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.0" version = "2.3.0"
@ -1538,6 +1906,25 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "png"
version = "0.17.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
version = "0.2.17" version = "0.2.17"
@ -1587,6 +1974,15 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "qoi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "quick-xml" name = "quick-xml"
version = "0.28.2" version = "0.28.2"
@ -1641,6 +2037,28 @@ dependencies = [
"getrandom", "getrandom",
] ]
[[package]]
name = "rayon"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.16" version = "0.2.16"
@ -1757,6 +2175,18 @@ dependencies = [
"winreg", "winreg",
] ]
[[package]]
name = "reqwest-conditional-middleware"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59e50a2e70970896c99d1b8f20ddc30a70b30d3ac6e619a03a8353b64a49b277"
dependencies = [
"async-trait",
"reqwest",
"reqwest-middleware 0.2.3",
"task-local-extensions",
]
[[package]] [[package]]
name = "reqwest-middleware" name = "reqwest-middleware"
version = "0.1.6" version = "0.1.6"
@ -1773,6 +2203,73 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "reqwest-middleware"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff44108c7925d082f2861e683a88618b68235ad9cdc60d64d9d1188efc951cdb"
dependencies = [
"anyhow",
"async-trait",
"http",
"reqwest",
"serde",
"task-local-extensions",
"thiserror",
]
[[package]]
name = "reqwest-retry"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6a11c05102e5bec712c0619b8c7b7eda8b21a558a0bd981ceee15c38df8be4"
dependencies = [
"anyhow",
"async-trait",
"chrono",
"futures",
"getrandom",
"http",
"hyper",
"parking_lot 0.11.2",
"reqwest",
"reqwest-middleware 0.2.3",
"retry-policies",
"task-local-extensions",
"tokio",
"tracing",
"wasm-timer",
]
[[package]]
name = "reqwest-tracing"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b1e66540e0cac90acadaf7109bf99c90d95abcc94b4c096bfa16a2d7aa7a71"
dependencies = [
"anyhow",
"async-trait",
"getrandom",
"matchit",
"opentelemetry",
"reqwest",
"reqwest-middleware 0.2.3",
"task-local-extensions",
"tracing",
"tracing-opentelemetry",
]
[[package]]
name = "retry-policies"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e09bbcb5003282bcb688f0bae741b278e9c7e8f378f561522c9806c58e075d9b"
dependencies = [
"anyhow",
"chrono",
"rand",
]
[[package]] [[package]]
name = "ring" name = "ring"
version = "0.16.20" version = "0.16.20"
@ -1782,7 +2279,7 @@ dependencies = [
"cc", "cc",
"libc", "libc",
"once_cell", "once_cell",
"spin", "spin 0.5.2",
"untrusted", "untrusted",
"web-sys", "web-sys",
"winapi", "winapi",
@ -1794,6 +2291,12 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustfmt-wrapper" name = "rustfmt-wrapper"
version = "0.2.0" version = "0.2.0"
@ -2102,6 +2605,21 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "shlex"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.1" version = "1.4.1"
@ -2111,6 +2629,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]] [[package]]
name = "similar" name = "similar"
version = "2.2.1" version = "2.2.1"
@ -2223,6 +2747,15 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@ -2367,6 +2900,17 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "tiff"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211"
dependencies = [
"flate2",
"jpeg-decoder",
"weezl",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.45" version = "0.1.45"
@ -2434,7 +2978,7 @@ dependencies = [
"libc", "libc",
"mio", "mio",
"num_cpus", "num_cpus",
"parking_lot", "parking_lot 0.12.1",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry", "signal-hook-registry",
"socket2 0.5.3", "socket2 0.5.3",
@ -2605,6 +3149,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-opentelemetry"
version = "0.17.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbbe89715c1dbbb790059e2565353978564924ee85017b5fff365c872ff6721f"
dependencies = [
"once_cell",
"opentelemetry",
"tracing",
"tracing-core",
"tracing-log",
"tracing-subscriber",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
dependencies = [
"sharded-slab",
"thread_local",
"tracing-core",
] ]
[[package]] [[package]]
@ -2668,6 +3249,19 @@ dependencies = [
"utf-8", "utf-8",
] ]
[[package]]
name = "twenty-twenty"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77f4c2039b998d194da9717843bb8c634609b5ccacac513be56985569f6e732"
dependencies = [
"anyhow",
"ffmpeg-next",
"image",
"image-compare",
"uuid",
]
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.16.0" version = "1.16.0"
@ -2769,6 +3363,18 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
@ -2877,14 +3483,20 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1"
name = "wasm-lib" name = "wasm-lib"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow",
"bson", "bson",
"futures", "futures",
"gloo-utils", "gloo-utils",
"image",
"js-sys", "js-sys",
"kcl-lib", "kcl-lib",
"kittycad", "kittycad",
"reqwest",
"serde_json", "serde_json",
"tokio",
"tower-lsp", "tower-lsp",
"twenty-twenty",
"uuid",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"wasm-streams", "wasm-streams",
@ -2904,6 +3516,21 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "wasm-timer"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f"
dependencies = [
"futures",
"js-sys",
"parking_lot 0.11.2",
"pin-utils",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.64" version = "0.3.64"
@ -2920,6 +3547,12 @@ version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
[[package]]
name = "weezl"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -3125,3 +3758,12 @@ name = "yansi"
version = "0.5.1" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zune-inflate"
version = "0.2.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02"
dependencies = [
"simd-adler32",
]

View File

@ -16,6 +16,15 @@ serde_json = "1.0.93"
wasm-bindgen = "0.2.87" wasm-bindgen = "0.2.87"
wasm-bindgen-futures = "0.4.37" wasm-bindgen-futures = "0.4.37"
[dev-dependencies]
anyhow = "1"
image = "0.24.7"
kittycad = "0.2.25"
reqwest = { version = "0.11.20", default-features = false }
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
twenty-twenty = "0.6.1"
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
futures = "0.3.28" futures = "0.3.28"
js-sys = "0.3.64" js-sys = "0.3.64"

View File

@ -12,7 +12,7 @@ use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, R
use crate::{ use crate::{
engine::EngineConnection, engine::EngineConnection,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange}, executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
parser::PIPE_OPERATOR, parser::PIPE_OPERATOR,
}; };
@ -500,15 +500,22 @@ impl BinaryPart {
pipe_info: &mut PipeInfo, pipe_info: &mut PipeInfo,
engine: &mut EngineConnection, engine: &mut EngineConnection,
) -> Result<MemoryItem, KclError> { ) -> Result<MemoryItem, KclError> {
pipe_info.is_in_pipe = false; // We DO NOT set this gloablly because if we did and this was called inside a pipe it would
// stop the execution of the pipe.
// THIS IS IMPORTANT.
let mut new_pipe_info = pipe_info.clone();
new_pipe_info.is_in_pipe = false;
match self { match self {
BinaryPart::Literal(literal) => Ok(literal.into()), BinaryPart::Literal(literal) => Ok(literal.into()),
BinaryPart::Identifier(identifier) => { BinaryPart::Identifier(identifier) => {
let value = memory.get(&identifier.name, identifier.into())?; let value = memory.get(&identifier.name, identifier.into())?;
Ok(value.clone()) Ok(value.clone())
} }
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(memory, pipe_info, engine), BinaryPart::BinaryExpression(binary_expression) => {
BinaryPart::CallExpression(call_expression) => call_expression.execute(memory, pipe_info, engine), binary_expression.get_result(memory, &mut new_pipe_info, engine)
}
BinaryPart::CallExpression(call_expression) => call_expression.execute(memory, &mut new_pipe_info, engine),
BinaryPart::UnaryExpression(unary_expression) => { BinaryPart::UnaryExpression(unary_expression) => {
// Return an error this should not happen. // Return an error this should not happen.
Err(KclError::Semantic(KclErrorDetails { Err(KclError::Semantic(KclErrorDetails {
@ -718,8 +725,12 @@ impl CallExpression {
binary_expression.get_result(memory, pipe_info, engine)? binary_expression.get_result(memory, pipe_info, engine)?
} }
Value::CallExpression(call_expression) => { Value::CallExpression(call_expression) => {
pipe_info.is_in_pipe = false; // We DO NOT set this gloablly because if we did and this was called inside a pipe it would
call_expression.execute(memory, pipe_info, engine)? // stop the execution of the pipe.
// THIS IS IMPORTANT.
let mut new_pipe_info = pipe_info.clone();
new_pipe_info.is_in_pipe = false;
call_expression.execute(memory, &mut new_pipe_info, engine)?
} }
Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, engine)?, Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, engine)?,
Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, engine)?, Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, engine)?,
@ -773,7 +784,7 @@ impl CallExpression {
Function::InMemory => { Function::InMemory => {
let mem = memory.clone(); let mem = memory.clone();
let func = mem.get(&fn_name, self.into())?; let func = mem.get(&fn_name, self.into())?;
let result = func.call_fn(&fn_args, memory, engine)?.ok_or_else(|| { let result = func.call_fn(&fn_args, &mem, engine)?.ok_or_else(|| {
KclError::UndefinedValue(KclErrorDetails { KclError::UndefinedValue(KclErrorDetails {
message: format!("Result of function {} is undefined", fn_name), message: format!("Result of function {} is undefined", fn_name),
source_ranges: vec![self.into()], source_ranges: vec![self.into()],
@ -1071,23 +1082,23 @@ impl Literal {
impl From<Literal> for MemoryItem { impl From<Literal> for MemoryItem {
fn from(literal: Literal) -> Self { fn from(literal: Literal) -> Self {
MemoryItem::UserVal { MemoryItem::UserVal(UserVal {
value: literal.value.clone(), value: literal.value.clone(),
meta: vec![Metadata { meta: vec![Metadata {
source_range: literal.into(), source_range: literal.into(),
}], }],
} })
} }
} }
impl From<&Box<Literal>> for MemoryItem { impl From<&Box<Literal>> for MemoryItem {
fn from(literal: &Box<Literal>) -> Self { fn from(literal: &Box<Literal>) -> Self {
MemoryItem::UserVal { MemoryItem::UserVal(UserVal {
value: literal.value.clone(), value: literal.value.clone(),
meta: vec![Metadata { meta: vec![Metadata {
source_range: literal.into(), source_range: literal.into(),
}], }],
} })
} }
} }
@ -1199,8 +1210,12 @@ impl ArrayExpression {
binary_expression.get_result(memory, pipe_info, engine)? binary_expression.get_result(memory, pipe_info, engine)?
} }
Value::CallExpression(call_expression) => { Value::CallExpression(call_expression) => {
pipe_info.is_in_pipe = false; // We DO NOT set this gloablly because if we did and this was called inside a pipe it would
call_expression.execute(memory, pipe_info, engine)? // stop the execution of the pipe.
// THIS IS IMPORTANT.
let mut new_pipe_info = pipe_info.clone();
new_pipe_info.is_in_pipe = false;
call_expression.execute(memory, &mut new_pipe_info, engine)?
} }
Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, engine)?, Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, engine)?,
Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, engine)?, Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, engine)?,
@ -1230,12 +1245,12 @@ impl ArrayExpression {
results.push(result); results.push(result);
} }
Ok(MemoryItem::UserVal { Ok(MemoryItem::UserVal(UserVal {
value: results.into(), value: results.into(),
meta: vec![Metadata { meta: vec![Metadata {
source_range: self.into(), source_range: self.into(),
}], }],
}) }))
} }
/// Rename all identifiers that have the old name to the new given name. /// Rename all identifiers that have the old name to the new given name.
@ -1321,8 +1336,12 @@ impl ObjectExpression {
binary_expression.get_result(memory, pipe_info, engine)? binary_expression.get_result(memory, pipe_info, engine)?
} }
Value::CallExpression(call_expression) => { Value::CallExpression(call_expression) => {
pipe_info.is_in_pipe = false; // We DO NOT set this gloablly because if we did and this was called inside a pipe it would
call_expression.execute(memory, pipe_info, engine)? // stop the execution of the pipe.
// THIS IS IMPORTANT.
let mut new_pipe_info = pipe_info.clone();
new_pipe_info.is_in_pipe = false;
call_expression.execute(memory, &mut new_pipe_info, engine)?
} }
Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, engine)?, Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, engine)?,
Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, engine)?, Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, engine)?,
@ -1351,12 +1370,12 @@ impl ObjectExpression {
object.insert(property.key.name.clone(), result.get_json_value()?); object.insert(property.key.name.clone(), result.get_json_value()?);
} }
Ok(MemoryItem::UserVal { Ok(MemoryItem::UserVal(UserVal {
value: object.into(), value: object.into(),
meta: vec![Metadata { meta: vec![Metadata {
source_range: self.into(), source_range: self.into(),
}], }],
}) }))
} }
/// Rename all identifiers that have the old name to the new given name. /// Rename all identifiers that have the old name to the new given name.
@ -1563,12 +1582,12 @@ impl MemberExpression {
if let serde_json::Value::Object(map) = object { if let serde_json::Value::Object(map) = object {
if let Some(value) = map.get(&property_name) { if let Some(value) = map.get(&property_name) {
Ok(MemoryItem::UserVal { Ok(MemoryItem::UserVal(UserVal {
value: value.clone(), value: value.clone(),
meta: vec![Metadata { meta: vec![Metadata {
source_range: self.into(), source_range: self.into(),
}], }],
}) }))
} else { } else {
Err(KclError::UndefinedValue(KclErrorDetails { Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("Property {} not found in object", property_name), message: format!("Property {} not found in object", property_name),
@ -1674,10 +1693,20 @@ impl BinaryExpression {
pipe_info: &mut PipeInfo, pipe_info: &mut PipeInfo,
engine: &mut EngineConnection, engine: &mut EngineConnection,
) -> Result<MemoryItem, KclError> { ) -> Result<MemoryItem, KclError> {
pipe_info.is_in_pipe = false; // We DO NOT set this gloablly because if we did and this was called inside a pipe it would
// stop the execution of the pipe.
// THIS IS IMPORTANT.
let mut new_pipe_info = pipe_info.clone();
new_pipe_info.is_in_pipe = false;
let left_json_value = self.left.get_result(memory, pipe_info, engine)?.get_json_value()?; let left_json_value = self
let right_json_value = self.right.get_result(memory, pipe_info, engine)?.get_json_value()?; .left
.get_result(memory, &mut new_pipe_info, engine)?
.get_json_value()?;
let right_json_value = self
.right
.get_result(memory, &mut new_pipe_info, engine)?
.get_json_value()?;
// First check if we are doing string concatenation. // First check if we are doing string concatenation.
if self.operator == BinaryOperator::Add { if self.operator == BinaryOperator::Add {
@ -1686,12 +1715,12 @@ impl BinaryExpression {
parse_json_value_as_string(&right_json_value), parse_json_value_as_string(&right_json_value),
) { ) {
let value = serde_json::Value::String(format!("{}{}", left, right)); let value = serde_json::Value::String(format!("{}{}", left, right));
return Ok(MemoryItem::UserVal { return Ok(MemoryItem::UserVal(UserVal {
value, value,
meta: vec![Metadata { meta: vec![Metadata {
source_range: self.into(), source_range: self.into(),
}], }],
}); }));
} }
} }
@ -1706,12 +1735,12 @@ impl BinaryExpression {
BinaryOperator::Mod => (left % right).into(), BinaryOperator::Mod => (left % right).into(),
}; };
Ok(MemoryItem::UserVal { Ok(MemoryItem::UserVal(UserVal {
value, value,
meta: vec![Metadata { meta: vec![Metadata {
source_range: self.into(), source_range: self.into(),
}], }],
}) }))
} }
/// Rename all identifiers that have the old name to the new given name. /// Rename all identifiers that have the old name to the new given name.
@ -1803,18 +1832,25 @@ impl UnaryExpression {
pipe_info: &mut PipeInfo, pipe_info: &mut PipeInfo,
engine: &mut EngineConnection, engine: &mut EngineConnection,
) -> Result<MemoryItem, KclError> { ) -> Result<MemoryItem, KclError> {
pipe_info.is_in_pipe = false; // We DO NOT set this gloablly because if we did and this was called inside a pipe it would
// stop the execution of the pipe.
// THIS IS IMPORTANT.
let mut new_pipe_info = pipe_info.clone();
new_pipe_info.is_in_pipe = false;
let num = parse_json_number_as_f64( let num = parse_json_number_as_f64(
&self.argument.get_result(memory, pipe_info, engine)?.get_json_value()?, &self
.argument
.get_result(memory, &mut new_pipe_info, engine)?
.get_json_value()?,
self.into(), self.into(),
)?; )?;
Ok(MemoryItem::UserVal { Ok(MemoryItem::UserVal(UserVal {
value: (-(num)).into(), value: (-(num)).into(),
meta: vec![Metadata { meta: vec![Metadata {
source_range: self.into(), source_range: self.into(),
}], }],
}) }))
} }
/// Returns a hover value that includes the given character position. /// Returns a hover value that includes the given character position.
@ -1980,6 +2016,9 @@ impl_value_meta!(FunctionExpression);
impl FunctionExpression { impl FunctionExpression {
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String { pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
// We don't want to end with a new line inside nested functions.
let mut new_options = options.clone();
new_options.insert_final_newline = false;
format!( format!(
"({}) => {{\n{}{}\n}}", "({}) => {{\n{}{}\n}}",
self.params self.params
@ -1988,7 +2027,7 @@ impl FunctionExpression {
.collect::<Vec<String>>() .collect::<Vec<String>>()
.join(", "), .join(", "),
options.get_indentation(indentation_level + 1), options.get_indentation(indentation_level + 1),
self.body.recast(options, indentation_level + 1) self.body.recast(&new_options, indentation_level + 1)
) )
} }
@ -2056,7 +2095,7 @@ impl FormatOptions {
Self { Self {
tab_size: 2, tab_size: 2,
use_tabs: false, use_tabs: false,
insert_final_newline: false, insert_final_newline: true,
} }
} }
@ -2127,7 +2166,8 @@ show(part001)"#;
r#"const part001 = startSketchAt('default') r#"const part001 = startSketchAt('default')
|> ry(90, %) |> ry(90, %)
|> line('default', %) |> line('default', %)
show(part001)"# show(part001)
"#
); );
} }
@ -2145,7 +2185,8 @@ show(part001)"#
recasted, recasted,
r#"const part001 = startSketchAt([0.0, 5.0]) r#"const part001 = startSketchAt([0.0, 5.0])
|> line([0.4900857016, -0.0240763666], %) |> line([0.4900857016, -0.0240763666], %)
|> line([0.6804562304, 0.9087880491], %)"# |> line([0.6804562304, 0.9087880491], %)
"#
); );
} }
@ -2163,7 +2204,8 @@ show(part001)"#
recasted, recasted,
r#"const part001 = startSketchAt([0.0, 5.0]) r#"const part001 = startSketchAt([0.0, 5.0])
|> line([0.4900857016, -0.0240763666], %) // hello world |> line([0.4900857016, -0.0240763666], %) // hello world
|> line([0.6804562304, 0.9087880491], %)"# |> line([0.6804562304, 0.9087880491], %)
"#
); );
} }
#[test] #[test]
@ -2182,7 +2224,8 @@ show(part001)"#
r#"const part001 = startSketchAt([0.0, 5.0]) r#"const part001 = startSketchAt([0.0, 5.0])
|> line([0.4900857016, -0.0240763666], %) |> line([0.4900857016, -0.0240763666], %)
// hello world // hello world
|> line([0.6804562304, 0.9087880491], %)"# |> line([0.6804562304, 0.9087880491], %)
"#
); );
} }
@ -2212,7 +2255,8 @@ show(part001)"#
const key = 'c' const key = 'c'
// this is also a comment // this is also a comment
return things return things
}"# }
"#
); );
} }
@ -2254,7 +2298,8 @@ a comment between pipe expression statements */
// and another with just white space between others below // and another with just white space between others below
|> ry(45, %) |> ry(45, %)
|> rx(45, %) |> rx(45, %)
// one more for good measure"# // one more for good measure
"#
); );
} }
@ -2276,7 +2321,7 @@ show(part001)"#;
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0); let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted, some_program_string); assert_eq!(recasted.trim(), some_program_string);
} }
#[test] #[test]
@ -2295,7 +2340,8 @@ const yo = [
"three", "three",
4 + 5, 4 + 5,
" hey oooooo really long long long" " hey oooooo really long long long"
]"#; ]
"#;
let tokens = crate::tokeniser::lexer(some_program_string); let tokens = crate::tokeniser::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens); let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
@ -2319,7 +2365,7 @@ const things = "things"
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0); let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted, some_program_string.trim()); assert_eq!(recasted.trim(), some_program_string.trim());
} }
#[test] #[test]
@ -2337,7 +2383,7 @@ const things = "things"
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0); let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted, some_program_string.trim()); assert_eq!(recasted.trim(), some_program_string.trim());
} }
#[test] #[test]
@ -2362,7 +2408,7 @@ const part001 = startSketchAt([0, 0])
let program = parser.ast().unwrap(); let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0); let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted, some_program_string); assert_eq!(recasted.trim(), some_program_string);
} }
#[test] #[test]
@ -2435,7 +2481,8 @@ fn ghi = (part001) => {
return part001 return part001
} }
show(mySuperCoolPart)"# show(mySuperCoolPart)
"#
); );
} }
@ -2454,7 +2501,45 @@ show(mySuperCoolPart)"#
recasted, recasted,
r#"fn ghi = (newName, y, z) => { r#"fn ghi = (newName, y, z) => {
return newName return newName
}"# }
"#
);
}
#[test]
fn test_recast_negative_var() {
let some_program_string = r#"const w = 20
const l = 8
const h = 10
const firstExtrude = startSketchAt([0,0])
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, -l], %)
|> close(%)
|> extrude(h, %)
show(firstExtrude)"#;
let tokens = crate::tokeniser::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
recasted,
r#"const w = 20
const l = 8
const h = 10
const firstExtrude = startSketchAt([0, 0])
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, -l], %)
|> close(%)
|> extrude(h, %)
show(firstExtrude)
"#
); );
} }
} }

View File

@ -98,16 +98,14 @@ impl ProgramReturn {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")] #[serde(tag = "type")]
pub enum MemoryItem { pub enum MemoryItem {
UserVal { UserVal(UserVal),
value: serde_json::Value,
#[serde(rename = "__meta")]
meta: Vec<Metadata>,
},
SketchGroup(SketchGroup), SketchGroup(SketchGroup),
ExtrudeGroup(ExtrudeGroup), ExtrudeGroup(ExtrudeGroup),
#[ts(skip)]
ExtrudeTransform(ExtrudeTransform), ExtrudeTransform(ExtrudeTransform),
#[ts(skip)]
Function { Function {
#[serde(skip)] #[serde(skip)]
func: Option<MemoryFunction>, func: Option<MemoryFunction>,
@ -119,7 +117,16 @@ pub enum MemoryItem {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(tag = "type", rename_all = "camelCase")]
pub struct UserVal {
pub value: serde_json::Value,
#[serde(rename = "__meta")]
pub meta: Vec<Metadata>,
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
pub struct ExtrudeTransform { pub struct ExtrudeTransform {
pub position: Position, pub position: Position,
pub rotation: Rotation, pub rotation: Rotation,
@ -138,7 +145,7 @@ pub type MemoryFunction = fn(
impl From<MemoryItem> for Vec<SourceRange> { impl From<MemoryItem> for Vec<SourceRange> {
fn from(item: MemoryItem) -> Self { fn from(item: MemoryItem) -> Self {
match item { match item {
MemoryItem::UserVal { meta, .. } => meta.iter().map(|m| m.source_range).collect(), MemoryItem::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
MemoryItem::SketchGroup(s) => s.meta.iter().map(|m| m.source_range).collect(), MemoryItem::SketchGroup(s) => s.meta.iter().map(|m| m.source_range).collect(),
MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(), MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(), MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
@ -149,8 +156,8 @@ impl From<MemoryItem> for Vec<SourceRange> {
impl MemoryItem { impl MemoryItem {
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> { pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
if let MemoryItem::UserVal { value, .. } = self { if let MemoryItem::UserVal(user_val) = self {
Ok(value.clone()) Ok(user_val.value.clone())
} else { } else {
Err(KclError::Semantic(KclErrorDetails { Err(KclError::Semantic(KclErrorDetails {
message: format!("Not a user value: {:?}", self), message: format!("Not a user value: {:?}", self),
@ -186,7 +193,7 @@ impl MemoryItem {
/// A sketch group is a collection of paths. /// A sketch group is a collection of paths.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(tag = "type", rename_all = "camelCase")]
pub struct SketchGroup { pub struct SketchGroup {
/// The id of the sketch group. /// The id of the sketch group.
pub id: uuid::Uuid, pub id: uuid::Uuid,
@ -238,7 +245,7 @@ impl SketchGroup {
/// An extrude group is a collection of extrude surfaces. /// An extrude group is a collection of extrude surfaces.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(tag = "type", rename_all = "camelCase")]
pub struct ExtrudeGroup { pub struct ExtrudeGroup {
/// The id of the extrude group. /// The id of the extrude group.
pub id: uuid::Uuid, pub id: uuid::Uuid,
@ -276,15 +283,15 @@ pub enum BodyType {
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)] #[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
pub struct Position(pub [f64; 3]); pub struct Position(#[ts(type = "[number, number, number]")] pub [f64; 3]);
#[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)] #[derive(Debug, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
pub struct Rotation(pub [f64; 4]); pub struct Rotation(#[ts(type = "[number, number, number, number]")] pub [f64; 4]);
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)] #[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
#[ts(export)] #[ts(export)]
pub struct SourceRange(pub [usize; 2]); pub struct SourceRange(#[ts(type = "[number, number]")] pub [usize; 2]);
impl SourceRange { impl SourceRange {
/// Create a new source range. /// Create a new source range.
@ -401,8 +408,10 @@ impl From<SourceRange> for Metadata {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct BasePath { pub struct BasePath {
/// The from point. /// The from point.
#[ts(type = "[number, number]")]
pub from: [f64; 2], pub from: [f64; 2],
/// The to point. /// The to point.
#[ts(type = "[number, number]")]
pub to: [f64; 2], pub to: [f64; 2],
/// The name of the path. /// The name of the path.
pub name: String, pub name: String,
@ -595,7 +604,9 @@ pub fn execute(
memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone())); memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
} else if let Some(func) = memory.clone().root.get(&fn_name) { } else if let Some(func) = memory.clone().root.get(&fn_name) {
func.call_fn(&args, memory, engine)?; let result = func.call_fn(&args, memory, engine)?;
memory.return_ = result;
} else { } else {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: format!("No such name {} defined", fn_name), message: format!("No such name {} defined", fn_name),
@ -696,11 +707,39 @@ pub fn execute(
let result = bin_expr.get_result(memory, &mut pipe_info, engine)?; let result = bin_expr.get_result(memory, &mut pipe_info, engine)?;
memory.return_ = Some(ProgramReturn::Value(result)); memory.return_ = Some(ProgramReturn::Value(result));
} }
Value::UnaryExpression(unary_expr) => {
let result = unary_expr.get_result(memory, &mut pipe_info, engine)?;
memory.return_ = Some(ProgramReturn::Value(result));
}
Value::Identifier(identifier) => { Value::Identifier(identifier) => {
let value = memory.get(&identifier.name, identifier.into())?.clone(); let value = memory.get(&identifier.name, identifier.into())?.clone();
memory.return_ = Some(ProgramReturn::Value(value)); memory.return_ = Some(ProgramReturn::Value(value));
} }
_ => (), Value::Literal(literal) => {
memory.return_ = Some(ProgramReturn::Value(literal.into()));
}
Value::ArrayExpression(array_expr) => {
let result = array_expr.execute(memory, &mut pipe_info, engine)?;
memory.return_ = Some(ProgramReturn::Value(result));
}
Value::ObjectExpression(obj_expr) => {
let result = obj_expr.execute(memory, &mut pipe_info, engine)?;
memory.return_ = Some(ProgramReturn::Value(result));
}
Value::CallExpression(call_expr) => {
let result = call_expr.execute(memory, &mut pipe_info, engine)?;
memory.return_ = Some(ProgramReturn::Value(result));
}
Value::MemberExpression(member_expr) => {
let result = member_expr.get_result(memory)?;
memory.return_ = Some(ProgramReturn::Value(result));
}
Value::PipeExpression(pipe_expr) => {
let result = pipe_expr.get_result(memory, &mut pipe_info, engine)?;
memory.return_ = Some(ProgramReturn::Value(result));
}
Value::PipeSubstitution(_) => {}
Value::FunctionExpression(_) => {}
}, },
} }
} }
@ -856,4 +895,116 @@ show(part001)"#;
parse_execute(ast).await.unwrap(); parse_execute(ast).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_function_literal_in_pipe() {
let ast = r#"const w = 20
const l = 8
const h = 10
fn thing = () => {
return -8
}
const firstExtrude = startSketchAt([0,0])
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, thing()], %)
|> close(%)
|> extrude(h, %)
show(firstExtrude)"#;
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_function_unary_in_pipe() {
let ast = r#"const w = 20
const l = 8
const h = 10
fn thing = (x) => {
return -x
}
const firstExtrude = startSketchAt([0,0])
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, thing(8)], %)
|> close(%)
|> extrude(h, %)
show(firstExtrude)"#;
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_function_array_in_pipe() {
let ast = r#"const w = 20
const l = 8
const h = 10
fn thing = (x) => {
return [0, -x]
}
const firstExtrude = startSketchAt([0,0])
|> line([0, l], %)
|> line([w, 0], %)
|> line(thing(8), %)
|> close(%)
|> extrude(h, %)
show(firstExtrude)"#;
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_function_call_in_pipe() {
let ast = r#"const w = 20
const l = 8
const h = 10
fn other_thing = (y) => {
return -y
}
fn thing = (x) => {
return other_thing(x)
}
const firstExtrude = startSketchAt([0,0])
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, thing(8)], %)
|> close(%)
|> extrude(h, %)
show(firstExtrude)"#;
parse_execute(ast).await.unwrap();
}
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_function_sketch() {
let ast = r#"const box = (h, l, w) => {
const myBox = startSketchAt([0,0])
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, -l], %)
|> close(%)
|> extrude(h, %)
return myBox
}
const fnBox = box(3, 6, 10)
show(fnBox)"#;
parse_execute(ast).await.unwrap();
}
} }

View File

@ -103,12 +103,12 @@ impl<'a> Args<'a> {
} }
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> { fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
Ok(MemoryItem::UserVal { Ok(MemoryItem::UserVal(crate::executor::UserVal {
value: j, value: j,
meta: vec![Metadata { meta: vec![Metadata {
source_range: self.source_range, source_range: self.source_range,
}], }],
}) }))
} }
fn make_user_val_from_f64(&self, f: f64) -> Result<MemoryItem, KclError> { fn make_user_val_from_f64(&self, f: f64) -> Result<MemoryItem, KclError> {

View File

@ -382,6 +382,20 @@ fn inner_angled_line(
}, },
}; };
args.send_modeling_cmd(
id,
ModelingCmd::ExtendPath {
path: sketch_group.id,
segment: kittycad::types::PathSegment::Line {
end: Point3D {
x: to[0],
y: to[1],
z: 0.0,
},
},
},
)?;
let mut new_sketch_group = sketch_group.clone(); let mut new_sketch_group = sketch_group.clone();
new_sketch_group.value.push(current_path); new_sketch_group.value.push(current_path);
Ok(new_sketch_group) Ok(new_sketch_group)

View File

@ -0,0 +1,98 @@
use anyhow::Result;
/// Executes a kcl program and takes a snapshot of the result.
/// This returns the bytes of the snapshot.
async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
let http_client = reqwest::Client::builder()
.user_agent(user_agent)
// For file conversions we need this to be long.
.timeout(std::time::Duration::from_secs(600))
.connect_timeout(std::time::Duration::from_secs(60));
let ws_client = reqwest::Client::builder()
.user_agent(user_agent)
// For file conversions we need this to be long.
.timeout(std::time::Duration::from_secs(600))
.connect_timeout(std::time::Duration::from_secs(60))
.tcp_keepalive(std::time::Duration::from_secs(600))
.http1_only();
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
// Create the client.
let client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
let ws = client
.modeling()
.commands_ws(None, None, None, None, Some(false))
.await?;
// Create a temporary file to write the output to.
let output_file = std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
let tokens = kcl_lib::tokeniser::lexer(code);
let parser = kcl_lib::parser::Parser::new(tokens);
let program = parser.ast()?;
let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
let mut engine = kcl_lib::engine::EngineConnection::new(
ws,
std::env::temp_dir().display().to_string().as_str(),
output_file.display().to_string().as_str(),
)
.await?;
let _ = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &mut engine)?;
// Send a snapshot request to the engine.
engine.send_modeling_cmd(
uuid::Uuid::new_v4(),
kcl_lib::executor::SourceRange::default(),
kittycad::types::ModelingCmd::TakeSnapshot {
format: kittycad::types::ImageFormat::Png,
},
)?;
// Wait for the snapshot to be taken.
engine.wait_for_snapshot().await;
// Read the output file.
let actual = image::io::Reader::open(output_file).unwrap().decode().unwrap();
Ok(actual)
}
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_function_sketch() {
let code = r#"const box = (h, l, w) => {
const myBox = startSketchAt([0,0])
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, -l], %)
|> close(%)
|> extrude(h, %)
return myBox
}
const fnBox = box(3, 6, 10)
show(fnBox)"#;
let result = execute_and_snapshot(code).await.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/function_sketch.png", &result, 1.0);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_angled_line() {
let code = r#"const part001 = startSketchAt([4.83, 12.56])
|> line([15.1, 2.48], %)
|> line({ to: [3.15, -9.85], tag: 'seg01' }, %)
|> line([-15.17, -4.1], %)
|> angledLine([segAng('seg01', %), 12.35], %)
|> line([-13.02, 10.03], %)
|> close(%)
|> extrude(4, %)
show(part001)"#;
let result = execute_and_snapshot(code).await.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/angled_line.png", &result, 1.0);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB