Compare commits

..

18 Commits

Author SHA1 Message Date
977e566ae4 Merge branch 'main' into franknoirot/xstate-toolbar 2023-09-14 12:24:16 -04:00
7aaf923529 Update moved useEffect hook after merge 2023-09-13 14:52:43 -04:00
bcb05d02b4 Merge branch 'main' into franknoirot/xstate-toolbar 2023-09-13 14:51:24 -04:00
ef451b70b6 Add barebones modeling machine to app
Only implementing adding to code-based selections in the text editor so far
2023-09-13 14:46:10 -04:00
c33107aa28 Add TS schema, selection actions to modelingMachine 2023-09-13 14:45:14 -04:00
26737e055a Refactor: move other engine-related useEffect into hook 2023-09-13 12:04:55 -04:00
c01590b49b Create modeling provider, move engine management to it 2023-09-13 09:50:10 -04:00
1d656d68c6 Refactor: break out engine manager setup into hook
Preparing for making a wrapper component around the App
that will manage the engine manager at the same level as
the modelingMachine.
2023-09-13 09:37:29 -04:00
e180b73c9d Merge branch 'main' into franknoirot/xstate-toolbar 2023-09-12 14:34:46 -04:00
738b1a7c21 Merge branch 'main' into franknoirot/xstate-toolbar 2023-09-01 18:04:51 -04:00
62aebaf523 Add fillet tool flow 2023-09-01 18:04:12 -04:00
2095375b37 Add initial modeling machine
This is not a full description of how the modelingMachine should work,
but begins to replicate all of the features of our useStore in XState
instead of zustand.
2023-09-01 11:49:15 -04:00
7a9a33c656 Add transitions 2023-08-29 15:26:33 -04:00
a4a393fc45 Remove ActionButton until after tool logic refactor 2023-08-29 14:35:36 -04:00
10884fd0b0 Turn toolbar buttons back on 2023-08-29 14:18:02 -04:00
bcf83dc7ee Add support for 2D and 3D mode styling 2023-08-29 13:25:10 -04:00
32f79c98f8 Fix up light mode of basic bar 2023-08-29 12:27:50 -04:00
c11149e909 Add basic Popover functionality 2023-08-29 12:21:42 -04:00
127 changed files with 2420 additions and 7190 deletions

View File

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

View File

@ -24,7 +24,7 @@ jobs:
matrix:
dir: ['src/wasm-lib']
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Install latest rust
uses: actions-rs/toolchain@v1

View File

@ -24,7 +24,7 @@ jobs:
matrix:
dir: ['src/wasm-lib']
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Install latest rust
uses: actions-rs/toolchain@v1
with:

View File

@ -27,7 +27,7 @@ jobs:
matrix:
dir: ['src/wasm-lib', 'src-tauri']
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Install latest rust
uses: actions-rs/toolchain@v1
with:

View File

@ -26,7 +26,7 @@ jobs:
matrix:
dir: ['src/wasm-lib']
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Install latest rust
uses: actions-rs/toolchain@v1
with:
@ -55,7 +55,7 @@ jobs:
shell: bash
run: |-
cd "${{ matrix.dir }}"
cargo nextest run --workspace --no-fail-fast -P ci
cargo test --all
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}

View File

@ -4,15 +4,16 @@ on:
pull_request:
push:
branches:
- main
- main
release:
types: [published]
jobs:
check-format:
runs-on: 'ubuntu-20.04'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
@ -24,7 +25,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version-file: '.nvmrc'
@ -32,17 +33,19 @@ jobs:
- run: yarn install
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
workspaces: "./src/wasm-lib"
- run: yarn build:wasm
- run: yarn tsc
build-test-web:
runs-on: ubuntu-20.04
outputs:
version: ${{ steps.export_version.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
@ -53,7 +56,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
workspaces: "./src/wasm-lib"
- run: yarn build:wasm
@ -66,6 +69,7 @@ jobs:
- id: export_version
run: echo "version=`cat package.json | jq -r '.version'`" >> "$GITHUB_OUTPUT"
build-apps:
needs: [check-format, build-test-web, check-types]
runs-on: ${{ matrix.os }}
@ -73,7 +77,8 @@ jobs:
matrix:
os: [macos-latest, ubuntu-20.04, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: install ubuntu system dependencies
if: matrix.os == 'ubuntu-20.04'
@ -99,7 +104,7 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
workspaces: "./src/wasm-lib"
- name: wasm prep
shell: bash
@ -109,6 +114,18 @@ jobs:
cd ../../
cp src/wasm-lib/pkg/wasm_lib_bg.wasm public
- name: macos sed
if: matrix.os == 'macos-latest'
shell: bash
run: |
sed -i '' 's/import.meta.url//g' "./src/wasm-lib/pkg/wasm_lib.js"
- name: ubuntu and windows sed
if: matrix.os != 'macos-latest'
shell: bash
run: |
sed -i 's/import.meta.url//g' "./src/wasm-lib/pkg/wasm_lib.js"
- name: Fix format
run: yarn fmt
@ -117,42 +134,11 @@ jobs:
run: |
rustup target add aarch64-apple-darwin
- name: Prepare Windows certificate and variables
if: matrix.os == 'windows-latest'
run: |
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
cat /d/Certificate_pkcs12.p12
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
shell: bash
- name: Setup Windows certicate with SSM KSP
if: matrix.os == 'windows-latest'
run: |
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
msiexec /i smtools-windows-x64.msi /quiet /qn
smksp_registrar.exe list
smctl.exe keypair ls
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
smksp_cert_sync.exe
shell: cmd
- name: Build and sign the app for the current platform
- name: Build the app for the current platform (no upload)
uses: tauri-apps/tauri-action@v0
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
with:
args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }}
@ -160,15 +146,68 @@ jobs:
with:
path: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin/release/bundle/*/*' || 'src-tauri/target/release/bundle/*/*' }}
sign-windows-msi:
runs-on: windows-latest
if: github.event_name == 'release'
needs: [build-test-web, build-apps]
env:
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
steps:
- uses: actions/download-artifact@v3
- name: Setup Certificate
run: |
echo "${{secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
cat /d/Certificate_pkcs12.p12
shell: bash
- name: Set variables
id: variables
run: |
echo "::set-output name=version::${GITHUB_REF#refs/tags/v}"
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
echo "C:\Program Files (x86)\Windows Kits\10\App Certification Kit" >> $GITHUB_PATH
echo "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools" >> $GITHUB_PATH
echo "C:\Program Files\DigiCert\DigiCert One Signing Manager Tools" >> $GITHUB_PATH
shell: bash
- name: Setup SSM KSP on windows latest
run: |
curl -X GET https://one.digicert.com/signingmanager/api-ui/v1/releases/smtools-windows-x64.msi/download -H "x-api-key:%SM_API_KEY%" -o smtools-windows-x64.msi
msiexec /i smtools-windows-x64.msi /quiet /qn
smksp_registrar.exe list
smctl.exe keypair ls
C:\Windows\System32\certutil.exe -csp "DigiCert Signing Manager KSP" -key -user
smksp_cert_sync.exe
shell: cmd
- name: Signing using Signtool
run: |
signtool.exe sign /sha1 ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256 "artifact\msi\*.msi"
signtool.exe verify /v /pa "artifact\msi\*.msi"
# TODO: for the updater, investigate if we need to also replace what's in the .zip, and what to do about the .sig file
- uses: actions/upload-artifact@v3
with:
path: artifact/*
publish-apps-release:
runs-on: ubuntu-20.04
if: github.event_name == 'release'
needs: [build-test-web, build-apps]
needs: [build-test-web, build-apps, sign-windows-msi]
env:
VERSION_NO_V: ${{ needs.build-test-web.outputs.version }}
PUB_DATE: ${{ github.event.release.created_at }}
NOTES: ${{ github.event.release.body }}
steps:
- uses: actions/download-artifact@v3
- name: Generate the update static endpoint

View File

@ -16,7 +16,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3.5.0
- shell: bash
run: |
# checkout our branch

View File

@ -1,60 +1,4 @@
[
{
"name": "abs",
"summary": "Computes the absolute value of a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "acos",
"summary": "Computes the arccosine of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "angleToMatchLengthX",
"summary": "Returns the angle to match the given length for x.",
@ -7529,62 +7473,6 @@
"unpublished": false,
"deprecated": false
},
{
"name": "asin",
"summary": "Computes the arcsine of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "atan",
"summary": "Computes the arctangent of a number (in radians).",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "bezierCurve",
"summary": "Draw a bezier curve.",
@ -8558,34 +8446,6 @@
"unpublished": false,
"deprecated": false
},
{
"name": "ceil",
"summary": "Computes the smallest integer greater than or equal to a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "close",
"summary": "Close the current sketch.",
@ -9490,24 +9350,6 @@
"unpublished": false,
"deprecated": false
},
{
"name": "e",
"summary": "Return the value of Eulers number `e`.",
"description": "",
"tags": [],
"args": [],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "extrude",
"summary": "Extrudes by a given amount.",
@ -10102,34 +9944,6 @@
"unpublished": false,
"deprecated": false
},
{
"name": "floor",
"summary": "Computes the largest integer less than or equal to a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "getExtrudeWallTransform",
"summary": "Returns the extrude wall transform.",
@ -13201,161 +13015,9 @@
"unpublished": false,
"deprecated": false
},
{
"name": "ln",
"summary": "Computes the natural logarithm of the number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "log",
"summary": "Computes the logarithm of the number with respect to an arbitrary base.",
"description": "The result might not be correctly rounded owing to implementation details; `log2()` can produce more accurate results for base 2, and `log10()` can produce more accurate results for base 10.",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
{
"name": "base",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "log10",
"summary": "Computes the base 10 logarithm of the number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "log2",
"summary": "Computes the base 2 logarithm of the number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "max",
"summary": "Computes the maximum of the given arguments.",
"description": "",
"tags": [],
"args": [
{
"name": "args",
"type": "[number]",
"schema": {
"type": "array",
"items": {
"type": "number",
"format": "double"
}
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "min",
"summary": "Computes the minimum of the given arguments.",
"summary": "Returns the minimum of the given arguments.",
"description": "",
"tags": [],
"args": [
@ -13386,7 +13048,7 @@
},
{
"name": "pi",
"summary": "Return the value of `pi`. Archimedes constant (π).",
"summary": "Return the value of `pi`.",
"description": "",
"tags": [],
"args": [],
@ -13402,43 +13064,6 @@
"unpublished": false,
"deprecated": false
},
{
"name": "pow",
"summary": "Computes the number to a power.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
{
"name": "pow",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "segAng",
"summary": "Returns the angle of the segment.",
@ -15751,34 +15376,6 @@
"unpublished": false,
"deprecated": false
},
{
"name": "sqrt",
"summary": "Computes the square root of a number.",
"description": "",
"tags": [],
"args": [
{
"name": "num",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
}
],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "startSketchAt",
"summary": "Start a sketch at a given point.",
@ -16294,24 +15891,6 @@
"unpublished": false,
"deprecated": false
},
{
"name": "tau",
"summary": "Return the value of `tau`. The full circle constant (τ). Equal to 2π.",
"description": "",
"tags": [],
"args": [],
"returnValue": {
"name": "",
"type": "number",
"schema": {
"type": "number",
"format": "double"
},
"required": true
},
"unpublished": false,
"deprecated": false
},
{
"name": "xLine",
"summary": "Draw a line on the x-axis.",

View File

@ -5,8 +5,6 @@
## Table of Contents
* [Functions](#functions)
* [`abs`](#abs)
* [`acos`](#acos)
* [`angleToMatchLengthX`](#angleToMatchLengthX)
* [`angleToMatchLengthY`](#angleToMatchLengthY)
* [`angledLine`](#angledLine)
@ -16,15 +14,10 @@
* [`angledLineToX`](#angledLineToX)
* [`angledLineToY`](#angledLineToY)
* [`arc`](#arc)
* [`asin`](#asin)
* [`atan`](#atan)
* [`bezierCurve`](#bezierCurve)
* [`ceil`](#ceil)
* [`close`](#close)
* [`cos`](#cos)
* [`e`](#e)
* [`extrude`](#extrude)
* [`floor`](#floor)
* [`getExtrudeWallTransform`](#getExtrudeWallTransform)
* [`lastSegX`](#lastSegX)
* [`lastSegY`](#lastSegY)
@ -33,24 +26,16 @@
* [`legLen`](#legLen)
* [`line`](#line)
* [`lineTo`](#lineTo)
* [`ln`](#ln)
* [`log`](#log)
* [`log10`](#log10)
* [`log2`](#log2)
* [`max`](#max)
* [`min`](#min)
* [`pi`](#pi)
* [`pow`](#pow)
* [`segAng`](#segAng)
* [`segEndX`](#segEndX)
* [`segEndY`](#segEndY)
* [`segLen`](#segLen)
* [`show`](#show)
* [`sin`](#sin)
* [`sqrt`](#sqrt)
* [`startSketchAt`](#startSketchAt)
* [`tan`](#tan)
* [`tau`](#tau)
* [`xLine`](#xLine)
* [`xLineTo`](#xLineTo)
* [`yLine`](#yLine)
@ -59,46 +44,6 @@
## Functions
### abs
Computes the absolute value of a number.
```
abs(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### acos
Computes the arccosine of a number (in radians).
```
acos(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### angleToMatchLengthX
Returns the angle to match the given length for x.
@ -1387,46 +1332,6 @@ arc(data: ArcData, sketch_group: SketchGroup) -> SketchGroup
### asin
Computes the arcsine of a number (in radians).
```
asin(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### atan
Computes the arctangent of a number (in radians).
```
atan(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### bezierCurve
Draw a bezier curve.
@ -1592,26 +1497,6 @@ bezierCurve(data: BezierData, sketch_group: SketchGroup) -> SketchGroup
### ceil
Computes the smallest integer greater than or equal to a number.
```
ceil(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### close
Close the current sketch.
@ -1776,25 +1661,6 @@ cos(num: number) -> number
### e
Return the value of Eulers number `e`.
```
e() -> number
```
#### Arguments
#### Returns
* `number`
### extrude
Extrudes by a given amount.
@ -1904,26 +1770,6 @@ extrude(length: number, sketch_group: SketchGroup) -> ExtrudeGroup
### floor
Computes the largest integer less than or equal to a number.
```
floor(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### getExtrudeWallTransform
Returns the extrude wall transform.
@ -2512,110 +2358,9 @@ lineTo(data: LineToData, sketch_group: SketchGroup) -> SketchGroup
### ln
Computes the natural logarithm of the number.
```
ln(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### log
Computes the logarithm of the number with respect to an arbitrary base.
The result might not be correctly rounded owing to implementation details; `log2()` can produce more accurate results for base 2, and `log10()` can produce more accurate results for base 10.
```
log(num: number, base: number) -> number
```
#### Arguments
* `num`: `number`
* `base`: `number`
#### Returns
* `number`
### log10
Computes the base 10 logarithm of the number.
```
log10(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### log2
Computes the base 2 logarithm of the number.
```
log2(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### max
Computes the maximum of the given arguments.
```
max(args: [number]) -> number
```
#### Arguments
* `args`: `[number]`
#### Returns
* `number`
### min
Computes the minimum of the given arguments.
Returns the minimum of the given arguments.
@ -2635,7 +2380,7 @@ min(args: [number]) -> number
### pi
Return the value of `pi`. Archimedes constant (π).
Return the value of `pi`.
@ -2652,27 +2397,6 @@ pi() -> number
### pow
Computes the number to a power.
```
pow(num: number, pow: number) -> number
```
#### Arguments
* `num`: `number`
* `pow`: `number`
#### Returns
* `number`
### segAng
Returns the angle of the segment.
@ -3103,26 +2827,6 @@ sin(num: number) -> number
### sqrt
Computes the square root of a number.
```
sqrt(num: number) -> number
```
#### Arguments
* `num`: `number`
#### Returns
* `number`
### startSketchAt
Start a sketch at a given point.
@ -3234,25 +2938,6 @@ tan(num: number) -> number
### tau
Return the value of `tau`. The full circle constant (τ). Equal to 2π.
```
tau() -> number
```
#### Arguments
#### Returns
* `number`
### xLine
Draw a line on the x-axis.

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.8.2",
"version": "0.7.0",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.9.0",
@ -57,22 +57,20 @@
"zustand": "^4.1.4"
},
"scripts": {
"start": "BROWSER=none vite",
"start": "vite",
"build": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source \"$HOME/.cargo/env\" && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y && yarn build:wasm && vite build",
"build:local": "vite build",
"build:both": "vite build",
"build:both:local": "yarn build:wasm && vite build",
"pretest": "yarn remove-importmeta",
"test": "vitest --mode development",
"test:nowatch": "vitest run --mode development",
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests)",
"test:cov": "vitest run --coverage --mode development",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"simpleserver:ci": "http-server ./public --cors -p 3000 &",
"simpleserver": "http-server ./public --cors -p 3000",
"fmt": "prettier --write ./src",
"fmt-check": "prettier --check ./src",
"build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
"build:wasm": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt && yarn remove-importmeta",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
"wasm-prep": "rm -rf src/wasm-lib/pkg && mkdir src/wasm-lib/pkg && rm -rf src/wasm-lib/kcl/bindings",
"lint": "eslint --fix src",

View File

@ -1,42 +0,0 @@
## Alpha Users Expectations
### Welcome
First off, thank you so much for your interest in being a part of the closed Alpha program! We are thrilled to have others use our product and see what you build with it (and truthfully, how you break it too).
### KittyCAD Modeling App (KCMA)
What we are introducing to you is our KittyCAD Modeling App (KCMA). KCMA is a CAD application that expresses a hybrid style of traditional CAD interface along with a code-CAD interface. KCMA is a great way for us to test our own APIs as well as inspire others to develop their own applications.
### Why Code?
Plenty of you have professional CAD experience, and may not understand why coding your model would be helpful. The "code-CAD" paradigm isnt as popular as traditional CAD programs (SolidWorks, NX, CREO, OnShape, etc.), but it certainly has its benefits. Some benefits include:
- Automation and parametric design
- Customization and flexibility
- Algorithmic and generative design
- Reproducibility
- Easier integration with other tools
### Before You Use KCMA
Before you dive straight into the app, we wanted to lay some expectations out for you.
- KCMA is in early development. Kurt pitched the idea back in January, and the team has been working hard on it since then. KCMA has really basic CAD features for now, but we have plenty of features on our roadmap. Most of the features that you may be currently used to in your CAD workflow today will be available down the road.
- For a list of all scripting functions, please reference our [documentation](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md). For a basic rundown of our types, please reference [this document](https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/types.md).
- With that being said, we have created an external new features list in [GH Discussions](https://github.com/KittyCAD/modeling-app/discussions). For our current priority list, please click [here](https://github.com/KittyCAD/modeling-app/blob/main/public/roadmap.md). Please upvote any features in the GH Discussions page that you would like to see implemented first. We will prioritize the highest upvoted items or items that are foundational for other features on the list. You can also add your own, but we will review it to make sure its not a duplicate or its feasible for the current state of the app.
- Please report any and all bugs/issues you find. Even the smallest bugs are important! You can report them in a GH Issue [here](https://github.com/KittyCAD/modeling-app/issues/new). You are more than welcome to link your GH Issue in the **bugs** section of our Discord, but if you want to discuss the bug further, please keep that in the GH Issue thread. Please include the severity of the bug in your GH Issue ticket (High, Medium, or Low). If you are having trouble deciding what severity the bug is, use this guideline:
- **High:** The bug is blocking you from continuing.
- Example: Every time I click the extrude button with two faces selected, the app crashes.
- **Medium:** You can find a workaround to the problem, but it increases your time spent working or makes it unenjoyable.
- Example: When the app is full screen on Mac, the settings are not showing properly. It works if I have the app windowed.
- **Low:** The bug is annoying but doesnt affect workflow or block you from continuing (usually you can say “It would be nice if ___, but its not needed”)
- Example: It would be nice if the camera would orient normal to the sketching surface when I select a face/plane and click “sketch”.
- We want you all to be aware that we may reach out to you in regard to issues, bugs, problems, and satisfaction. This will typically be for further clarification so we can really nail things down.
### Discord
We will be using Discord a lot more now that the Alpha has been released to people outside of the company. Please feel free to discuss and talk with us in the **alpha users** section of the server. We highly encourage you to engage with us on Discord!
### Thank You!
Once again, from all of us to you, thank you for being a part of the closed Alpha. We are happy to chat with you all, hear your feedback, and see some of your projects!

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

742
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -16,14 +16,13 @@ tauri-build = { version = "1.4.0", features = [] }
[dependencies]
anyhow = "1"
kittycad = "0.2.25"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "1.4.1", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tauri = { version = "1.4.1", features = ["dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
tokio = { version = "1.32.0", features = ["time"] }
toml = "0.8.0"
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.

View File

@ -6,7 +6,6 @@ use std::io::Read;
use anyhow::Result;
use oauth2::TokenResponse;
use tauri::{InvokeError, Manager};
const DEFAULT_HOST: &str = "https://api.kittycad.io";
/// This command returns the a json string parse from a toml file at the path.
#[tauri::command]
@ -86,47 +85,6 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
Ok(token)
}
///This command returns the KittyCAD user info given a token.
/// The string returned from this method is the user info as a json string.
#[tauri::command]
async fn get_user(
token: Option<String>,
hostname: &str,
) -> Result<kittycad::types::User, InvokeError> {
// Use the host passed in if it's set.
// Otherwise, use the default host.
let host = if hostname.is_empty() {
DEFAULT_HOST.to_string()
} else {
hostname.to_string()
};
// Change the baseURL to the one we want.
let mut baseurl = host.to_string();
if !host.starts_with("http://") && !host.starts_with("https://") {
baseurl = format!("https://{host}");
if host.starts_with("localhost") {
baseurl = format!("http://{host}")
}
}
println!("Getting user info...");
// use kittycad library to fetch the user info from /user/me
let mut client = kittycad::Client::new(token.unwrap());
if baseurl != DEFAULT_HOST {
client.set_base_url(&baseurl);
}
let user_info: kittycad::types::User = client
.users()
.get_self()
.await
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
Ok(user_info)
}
fn main() {
tauri::Builder::default()
.setup(|app| {
@ -139,12 +97,7 @@ fn main() {
}
Ok(())
})
.invoke_handler(tauri::generate_handler![
get_user,
login,
read_toml,
read_txt_file
])
.invoke_handler(tauri::generate_handler![login, read_toml, read_txt_file])
.plugin(tauri_plugin_fs_extra::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");

View File

@ -8,7 +8,7 @@
},
"package": {
"productName": "kittycad-modeling",
"version": "0.8.2"
"version": "0.7.0"
},
"tauri": {
"allowlist": {
@ -36,9 +36,6 @@
"https://api.dev.kittycad.io/*"
]
},
"os": {
"all": true
},
"shell": {
"open": true
},
@ -74,9 +71,9 @@
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com"
"timestampUrl": ""
}
},
"security": {

View File

@ -1,4 +1,4 @@
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
import { useEffect, useCallback, MouseEventHandler } from 'react'
import { DebugPanel } from './components/DebugPanel'
import { v4 as uuidv4 } from 'uuid'
import { PaneType, useStore } from './useStore'
@ -29,13 +29,10 @@ import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/mod
import { CodeMenu } from 'components/CodeMenu'
import { TextEditor } from 'components/TextEditor'
import { Themes, getSystemTheme } from 'lib/theme'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
export function App() {
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
const streamRef = useRef<HTMLDivElement>(null)
useHotKeyListener()
const {
setCode,
@ -46,11 +43,8 @@ export function App() {
didDragInStream,
streamDimensions,
guiMode,
setGuiMode,
executeAst,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
setCode: s.setCode,
engineCommandManager: s.engineCommandManager,
buttonDownInStream: s.buttonDownInStream,
@ -58,13 +52,9 @@ export function App() {
setOpenPanes: s.setOpenPanes,
didDragInStream: s.didDragInStream,
streamDimensions: s.streamDimensions,
executeAst: s.executeAst,
}))
const {
auth: {
context: { token },
},
settings: {
context: { showDebugPanel, onboardingStatus, cameraControls, theme },
},
@ -85,58 +75,13 @@ export function App() {
useHotkeys('shift + l', () => togglePane('logs'))
useHotkeys('shift + e', () => togglePane('kclErrors'))
useHotkeys('shift + d', () => togglePane('debug'))
useHotkeys('esc', () => {
if (guiMode.mode === 'sketch') {
if (guiMode.sketchMode === 'selectFace') return
if (guiMode.sketchMode === 'sketchEdit') {
// TODO: share this with Toolbar's "Exit sketch" button
// exiting sketch should be done consistently across all exits
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
setGuiMode({ mode: 'default' })
// this is necessary to get the UI back into a consistent
// state right now, hopefully won't need to rerender
// when exiting sketch mode in the future
executeAst()
} else {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'set_tool',
tool: 'select',
},
})
setGuiMode({
mode: 'sketch',
sketchMode: 'sketchEdit',
rotation: guiMode.rotation,
position: guiMode.position,
pathToNode: guiMode.pathToNode,
pathId: guiMode.pathId,
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
})
}
} else {
setGuiMode({ mode: 'default' })
}
})
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
(p) => p === onboardingStatus
)
? 'opacity-20'
: didDragInStream
? 'opacity-40'
: ''
const paneOpacity =
onboardingStatus === onboardingPaths.CAMERA
? 'opacity-20'
: didDragInStream
? 'opacity-40'
: ''
// Use file code loaded from disk
// on mount, and overwrite any locally-stored code
@ -152,9 +97,6 @@ export function App() {
}
}, [loadedCode, setCode])
useSetupEngineManager(streamRef, token)
useEngineConnectionSubscriptions()
const debounceSocketSend = throttle<EngineCommand>((message) => {
engineCommandManager?.sendSceneCommand(message)
}, 16)
@ -234,9 +176,8 @@ export function App() {
return (
<div
className="h-screen overflow-hidden relative flex flex-col cursor-pointer select-none"
className="relative h-full flex flex-col"
onMouseMove={handleMouseMove}
ref={streamRef}
>
<AppHeader
className={
@ -269,7 +210,7 @@ export function App() {
'hover:bg-liquid-30/40 dark:hover:bg-liquid-10/40 bg-transparent transition-colors duration-100 transition-ease-out delay-100',
}}
>
<div id="code-pane" className="h-full flex flex-col justify-between">
<div className="h-full flex flex-col justify-between">
<CollapsiblePanel
title="Code"
icon={faCode}

View File

@ -6,9 +6,9 @@ export const Auth = ({ children }: React.PropsWithChildren) => {
const {
auth: { state },
} = useGlobalStateContext()
const isLoggingIn = state.matches('checkIfLoggedIn')
const isLoggedIn = state.matches('checkIfLoggedIn')
return isLoggingIn ? (
return isLoggedIn ? (
<Loading>Loading KittyCAD Modeling App...</Loading>
) : (
<>{children}</>

View File

@ -40,6 +40,7 @@ import { ContextFrom } from 'xstate'
import CommandBarProvider from 'components/CommandBar'
import { TEST, VITE_KC_SENTRY_DSN } from './env'
import * as Sentry from '@sentry/react'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
if (VITE_KC_SENTRY_DSN && !TEST) {
Sentry.init({
@ -130,17 +131,19 @@ const router = createBrowserRouter(
path: paths.INDEX,
loader: () =>
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
errorElement: <ErrorPage />,
},
{
path: paths.FILE + '/:id',
element: (
<Auth>
<Outlet />
<App />
<ModelingMachineProvider>
<App />
</ModelingMachineProvider>
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
</Auth>
),
errorElement: <ErrorPage />,
id: paths.FILE,
loader: async ({
request,

View File

@ -47,52 +47,6 @@
@apply hover:bg-cool-20;
}
.toolbarButtons::-webkit-scrollbar {
@apply h-0.5;
}
.toolbarButtons {
@apply flex items-center overflow-x-auto;
scrollbar-width: thin;
}
.toolbarButtons button {
@apply text-chalkboard-90 bg-chalkboard-10/50 border-chalkboard-50 whitespace-nowrap;
display: inline-flex;
align-items: center;
justify-content: center;
@apply gap-1.5 p-0.5 pr-1;
@apply rounded-sm;
}
:global(.dark) .toolbarButtons button {
@apply text-chalkboard-30 bg-chalkboard-90/50 border-chalkboard-50;
}
.toolbarButtons button:hover {
@apply text-cool-90 bg-cool-10;
}
:global(.sketch) .toolbarButtons button:hover {
@apply text-fern-90 bg-fern-10;
}
.toolbarButtons button:disabled {
@apply text-chalkboard-70 bg-chalkboard-30;
}
.toolbarButtons button:disabled:hover {
@apply !bg-inherit !text-inherit cursor-not-allowed;
}
:global(.dark) .toolbarButtons button {
@apply text-chalkboard-20 border-chalkboard-50;
}
:global(.dark) .toolbarButtons button:hover {
@apply text-cool-10 border-chalkboard-50 bg-cool-90;
}
:global(.dark .sketch) .toolbarButtons button:hover {
@apply text-fern-10 border-chalkboard-50 bg-fern-90;
}
:global(.dark) .toolbarButtons button:disabled {
@apply text-chalkboard-40 bg-chalkboard-80;
}
:global(.dark) .popoverToggle {
@apply hover:bg-cool-90;
}

View File

@ -1,4 +1,4 @@
import { useStore, toolTips, ToolTip } from './useStore'
import { useStore, toolTips, Selections } from './useStore'
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
import { getNodePathFromSourceRange } from './lang/queryAst'
import { HorzVert } from './components/Toolbar/HorzVert'
@ -17,30 +17,6 @@ import { Popover, Transition } from '@headlessui/react'
import styles from './Toolbar.module.css'
import { v4 as uuidv4 } from 'uuid'
import { useAppMode } from 'hooks/useAppMode'
import { ActionIcon } from 'components/ActionIcon'
export const sketchButtonClassnames = {
background:
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-fern-20 dark:group-hover:bg-fern-10 dark:hover:bg-fern-10 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50',
icon: 'text-fern-20 h-auto group-hover:text-fern-10 hover:text-fern-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-60 hover:group-disabled:text-inherit',
}
const sketchFnLabels: Record<ToolTip | 'sketch_line' | 'move', string> = {
sketch_line: 'Line',
line: 'Line',
move: 'Move',
angledLine: 'Angled Line',
angledLineThatIntersects: 'Angled Line That Intersects',
angledLineOfXLength: 'Angled Line Of X Length',
angledLineOfYLength: 'Angled Line Of Y Length',
angledLineToX: 'Angled Line To X',
angledLineToY: 'Angled Line To Y',
lineTo: 'Line to Point',
xLine: 'Horizontal Line',
yLine: 'Vertical Line',
xLineTo: 'Horizontal Line to Point',
yLineTo: 'Vertical Line to Point',
}
export const Toolbar = () => {
const {
@ -51,7 +27,6 @@ export const Toolbar = () => {
updateAst,
programMemory,
engineCommandManager,
executeAst,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
@ -60,7 +35,6 @@ export const Toolbar = () => {
updateAst: s.updateAst,
programMemory: s.programMemory,
engineCommandManager: s.engineCommandManager,
executeAst: s.executeAst,
}))
useAppMode()
@ -68,9 +42,9 @@ export const Toolbar = () => {
console.log('guiMode', guiMode)
}, [guiMode])
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
function ToolbarButtons() {
return (
<span className={styles.toolbarButtons + ' ' + className}>
<span className="overflow-x-auto">
{guiMode.mode === 'default' && (
<button
onClick={() => {
@ -79,9 +53,7 @@ export const Toolbar = () => {
sketchMode: 'selectFace',
})
}}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Start Sketch
</button>
)}
@ -98,33 +70,33 @@ export const Toolbar = () => {
pathToNode,
programMemory
)
updateAst(modifiedAst, true)
updateAst(modifiedAst)
}}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Sketch on Face
SketchOnFace
</button>
)}
{guiMode.mode === 'canEditSketch' && (
<button
onClick={() => {
const pathToNode = getNodePathFromSourceRange(
ast,
selectionRanges.codeBasedSelections[0].range
)
console.log('guiMode.pathId', guiMode.pathId)
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: guiMode.pathId,
},
})
setGuiMode({
mode: 'sketch',
sketchMode: 'enterSketchEdit',
pathToNode: pathToNode,
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathId: guiMode.pathId,
sketchMode: 'sketchEdit',
pathToNode: guiMode.pathToNode,
rotation: guiMode.rotation,
position: guiMode.position,
})
}}
className="group"
>
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
Edit Sketch
</button>
)}
@ -141,12 +113,10 @@ export const Toolbar = () => {
ast,
pathToNode
)
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
}}
className="group"
>
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude
ExtrudeSketch
</button>
<button
onClick={() => {
@ -160,12 +130,10 @@ export const Toolbar = () => {
pathToNode,
false
)
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
updateAst(modifiedAst, { focusPath: pathToExtrudeArg })
}}
className="group"
>
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
Extrude as new
ExtrudeSketch (w/o pipe)
</button>
</>
)}
@ -178,24 +146,9 @@ export const Toolbar = () => {
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
setGuiMode({ mode: 'default' })
executeAst()
}}
className="group"
>
<ActionIcon
icon="exit"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Exit sketch
</button>
)}
@ -236,25 +189,12 @@ export const Toolbar = () => {
sketchMode: sketchFnName,
waitingFirstClick: true,
isTooltip: true,
pathId: guiMode.pathId,
}),
})
}}
className={
'group ' +
(guiMode.sketchMode === sketchFnName
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
: '')
}
>
<ActionIcon
icon={sketchFnName.includes('line') ? 'line' : 'move'}
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
{sketchFnLabels[sketchFnName]}
{sketchFnName}
{guiMode.sketchMode === sketchFnName && ''}
</button>
)
})}
@ -285,7 +225,7 @@ export const Toolbar = () => {
<span className={styles.toolbarCap + ' ' + styles.label}>
{guiMode.mode === 'sketch' ? '2D' : '3D'}
</span>
<menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
<menu className="flex flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
<ToolbarButtons />
</menu>
<Popover.Button
@ -326,7 +266,7 @@ export const Toolbar = () => {
</Popover.Button>
</section>
<section>
<ToolbarButtons className="flex-wrap" />
<ToolbarButtons />
</section>
</Popover.Panel>
</Transition>

View File

@ -4,7 +4,6 @@ import {
} from '@fortawesome/free-solid-svg-icons'
import { IconDefinition as BrandIconDefinition } from '@fortawesome/free-brands-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { CustomIcon, CustomIconName } from './CustomIcon'
const iconSizes = {
sm: 12,
@ -14,7 +13,7 @@ const iconSizes = {
}
export interface ActionIconProps extends React.PropsWithChildren {
icon?: SolidIconDefinition | BrandIconDefinition | CustomIconName
icon?: SolidIconDefinition | BrandIconDefinition
className?: string
bgClassName?: string
iconClassName?: string
@ -29,39 +28,25 @@ export const ActionIcon = ({
size = 'md',
children,
}: ActionIconProps) => {
// By default, we reverse the icon color and background color in dark mode
const computedIconClassName =
iconClassName ||
`text-liquid-20 h-auto group-hover:text-liquid-10 hover:text-liquid-10 dark:text-chalkboard-100 dark:group-hover:text-chalkboard-100 dark:hover:text-chalkboard-100 group-disabled:bg-chalkboard-50 dark:group-disabled:bg-chalkboard-60 group-hover:group-disabled:bg-chalkboard-50 dark:group-hover:group-disabled:bg-chalkboard-50`
const computedBgClassName =
bgClassName ||
`bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-liquid-20 dark:group-hover:bg-liquid-10 dark:hover:bg-liquid-10 group-disabled:bg-chalkboard-80 dark:group-disabled:bg-chalkboard-80`
return (
<div
className={
`p-${
size === 'xl' ? '2' : '1'
} w-fit inline-grid place-content-center ${className} ` +
computedBgClassName
(bgClassName ||
'bg-chalkboard-100 group-hover:bg-chalkboard-90 hover:bg-chalkboard-90 dark:bg-liquid-20 dark:group-hover:bg-liquid-10 dark:hover:bg-liquid-10')
}
>
{children ? (
children
) : typeof icon === 'string' ? (
<CustomIcon
name={icon}
width={iconSizes[size]}
height={iconSizes[size]}
className={computedIconClassName}
/>
) : (
{children || (
<FontAwesomeIcon
icon={icon}
width={iconSizes[size]}
height={iconSizes[size]}
className={computedIconClassName}
className={
iconClassName ||
'text-liquid-20 h-auto group-hover:text-liquid-10 hover:text-liquid-10 dark:text-liquid-100 dark:group-hover:text-liquid-100 dark:hover:text-liquid-100'
}
/>
)}
</div>

View File

@ -29,7 +29,7 @@ export const AppHeader = ({
return (
<header
className={
(showToolbar ? 'w-full grid ' : 'flex justify-between ') +
(showToolbar ? 'grid ' : 'flex justify-between ') +
styles.header +
' overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' +
className
@ -38,7 +38,7 @@ export const AppHeader = ({
<ProjectSidebarMenu renderAsLink={!enableMenu} project={project} />
{/* Toolbar if the context deems it */}
{showToolbar && (
<div className="max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
<div className="max-w-4xl">
<Toolbar />
</div>
)}

View File

@ -1,15 +1,11 @@
import { Menu } from '@headlessui/react'
import { PropsWithChildren } from 'react'
import {
faArrowUpRightFromSquare,
faEllipsis,
} from '@fortawesome/free-solid-svg-icons'
import { faEllipsis } from '@fortawesome/free-solid-svg-icons'
import { ActionIcon } from './ActionIcon'
import { useStore } from 'useStore'
import styles from './CodeMenu.module.css'
import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { editorShortcutMeta } from './TextEditor'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
export const CodeMenu = ({ children }: PropsWithChildren) => {
const { formatCode } = useStore((s) => ({
@ -23,8 +19,7 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
<div
className="relative"
onClick={(e) => {
const target = e.target as HTMLElement
if (e.eventPhase === 3 && target.closest('a') === null) {
if (e.eventPhase === 3) {
e.stopPropagation()
e.preventDefault()
}
@ -57,24 +52,6 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
</button>
</Menu.Item>
)}
<Menu.Item>
<a
className={styles.button}
href="https://github.com/KittyCAD/modeling-app/blob/main/docs/kcl/std.md"
target="_blank"
rel="noopener noreferrer"
>
<span>Read the KCL docs</span>
<small>
On GitHub
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="ml-1 align-text-top"
width={12}
/>
</small>
</a>
</Menu.Item>
</Menu.Items>
</div>
</Menu>

View File

@ -62,7 +62,7 @@ export const CommandBarProvider = ({
const CommandBar = () => {
const { commands, commandBarOpen, setCommandBarOpen } = useCommandsContext()
useHotkeys(['meta+k', 'meta+/'], () => {
useHotkeys('meta+k', () => {
if (commands.length === 0) return
setCommandBarOpen(!commandBarOpen)
})
@ -221,10 +221,10 @@ const CommandBar = () => {
<Combobox
value={selectedCommand}
onChange={handleCommandSelection}
className="relative w-full max-w-xl p-2 mx-auto border rounded shadow-lg bg-chalkboard-10 dark:bg-chalkboard-100 dark:border-chalkboard-70"
className="rounded relative mx-auto p-2 bg-chalkboard-10 dark:bg-chalkboard-100 border dark:border-chalkboard-70 max-w-xl w-full shadow-lg"
as="div"
>
<div className="flex items-center gap-2">
<div className="flex gap-2 items-center">
<ActionIcon icon={faSearch} size="xl" className="rounded-sm" />
<div>
{inSubCommand && (
@ -235,7 +235,7 @@ const CommandBar = () => {
)}
<Combobox.Input
onChange={(event) => setQuery(event.target.value)}
className="w-full bg-transparent focus:outline-none"
className="bg-transparent focus:outline-none w-full"
onKeyDown={(event) => {
if (event.metaKey && event.key === 'k')
setCommandBarOpen(false)
@ -264,12 +264,12 @@ const CommandBar = () => {
/>
</div>
</div>
<Combobox.Options static className="overflow-y-auto max-h-96">
<Combobox.Options static className="max-h-96 overflow-y-auto">
{filteredCommands?.map((commandResult) => (
<Combobox.Option
key={commandResult.item.name}
value={commandResult}
className="px-2 py-1 my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90"
className="my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90 py-1 px-2"
>
<p>{commandResult.item.name}</p>
{(commandResult.item as SubCommand).description && (

View File

@ -1,161 +0,0 @@
export type CustomIconName =
| 'equal'
| 'exit'
| 'extrude'
| 'horizontal'
| 'line'
| 'move'
| 'parallel'
| 'sketch'
| 'vertical'
export const CustomIcon = ({
name,
...props
}: {
name: CustomIconName
} & React.SVGProps<SVGSVGElement>) => {
switch (name) {
case 'equal':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5 8.78V7H14.52V8.78H5ZM5 13.02V11.24H14.52V13.02H5Z"
fill="currentColor"
/>
</svg>
)
case 'exit':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17 10L3 10M3 10L6.5 6.5M3 10L6.5 13.5"
stroke="currentColor"
/>
</svg>
)
case 'extrude':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 3L10.3536 3.35355L12.3536 5.35355L11.6465 6.06066L10.5 4.91421V11.5854C11.0826 11.7913 11.5 12.3469 11.5 13C11.5 13.8284 10.8284 14.5 10 14.5C9.17157 14.5 8.5 13.8284 8.5 13C8.5 12.3469 8.91741 11.7913 9.5 11.5854V4.91421L8.35356 6.06066L7.64645 5.35355L9.64645 3.35355L10 3ZM1.95887 12.3282L8 8.63644V9.80838L2.91773 12.9142L10 17.2423L17.0823 12.9142L12 9.80838V8.63644L18.0411 12.3282L19 12.9142L19 14.9683H18V13.5253L10.5 18.1087V19.9683H9.5V18.1087L2 13.5253V14.9683H1L1 12.9142L1.95887 12.3282Z"
fill="currentColor"
/>
</svg>
)
case 'horizontal':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4 9.5H16V11.5H4V9.5Z"
fill="currentColor"
/>
</svg>
)
case 'line':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.5 6C16.3284 6 17 5.32843 17 4.5C17 3.67157 16.3284 3 15.5 3C14.6716 3 14 3.67157 14 4.5C14 4.73107 14.0522 4.94993 14.1456 5.14543L5.14543 14.1456C4.94993 14.0522 4.73107 14 4.5 14C3.67157 14 3 14.6716 3 15.5C3 16.3284 3.67157 17 4.5 17C5.32843 17 6 16.3284 6 15.5C6 15.2679 5.94729 15.0482 5.8532 14.852L14.852 5.8532C15.0482 5.94729 15.2679 6 15.5 6Z"
fill="currentColor"
/>
</svg>
)
case 'move':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M10 2.29289L10.3536 2.64645L12.3536 4.64645L11.6465 5.35355L10.5 4.20711V8V9.50001H12L15.7929 9.50001L14.6465 8.35356L15.3536 7.64645L17.3536 9.64645L17.7071 10L17.3536 10.3536L15.3536 12.3536L14.6465 11.6465L15.7929 10.5H12H10.5V12V15.7929L11.6465 14.6464L12.3536 15.3536L10.3536 17.3536L10 17.7071L9.64645 17.3536L7.64645 15.3536L8.35356 14.6464L9.50001 15.7929V12V10.5H8.00001H4.20712L5.35357 11.6465L4.64646 12.3536L2.64646 10.3536L2.29291 10L2.64646 9.64645L4.64646 7.64645L5.35357 8.35356L4.20712 9.50001H8.00001H9.50001V8V4.20711L8.35356 5.35355L7.64645 4.64645L9.64645 2.64645L10 2.29289Z"
fill="currentColor"
/>
</svg>
)
case 'parallel':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8 16V4H6V16H8ZM14 16V4H12V16H14Z"
fill="currentColor"
/>
</svg>
)
case 'sketch':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M14.8037 13.4035L15.5509 14.1635L16.3682 16.8386L13.5521 16.1346L12.8186 15.3885L14.8037 13.4035ZM14.1025 12.6903L12.1175 14.6754L3.48609 5.89624C2.94588 5.34678 2.94963 4.46456 3.49448 3.91971C4.04591 3.36828 4.94112 3.37208 5.48786 3.92817L14.1025 12.6903ZM6.20094 3.22709L16.4357 13.6371L17.5003 17.1216L17.8412 18.2376L16.7091 17.9546L13.0364 17.0364L2.77301 6.59732C1.84793 5.6564 1.85434 4.14564 2.78737 3.2126C3.73167 2.2683 5.26468 2.27481 6.20094 3.22709Z"
fill="currentColor"
/>
</svg>
)
case 'vertical':
return (
<svg
{...props}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11 4V16H9V4H11Z"
fill="currentColor"
/>
</svg>
)
}
}

View File

@ -30,11 +30,7 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
return (
<CollapsiblePanel
{...props}
className={
'!absolute overflow-hidden !h-auto bottom-5 right-5 ' + className
}
// header height, top-5, and bottom-5
style={{ maxHeight: 'calc(100% - 3rem - 1.25rem - 1.25rem)' }}
className={'!absolute !h-auto bottom-5 right-5 ' + className}
>
<section className="p-4 flex flex-col gap-4">
<Xyz

View File

@ -40,12 +40,12 @@ const DownloadAppBanner = () => {
</code>
, and isn't backed up anywhere! Visit{' '}
<a
href="https://kittycad.io/modeling-app/download"
href="https://github.com/KittyCAD/modeling-app/releases"
rel="noopener noreferrer"
target="_blank"
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
>
our website
our GitHub repository
</a>{' '}
to download the app for the best experience.
</p>

View File

@ -1,62 +1,8 @@
import { isTauri } from 'lib/isTauri'
import { useRouteError, isRouteErrorResponse } from 'react-router-dom'
import { ActionButton } from './ActionButton'
import {
faBug,
faHome,
faRefresh,
faTrash,
} from '@fortawesome/free-solid-svg-icons'
export const ErrorPage = () => {
let error = useRouteError()
console.error('error', error)
return (
<div className="flex flex-col items-center justify-center h-screen">
<section className="max-w-full xl:max-w-4xl mx-auto">
<h1 className="text-4xl mb-8 font-bold">
An unexpected error occurred
</h1>
{isRouteErrorResponse(error) && (
<p className="mb-8">
{error.status}: {error.data}
</p>
)}
<div className="flex justify-between gap-2 mt-6">
{isTauri() && (
<ActionButton Element="link" to={'/'} icon={{ icon: faHome }}>
Go Home
</ActionButton>
)}
<ActionButton
Element="button"
icon={{ icon: faRefresh }}
onClick={() => window.location.reload()}
>
Reload
</ActionButton>
<ActionButton
Element="button"
icon={{ icon: faTrash }}
onClick={() => {
window.localStorage.clear()
}}
>
Clear storage
</ActionButton>
<ActionButton
Element="link"
icon={{ icon: faBug }}
target="_blank"
rel="noopener noreferrer"
to="https://discord.com/channels/915388055236509727/1138967922614743060"
>
Report Bug
</ActionButton>
</div>
</section>
<h1 className="text-4xl font-bold">404</h1>
<p className="text-2xl font-bold">Page not found</p>
</div>
)
}

View File

@ -24,9 +24,6 @@ import {
StateFrom,
} from 'xstate'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { invoke } from '@tauri-apps/api'
import { isTauri } from 'lib/isTauri'
import { VITE_KC_API_BASE_URL } from 'env'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -111,7 +108,6 @@ export const GlobalStateProvider = ({
actions: {
goToSignInPage: () => {
navigate(paths.SIGN_IN)
logout()
},
goToIndexPage: () => {
@ -153,12 +149,10 @@ export const GlobalStateProvider = ({
export default GlobalStateProvider
export function logout() {
const url = withBaseUrl('/logout')
localStorage.removeItem(TOKEN_PERSIST_KEY)
return (
!isTauri() &&
fetch(withBaseUrl('/logout'), {
method: 'POST',
credentials: 'include',
})
)
return fetch(url, {
method: 'POST',
credentials: 'include',
})
}

View File

@ -24,11 +24,7 @@ export const MemoryPanel = ({
<CollapsiblePanel {...props}>
<div className="h-full relative">
<div className="absolute inset-0 flex flex-col items-start">
<div
className="overflow-y-auto h-full console-tile w-full"
style={{ marginBottom: 36 }}
>
{/* 36px is the height of PanelHeader */}
<div className=" h-full console-tile w-full">
<ReactJson
src={ProcessedMemory}
collapsed={1}

View File

@ -0,0 +1,106 @@
import { useMachine } from '@xstate/react'
import React, { createContext, useRef } from 'react'
import {
AnyStateMachine,
ContextFrom,
InterpreterFrom,
Prop,
StateFrom,
} from 'xstate'
import { modelingMachine } from 'machines/modelingMachine'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useCodeEval } from 'hooks/useCodeEval'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
context: ContextFrom<T>
send: Prop<InterpreterFrom<T>, 'send'>
}
export const ModelingMachineContext = createContext(
{} as MachineContext<typeof modelingMachine>
)
export const ModelingMachineProvider = ({
children,
}: {
children: React.ReactNode
}) => {
const {
auth: {
context: { token },
},
} = useGlobalStateContext()
const streamRef = useRef<HTMLDivElement>(null)
useSetupEngineManager(streamRef, token)
useCodeEval()
// const { commands } = useCommandsContext()
// Settings machine setup
// const retrievedSettings = useRef(
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
// )
// What should we persist from modeling state? Nothing?
// const persistedSettings = Object.assign(
// settingsMachine.initialState.context,
// JSON.parse(retrievedSettings.current) as Partial<
// (typeof settingsMachine)['context']
// >
// )
const [modelingState, modelingSend] = useMachine(modelingMachine, {
// context: persistedSettings,
actions: {
'Modify AST': () => {},
'Make selection horizontal': () => {},
'Make selection vertical': () => {},
'Update code selection cursors': () => {},
},
guards: {
'Can make selection horizontal': () => true,
'Can make selection vertical': () => true,
'Selection contains axis': () => true,
'Selection contains edge': () => true,
'Selection contains face': () => true,
'Selection contains line': () => true,
'Selection contains point': () => true,
'Selection is empty': () => true,
'Selection is not empty': () => true,
'Selection is one face': () => true,
'Selection is one or more edges': () => true,
},
services: {
createSketch: async () => {},
createLine: async () => {},
createExtrude: async () => {},
createFillet: async () => {},
},
})
// useStateMachineCommands({
// state: settingsState,
// send: settingsSend,
// commands,
// owner: 'settings',
// commandBarMeta: settingsCommandBarMeta,
// })
return (
<ModelingMachineContext.Provider
value={{
state: modelingState,
context: modelingState.context,
send: modelingSend,
}}
>
<div className="h-screen overflow-hidden select-none" ref={streamRef}>
{children}
</div>
</ModelingMachineContext.Provider>
)
}
export default ModelingMachineProvider

View File

@ -0,0 +1,42 @@
import { invoke } from '@tauri-apps/api/tauri'
import { open } from '@tauri-apps/api/dialog'
import { useStore } from '../useStore'
export const OpenFileButton = () => {
const { setCode } = useStore((s) => ({
setCode: s.setCode,
}))
const handleClick = async () => {
const selected = await open({
multiple: false,
directory: false,
filters: [
{
name: 'CAD',
extensions: ['toml'],
},
],
})
if (Array.isArray(selected)) {
// User selected multiple files
// We should not get here, since multiple is false.
} else if (selected === null) {
// User cancelled the selection
// Do nothing.
} else {
// User selected a single file
// We want to invoke our command to read the file.
const json: string = await invoke('read_toml', { path: selected })
const packageDetails = JSON.parse(json).package
if (packageDetails.main) {
const absPath = [
...selected.split('/').slice(0, -1),
packageDetails.main,
].join('/')
const file: string = await invoke('read_txt_file', { path: absPath })
setCode(file)
}
}
}
return <button onClick={() => handleClick()}>Open File</button>
}

View File

@ -16,8 +16,8 @@ const ProjectSidebarMenu = ({
}) => {
return renderAsLink ? (
<Link
to={paths.HOME}
className="h-9 max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
to={'../'}
className="flex items-center gap-4 my-2"
data-testid="project-sidebar-link"
>
<img
@ -26,7 +26,7 @@ const ProjectSidebarMenu = ({
className="h-9 w-auto"
/>
<span
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block"
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 min-w-max"
data-testid="project-sidebar-link-name"
>
{project?.name ? project.name : 'KittyCAD Modeling App'}
@ -35,15 +35,15 @@ const ProjectSidebarMenu = ({
) : (
<Popover className="relative">
<Popover.Button
className="h-9 max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
className="border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50"
data-testid="project-sidebar-toggle"
>
<img
src="/kitt-8bit-winking.svg"
alt="KittyCAD App"
className="h-full w-auto"
className="h-9 w-auto"
/>
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block">
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 min-w-max">
{isTauri() && project?.name ? project.name : 'KittyCAD Modeling App'}
</span>
</Popover.Button>

View File

@ -21,10 +21,6 @@ import {
} from 'lang/std/sketch'
import { getNodeFromPath } from 'lang/queryAst'
import { Program, VariableDeclarator } from 'lang/abstractSyntaxTreeTypes'
import { modify_ast_for_sketch } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from 'lang/errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { rangeTypeFix } from 'lang/abstractSyntaxTree'
export const Stream = ({ className = '' }) => {
const [isLoading, setIsLoading] = useState(true)
@ -215,75 +211,18 @@ export const Stream = ({ className = '' }) => {
}
}
engineCommandManager?.sendSceneCommand(command).then(async (resp) => {
if (!(guiMode.mode === 'sketch')) return
if (guiMode.sketchMode === 'selectFace') return
// Check if the sketch group already exists.
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
guiMode.pathToNode,
'VariableDeclarator'
).node
const variableName = varDec?.id?.name
const sketchGroup = programMemory.root[variableName]
const isEditingExistingSketch =
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
let sketchGroupId = ''
if (sketchGroup && sketchGroup.type === 'SketchGroup') {
sketchGroupId = sketchGroup.id
}
if (
guiMode.sketchMode === ('move' as any as 'line') &&
command.cmd.type === 'handle_mouse_drag_end'
) {
// Let's get the updated ast.
if (sketchGroupId === '') return
console.log('guiMode.pathId', guiMode.pathId)
// We have a problem if we do not have an id for the sketch group.
if (
guiMode.pathId === undefined ||
guiMode.pathId === null ||
guiMode.pathId === ''
)
return
let engineId = guiMode.pathId
try {
const updatedAst: Program = await modify_ast_for_sketch(
engineCommandManager,
JSON.stringify(ast),
variableName,
engineId
)
updateAst(updatedAst, false)
} catch (e: any) {
const parsed: RustKclError = JSON.parse(e.toString())
const kclError = new KCLError(
parsed.kind,
parsed.msg,
rangeTypeFix(parsed.sourceRanges)
)
console.log(kclError)
throw kclError
}
return
}
if (command?.cmd?.type !== 'mouse_click' || !ast) return
if (!(guiMode.sketchMode === ('sketch_line' as any as 'line'))) return
if (
!(
guiMode.mode === 'sketch' &&
guiMode.sketchMode === ('sketch_line' as any as 'line')
)
)
return
if (
resp?.data?.data?.entities_modified?.length &&
guiMode.waitingFirstClick &&
!isEditingExistingSketch
guiMode.waitingFirstClick
) {
const curve = await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
@ -306,25 +245,15 @@ export const Stream = ({ className = '' }) => {
const _modifiedAst = _addStartSketch.modifiedAst
const _pathToNode = _addStartSketch.pathToNode
// We need to update the guiMode with the right pathId so that we can
// move lines later and send the right sketch id to the engine.
for (const [id, artifact] of Object.entries(
engineCommandManager.artifactMap
)) {
if (artifact.commandType === 'start_path') {
guiMode.pathId = id
}
}
setGuiMode({
...guiMode,
pathToNode: _pathToNode,
waitingFirstClick: false,
})
updateAst(_modifiedAst, false)
updateAst(_modifiedAst)
} else if (
resp?.data?.data?.entities_modified?.length &&
(!guiMode.waitingFirstClick || isEditingExistingSketch)
!guiMode.waitingFirstClick
) {
const curve = await engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
@ -361,7 +290,6 @@ export const Stream = ({ className = '' }) => {
fnName: 'line',
pathToNode: guiMode.pathToNode,
}).modifiedAst
updateAst(_modifiedAst, false)
} else {
_modifiedAst = addCloseToPipe({
node: ast,
@ -371,18 +299,8 @@ export const Stream = ({ className = '' }) => {
setGuiMode({
mode: 'default',
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'default_camera_disable_sketch_mode' },
})
updateAst(_modifiedAst, true)
}
updateAst(_modifiedAst)
}
})
setDidDragInStream(false)
@ -415,7 +333,7 @@ export const Stream = ({ className = '' }) => {
onWheel={handleScroll}
onPlay={() => setIsLoading(false)}
onMouseMoveCapture={handleMouseMove}
className={`w-full h-full ${isExecuting && 'blur-md'}`}
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
/>
{isLoading && (

View File

@ -29,6 +29,7 @@ import {
import { isOverlap, roundOff } from 'lib/utils'
import { kclErrToDiagnostic } from 'lang/errors'
import { CSSRuleObject } from 'tailwindcss/types/config'
import { useModelingContext } from 'hooks/useModelingContext'
import interact from '@replit/codemirror-interact'
export const editorShortcutMeta = {
@ -50,7 +51,7 @@ export const TextEditor = ({
const pathParams = useParams()
const {
code,
deferredSetCode,
defferedSetCode,
editorView,
engineCommandManager,
formatCode,
@ -60,9 +61,10 @@ export const TextEditor = ({
setEditorView,
setIsLSPServerReady,
setSelectionRanges,
sourceRangeMap,
} = useStore((s) => ({
code: s.code,
deferredSetCode: s.deferredSetCode,
defferedSetCode: s.defferedSetCode,
editorView: s.editorView,
engineCommandManager: s.engineCommandManager,
formatCode: s.formatCode,
@ -72,8 +74,14 @@ export const TextEditor = ({
setEditorView: s.setEditorView,
setIsLSPServerReady: s.setIsLSPServerReady,
setSelectionRanges: s.setSelectionRanges,
sourceRangeMap: s.sourceRangeMap,
}))
const {
context: { selectionRanges: machineSelectionRanges },
send,
} = useModelingContext()
const {
settings: {
context: { textWrapping },
@ -124,7 +132,7 @@ export const TextEditor = ({
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
const onChange = (value: string, viewUpdate: ViewUpdate) => {
deferredSetCode(value)
defferedSetCode(value)
if (isTauri() && pathParams.id) {
// Save the file to disk
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
@ -172,11 +180,11 @@ export const TextEditor = ({
)
const idBasedSelections = codeBasedSelections
.map(({ type, range }) => {
const hasOverlap = Object.entries(
engineCommandManager?.sourceRangeMap || {}
).filter(([_, sourceRange]) => {
return isOverlap(sourceRange, range)
})
const hasOverlap = Object.entries(sourceRangeMap).filter(
([_, sourceRange]) => {
return isOverlap(sourceRange, range)
}
)
if (hasOverlap.length) {
return {
type,
@ -195,6 +203,14 @@ export const TextEditor = ({
otherSelections: [],
codeBasedSelections,
})
send({
type: 'Set selection',
data: {
...machineSelectionRanges,
codeBasedSelections,
},
})
}
const editorExtensions = useMemo(() => {

View File

@ -12,8 +12,6 @@ import {
getTransformInfos,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
export const EqualAngle = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
@ -84,22 +82,14 @@ export const EqualAngle = () => {
transformInfos,
programMemory,
})
updateAst(modifiedAst, true, {
updateAst(modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableEqual}
title="Parallel (or equal angle)"
className="group"
title="yo dawg"
>
<ActionIcon
icon="parallel"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Parallel
parallel
</button>
)
}

View File

@ -12,8 +12,6 @@ import {
getTransformInfos,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
export const EqualLength = () => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
@ -84,22 +82,14 @@ export const EqualLength = () => {
transformInfos,
programMemory,
})
updateAst(modifiedAst, true, {
updateAst(modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableEqual}
className="group"
title="Equal Length"
title="yo dawg"
>
<ActionIcon
icon="equal"
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
Equal Length
EqualLength
</button>
)
}

View File

@ -11,8 +11,6 @@ import {
transformAstSketchLines,
} from '../../lang/std/sketchcombos'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
export const HorzVert = ({
horOrVert,
@ -63,22 +61,14 @@ export const HorzVert = ({
programMemory,
referenceSegName: '',
})
updateAst(modifiedAst, true, {
updateAst(modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableHorz}
className="group"
title={horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
title="yo dawg"
>
<ActionIcon
icon={horOrVert === 'horizontal' ? 'horizontal' : 'vertical'}
className="!p-0.5"
bgClassName={sketchButtonClassnames.background}
iconClassName={sketchButtonClassnames.icon}
size="md"
/>
{horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
{horOrVert === 'horizontal' ? 'Horz' : 'Vert'}
</button>
)
}

View File

@ -154,7 +154,7 @@ export const Intersect = () => {
initialVariableName: 'offset',
} as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, {
updateAst(modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
@ -182,15 +182,14 @@ export const Intersect = () => {
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
updateAst(_modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
}}
disabled={!enable}
title="Set Perpendicular Distance"
>
Set Perpendicular Distance
perpendicularDistance
</button>
)
}

View File

@ -65,14 +65,14 @@ export const RemoveConstrainingValues = () => {
programMemory,
referenceSegName: '',
})
updateAst(modifiedAst, true, {
updateAst(modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}}
disabled={!enableHorz}
title="Remove Constraining Values"
title="yo dawg"
>
Remove Constraining Values
RemoveConstrainingValues
</button>
)
}

View File

@ -22,16 +22,11 @@ import { updateCursors } from '../../lang/util'
const getModalInfo = create(SetAngleLengthModal as any)
type ButtonType = 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
const buttonLabels: Record<ButtonType, string> = {
xAbs: 'Set distance from X Axis',
yAbs: 'Set distance from Y Axis',
snapToYAxis: 'Snap To Y Axis',
snapToXAxis: 'Snap To X Axis',
}
export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
export const SetAbsDistance = ({
buttonType,
}: {
buttonType: 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
guiMode: s.guiMode,
@ -129,7 +124,7 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
updateAst(_modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} catch (e) {
@ -137,9 +132,8 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
}
}}
disabled={!enableAngLen}
title={buttonLabels[buttonType]}
>
{buttonLabels[buttonType]}
{buttonType}
</button>
)
}

View File

@ -113,7 +113,7 @@ export const SetAngleBetween = () => {
initialVariableName: 'angle',
} as any)
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, {
updateAst(modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
@ -141,15 +141,14 @@ export const SetAngleBetween = () => {
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
updateAst(_modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
}}
disabled={!enable}
title="Set Angle Between"
>
Set Angle Between
angleBetween
</button>
)
}

View File

@ -21,28 +21,17 @@ import { GetInfoModal } from '../SetHorVertDistanceModal'
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { updateCursors } from '../../lang/util'
import { ActionIcon } from 'components/ActionIcon'
import { sketchButtonClassnames } from 'Toolbar'
const getModalInfo = create(GetInfoModal as any)
type ButtonType =
| 'setHorzDistance'
| 'setVertDistance'
| 'alignEndsHorizontally'
| 'alignEndsVertically'
const buttonLabels: Record<ButtonType, string> = {
setHorzDistance: 'Set Horizontal Distance',
setVertDistance: 'Set Vertical Distance',
alignEndsHorizontally: 'Align Ends Horizontally',
alignEndsVertically: 'Align Ends Vertically',
}
export const SetHorzVertDistance = ({
buttonType,
}: {
buttonType: ButtonType
buttonType:
| 'setHorzDistance'
| 'setVertDistance'
| 'alignEndsHorizontally'
| 'alignEndsVertically'
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
@ -148,7 +137,7 @@ export const SetHorzVertDistance = ({
constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
} as any))
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
updateAst(modifiedAst, true, {
updateAst(modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} else {
@ -174,15 +163,14 @@ export const SetHorzVertDistance = ({
)
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
updateAst(_modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
}
}}
disabled={!enable}
title={buttonLabels[buttonType]}
>
{buttonLabels[buttonType]}
{buttonType}
</button>
)
}

View File

@ -23,17 +23,10 @@ import { updateCursors } from '../../lang/util'
const getModalInfo = create(SetAngleLengthModal as any)
type ButtonType = 'setAngle' | 'setLength'
const buttonLabels: Record<ButtonType, string> = {
setAngle: 'Set Angle',
setLength: 'Set Length',
}
export const SetAngleLength = ({
angleOrLength,
}: {
angleOrLength: ButtonType
angleOrLength: 'setAngle' | 'setLength'
}) => {
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
useStore((s) => ({
@ -143,7 +136,7 @@ export const SetAngleLength = ({
_modifiedAst.body = newBody
}
updateAst(_modifiedAst, true, {
updateAst(_modifiedAst, {
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
})
} catch (e) {
@ -151,9 +144,8 @@ export const SetAngleLength = ({
}
}}
disabled={!enableAngLen}
title={buttonLabels[angleOrLength]}
>
{buttonLabels[angleOrLength]}
{angleOrLength}
</button>
)
}

View File

@ -2,7 +2,7 @@ import { Popover, Transition } from '@headlessui/react'
import { ActionButton } from './ActionButton'
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
import { useLocation, useNavigate } from 'react-router-dom'
import { useNavigate } from 'react-router-dom'
import { Fragment, useState } from 'react'
import { paths } from '../Router'
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
@ -12,7 +12,6 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
type User = Models['User_type']
const UserSidebarMenu = ({ user }: { user?: User }) => {
const location = useLocation()
const displayedName = getDisplayName(user)
const [imageLoadFailed, setImageLoadFailed] = useState(false)
const navigate = useNavigate()
@ -39,7 +38,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
<Popover className="relative">
{user?.image && !imageLoadFailed ? (
<Popover.Button
className="border-0 rounded-full w-fit min-w-max p-0 focus:outline-none group"
className="border-0 rounded-full w-fit p-0 focus:outline-none group"
data-testid="user-sidebar-toggle"
>
<div className="rounded-full border border-chalkboard-70/50 hover:border-liquid-50 group-focus:border-liquid-50 overflow-hidden">
@ -127,11 +126,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
// since /settings is a nested route the sidebar doesn't close
// automatically when navigating to it
close()
navigate(
(location.pathname.endsWith('/')
? location.pathname.slice(0, -1)
: location.pathname) + paths.SETTINGS
)
navigate(makeUrlPathRelative(paths.SETTINGS))
}}
>
Settings

View File

@ -5,6 +5,8 @@ import init, {
} from '../../wasm-lib/pkg/wasm_lib'
import { FromServer, IntoServer } from './codec'
let server: null | Server
export default class Server {
readonly initOutput: InitOutput
readonly #intoServer: IntoServer
@ -24,8 +26,12 @@ export default class Server {
intoServer: IntoServer,
fromServer: FromServer
): Promise<Server> {
const initOutput = await init()
const server = new Server(initOutput, intoServer, fromServer)
if (null == server) {
const initOutput = await init()
server = new Server(initOutput, intoServer, fromServer)
} else {
console.warn('Server already initialized; ignoring')
}
return server
}

View File

@ -11,9 +11,8 @@ import { isOverlap } from 'lib/utils'
interface DefaultPlanes {
xy: string
// TODO re-enable
// yz: string
// xz: string
yz: string
xz: string
}
export function useAppMode() {
@ -37,67 +36,40 @@ export function useAppMode() {
guiMode.sketchMode === 'selectFace' &&
engineCommandManager
) {
const createAndShowPlanes = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
setDefaultPlanes(localDefaultPlanes)
} else {
localDefaultPlanes = defaultPlanes
}
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, false)
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)
}
createAndShowPlanes()
}
if (
guiMode.mode === 'sketch' &&
guiMode.sketchMode === 'enterSketchEdit' &&
engineCommandManager
) {
const enableSketchMode = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
setDefaultPlanes(localDefaultPlanes)
} else {
localDefaultPlanes = defaultPlanes
}
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, true)
// TODO figure out the plane to use based on the sketch
// maybe it's easier to make a new plane than rely on the defaults
await engineCommandManager?.sendSceneCommand({
if (guiMode.mode !== 'sketch' && defaultPlanes) {
Object.values(defaultPlanes).forEach((planeId) => {
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: localDefaultPlanes.xy,
ortho: true,
animated: !isReducedMotion(),
type: 'object_visible',
object_id: planeId,
hidden: true,
},
})
const proms: any[] = []
proms.push(
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'edit_mode_enter',
target: guiMode.pathId,
},
})
)
await Promise.all(proms)
}
enableSketchMode()
setGuiMode({
...guiMode,
sketchMode: 'sketchEdit',
})
}
if (guiMode.mode !== 'sketch' && defaultPlanes) {
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
}
if (guiMode.mode === 'default') {
} else if (guiMode.mode === 'default') {
const pathId =
engineCommandManager &&
isCursorInSketchCommandRange(
@ -156,7 +128,7 @@ export function useAppMode() {
},
}
)
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
hideDefaultPlanes(engineCommandManager, defaultPlanes)
const sketchUuid = uuidv4()
const proms: any[] = []
proms.push(
@ -186,7 +158,6 @@ export function useAppMode() {
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [],
pathId: sketchUuid,
})
console.log('sketchModeResponse', sketchModeResponse)
@ -196,7 +167,7 @@ export function useAppMode() {
}, [engineCommandManager, defaultPlanes])
}
async function createPlane(
function createPlane(
engineCommandManager: EngineCommandManager,
{
x_axis,
@ -209,7 +180,7 @@ async function createPlane(
}
) {
const planeId = uuidv4()
await engineCommandManager?.sendSceneCommand({
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'make_plane',
@ -221,7 +192,7 @@ async function createPlane(
},
cmd_id: planeId,
})
await engineCommandManager?.sendSceneCommand({
engineCommandManager?.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'plane_set_color',
@ -233,10 +204,9 @@ async function createPlane(
return planeId
}
function setDefaultPlanesHidden(
engineCommandManager: EngineCommandManager | undefined,
defaultPlanes: DefaultPlanes,
hidden: boolean
function hideDefaultPlanes(
engineCommandManager: EngineCommandManager,
defaultPlanes: DefaultPlanes
) {
Object.values(defaultPlanes).forEach((planeId) => {
engineCommandManager?.sendSceneCommand({
@ -245,34 +215,12 @@ function setDefaultPlanesHidden(
cmd: {
type: 'object_visible',
object_id: planeId,
hidden: hidden,
hidden: true,
},
})
})
}
async function initDefaultPlanes(
engineCommandManager: EngineCommandManager
): Promise<DefaultPlanes> {
const xy = await 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 },
})
// TODO re-enable
// 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 },
// })
return { xy }
}
function isCursorInSketchCommandRange(
artifactMap: ArtifactMap,
selectionRanges: Selections
@ -281,17 +229,15 @@ function isCursorInSketchCommandRange(
([id, artifact]) =>
selectionRanges.codeBasedSelections.some(
(selection) =>
Array.isArray(selection?.range) &&
Array.isArray(artifact?.range) &&
Array.isArray(selection.range) &&
Array.isArray(artifact.range) &&
isOverlap(selection.range, artifact.range) &&
(artifact.commandType === 'start_path' ||
artifact.commandType === 'extend_path' ||
artifact.commandType === 'close_path')
'close_path')
)
)
return overlapingEntries.length && overlapingEntries[0][1].parentId
return overlapingEntries.length === 1 && overlapingEntries[0][1].parentId
? overlapingEntries[0][1].parentId
: overlapingEntries.find(
([, artifact]) => artifact.commandType === 'start_path'
)?.[0] || false
: false
}

156
src/hooks/useCodeEval.ts Normal file
View File

@ -0,0 +1,156 @@
import { useEffect } from 'react'
import { asyncParser } from '../lang/abstractSyntaxTree'
import { _executor } from '../lang/executor'
import { useStore } from '../useStore'
import { KCLError } from '../lang/errors'
// This recently moved out of app.tsx
// and is our old way of thinking that whenever the code changes we need to re-execute, instead of
// being more decisive about when and where we execute, its likey this custom hook will be
// refactored away entirely at some point
export function useCodeEval() {
const {
addLog,
addKCLError,
setAst,
setError,
setProgramMemory,
resetLogs,
resetKCLErrors,
setArtifactMap,
engineCommandManager,
highlightRange,
setHighlightRange,
setCursor2,
isStreamReady,
setIsExecuting,
defferedCode,
} = useStore((s) => ({
addLog: s.addLog,
defferedCode: s.defferedCode,
setAst: s.setAst,
setError: s.setError,
setProgramMemory: s.setProgramMemory,
resetLogs: s.resetLogs,
resetKCLErrors: s.resetKCLErrors,
setArtifactMap: s.setArtifactNSourceRangeMaps,
engineCommandManager: s.engineCommandManager,
highlightRange: s.highlightRange,
setHighlightRange: s.setHighlightRange,
setCursor2: s.setCursor2,
isStreamReady: s.isStreamReady,
addKCLError: s.addKCLError,
setIsExecuting: s.setIsExecuting,
}))
useEffect(() => {
if (!isStreamReady) return
if (!engineCommandManager) return
let unsubFn: any[] = []
const asyncWrap = async () => {
try {
if (!defferedCode) {
setAst({
start: 0,
end: 0,
body: [],
nonCodeMeta: {
noneCodeNodes: {},
start: null,
},
})
setProgramMemory({ root: {}, return: null })
engineCommandManager.endSession()
engineCommandManager.startNewSession()
return
}
const _ast = await asyncParser(defferedCode)
setAst(_ast)
resetLogs()
resetKCLErrors()
engineCommandManager.endSession()
engineCommandManager.startNewSession()
setIsExecuting(true)
const programMemory = await _executor(
_ast,
{
root: {
_0: {
type: 'UserVal',
value: 0,
__meta: [],
},
_90: {
type: 'UserVal',
value: 90,
__meta: [],
},
_180: {
type: 'UserVal',
value: 180,
__meta: [],
},
_270: {
type: 'UserVal',
value: 270,
__meta: [],
},
},
return: null,
},
engineCommandManager
)
const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands(_ast, programMemory)
setIsExecuting(false)
if (programMemory !== undefined) {
setProgramMemory(programMemory)
}
setArtifactMap({ artifactMap, sourceRangeMap })
const unSubHover = engineCommandManager.subscribeToUnreliable({
event: 'highlight_set_entity',
callback: ({ data }) => {
if (data?.entity_id) {
const sourceRange = sourceRangeMap[data.entity_id]
setHighlightRange(sourceRange)
} else if (
!highlightRange ||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
) {
setHighlightRange([0, 0])
}
},
})
const unSubClick = engineCommandManager.subscribeTo({
event: 'select_with_point',
callback: ({ data }) => {
if (!data?.entity_id) {
setCursor2()
return
}
const sourceRange = sourceRangeMap[data.entity_id]
setCursor2({ range: sourceRange, type: 'default' })
},
})
unsubFn.push(unSubHover, unSubClick)
setError()
} catch (e: any) {
setIsExecuting(false)
if (e instanceof KCLError) {
addKCLError(e)
} else {
setError('problem')
console.log(e)
addLog(e)
}
}
}
asyncWrap()
return () => {
unsubFn.forEach((fn) => fn())
}
}, [defferedCode, isStreamReady, engineCommandManager])
}

View File

@ -1,13 +0,0 @@
import { useLocation } from 'react-router-dom'
export function useDotDotSlash(): (count?: number) => string {
const location = useLocation()
const dotDotSlash = (count = 1): string => {
// since we can't use relative paths (../) for windows
if (location.pathname === '/') return ''
const path = location.pathname.slice(0, location.pathname.lastIndexOf('/'))
if (count <= 1) return path
return dotDotSlash(count - 1)
}
return dotDotSlash
}

View File

@ -1,50 +0,0 @@
import { useEffect } from 'react'
import { useStore } from 'useStore'
export function useEngineConnectionSubscriptions() {
const {
engineCommandManager,
setCursor2,
setHighlightRange,
highlightRange,
} = useStore((s) => ({
engineCommandManager: s.engineCommandManager,
setCursor2: s.setCursor2,
setHighlightRange: s.setHighlightRange,
highlightRange: s.highlightRange,
}))
useEffect(() => {
if (!engineCommandManager) return
const unSubHover = engineCommandManager.subscribeToUnreliable({
event: 'highlight_set_entity',
callback: ({ data }) => {
if (data?.entity_id) {
const sourceRange =
engineCommandManager.sourceRangeMap[data.entity_id]
setHighlightRange(sourceRange)
} else if (
!highlightRange ||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
) {
setHighlightRange([0, 0])
}
},
})
const unSubClick = engineCommandManager.subscribeTo({
event: 'select_with_point',
callback: ({ data }) => {
if (!data?.entity_id) {
setCursor2()
return
}
const sourceRange = engineCommandManager.sourceRangeMap[data.entity_id]
setCursor2({ range: sourceRange, type: 'default' })
},
})
return () => {
unSubHover()
unSubClick()
}
}, [engineCommandManager, setCursor2, setHighlightRange, highlightRange])
}

View File

@ -0,0 +1,6 @@
import { ModelingMachineContext } from 'components/ModelingMachineProvider'
import { useContext } from 'react'
export const useModelingContext = () => {
return useContext(ModelingMachineContext)
}

View File

@ -12,13 +12,11 @@ export function useSetupEngineManager(
setMediaStream,
setIsStreamReady,
setStreamDimensions,
executeCode,
} = useStore((s) => ({
setEngineCommandManager: s.setEngineCommandManager,
setMediaStream: s.setMediaStream,
setIsStreamReady: s.setIsStreamReady,
setStreamDimensions: s.setStreamDimensions,
executeCode: s.executeCode,
}))
const streamWidth = streamRef?.current?.offsetWidth
@ -43,9 +41,6 @@ export function useSetupEngineManager(
token,
})
setEngineCommandManager(eng)
eng.waitForReady.then(() => {
executeCode()
})
return () => {
eng?.tearDown()
}

View File

@ -46,7 +46,7 @@ export function useConvertToVariable() {
variableName
)
updateAst(_modifiedAst, true)
updateAst(_modifiedAst)
} catch (e) {
console.log('e', e)
}

View File

@ -31,14 +31,6 @@ body.dark {
@apply text-chalkboard-10;
}
select {
@apply bg-chalkboard-20;
}
.dark select {
@apply bg-chalkboard-90;
}
::-webkit-scrollbar {
@apply w-2 h-2 rounded-sm;
@apply bg-chalkboard-20;

View File

@ -139,6 +139,54 @@ const newVar = myVar + 1
},
])
})
test('using std function "log"', () => {
const code = `log(5, "hello", aIdentifier)`
const { body } = parser_wasm(code)
expect(body).toEqual([
{
type: 'ExpressionStatement',
start: 0,
end: 28,
expression: {
type: 'CallExpression',
start: 0,
end: 28,
callee: {
type: 'Identifier',
start: 0,
end: 3,
name: 'log',
},
arguments: [
{
type: 'Literal',
start: 4,
end: 5,
value: 5,
raw: '5',
},
{
type: 'Literal',
start: 7,
end: 14,
value: 'hello',
raw: '"hello"',
},
{
type: 'Identifier',
start: 16,
end: 27,
name: 'aIdentifier',
},
],
function: {
type: 'InMemory',
},
optional: false,
},
},
])
})
})
describe('testing function declaration', () => {
@ -1512,7 +1560,7 @@ const yo = { a: { b: { c: '123' } } }
// this is a comment
const key = 'c'`
const nonCodeMetaInstance = {
type: 'NonCodeNode',
type: 'NoneCodeNode',
start: code.indexOf('\n// this is a comment'),
end: code.indexOf('const key'),
value: {
@ -1521,17 +1569,17 @@ const key = 'c'`
},
}
const { nonCodeMeta } = parser_wasm(code)
expect(nonCodeMeta.nonCodeNodes[0]).toEqual(nonCodeMetaInstance)
expect(nonCodeMeta.noneCodeNodes[0]).toEqual(nonCodeMetaInstance)
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
const codeWithExtraStartWhitespace = '\n\n\n' + code
const { nonCodeMeta: nonCodeMeta2 } = parser_wasm(
codeWithExtraStartWhitespace
)
expect(nonCodeMeta2.nonCodeNodes[0].value).toStrictEqual(
expect(nonCodeMeta2.noneCodeNodes[0].value).toStrictEqual(
nonCodeMetaInstance.value
)
expect(nonCodeMeta2.nonCodeNodes[0].start).not.toBe(
expect(nonCodeMeta2.noneCodeNodes[0].start).not.toBe(
nonCodeMetaInstance.start
)
})
@ -1548,9 +1596,9 @@ const key = 'c'`
const { body } = parser_wasm(code)
const indexOfSecondLineToExpression = 2
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
.nonCodeNodes
.noneCodeNodes
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
type: 'NonCodeNode',
type: 'NoneCodeNode',
start: 106,
end: 166,
value: {
@ -1571,9 +1619,9 @@ const key = 'c'`
const { body } = parser_wasm(code)
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
.nonCodeNodes
.noneCodeNodes
expect(sketchNonCodeMeta[3]).toEqual({
type: 'NonCodeNode',
type: 'NoneCodeNode',
start: 125,
end: 141,
value: {

View File

@ -33,5 +33,5 @@ export type SyntaxType =
| 'PipeExpression'
| 'PipeSubstitution'
| 'Literal'
| 'NonCodeNode'
| 'NoneCodeNode'
| 'UnaryExpression'

View File

@ -106,7 +106,7 @@ describe('Testing addSketchTo', () => {
body: [],
start: 0,
end: 0,
nonCodeMeta: { nonCodeNodes: {}, start: null },
nonCodeMeta: { noneCodeNodes: {}, start: null },
},
'yz'
)

View File

@ -1,4 +1,4 @@
import { Selection, ToolTip } from '../useStore'
import { Selection, TooTip } from '../useStore'
import {
Program,
CallExpression,
@ -305,11 +305,7 @@ export function extrudeSketch(
}
const name = findUniqueName(node, 'part')
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
let showCallIndex = getShowIndex(_node)
if (showCallIndex == -1) {
// We didn't find a show, so let's just append everything
showCallIndex = _node.body.length
}
const showCallIndex = getShowIndex(_node)
_node.body.splice(showCallIndex, 0, VariableDeclaration)
const pathToExtrudeArg: PathToNode = [
['body', ''],
@ -537,7 +533,7 @@ export function createPipeExpression(
start: 0,
end: 0,
body,
nonCodeMeta: { nonCodeNodes: {}, start: null },
nonCodeMeta: { noneCodeNodes: {}, start: null },
}
}
@ -639,7 +635,7 @@ export function giveSketchFnCallTag(
createLiteral(tag || findUniqueName(ast, 'seg', 2))) as Literal
const tagStr = String(tagValue.value)
const newFirstArg = createFirstArg(
primaryCallExp.callee.name as ToolTip,
primaryCallExp.callee.name as TooTip,
firstArg.val,
tagValue
)

View File

@ -1,5 +1,5 @@
import { PathToNode, ProgramMemory, SketchGroup, SourceRange } from './executor'
import { Selection, ToolTip } from '../useStore'
import { Selection, TooTip } from '../useStore'
import {
BinaryExpression,
Program,
@ -457,7 +457,7 @@ export function isLinesParallelAndConstrained(
const secondaryFirstArg = getFirstArg(secondaryNode)
const constraintType = getConstraintType(
secondaryFirstArg.val,
secondaryNode.callee.name as ToolTip
secondaryNode.callee.name as TooTip
)
const constraintLevel = getConstraintLevelFromSourceRange(
secondaryLine.range,

View File

@ -15,13 +15,9 @@ interface CommandInfo {
range: SourceRange
parentId?: string
}
type WebSocketResponse = Models['OkWebSocketResponseData_type']
interface ResultCommand extends CommandInfo {
type: 'result'
data: any
raw: WebSocketResponse
}
interface PendingCommand extends CommandInfo {
type: 'pending'
@ -41,6 +37,8 @@ interface NewTrackArgs {
mediaStream: MediaStream
}
type WebSocketResponse = Models['OkWebSocketResponseData_type']
type ClientMetrics = Models['ClientMetrics_type']
// EngineConnection encapsulates the connection(s) to the Engine
@ -397,6 +395,8 @@ export class EngineConnection {
let videoTrack = mediaStream.getVideoTracks()[0]
this.pc?.getStats(videoTrack).then((videoTrackStats) => {
// TODO(paultag): this needs type information from the KittyCAD typescript
// library once it's updated
let client_metrics: ClientMetrics = {
rtc_frames_decoded: 0,
rtc_frames_dropped: 0,
@ -424,13 +424,12 @@ export class EngineConnection {
videoTrackReport.framesReceived
client_metrics.rtc_frames_per_second =
videoTrackReport.framesPerSecond || 0
client_metrics.rtc_freeze_count =
videoTrackReport.freezeCount || 0
client_metrics.rtc_freeze_count = videoTrackReport.freezeCount
client_metrics.rtc_jitter_sec = videoTrackReport.jitter
client_metrics.rtc_keyframes_decoded =
videoTrackReport.keyFramesDecoded
client_metrics.rtc_total_freezes_duration_sec =
videoTrackReport.totalFreezesDuration || 0
videoTrackReport.totalFreezesDuration
} else if (videoTrackReport.type === 'transport') {
// videoTrackReport.bytesReceived,
// videoTrackReport.bytesSent,
@ -476,13 +475,6 @@ export class EngineConnection {
this.onConnectionStarted(this)
}
unreliableSend(message: object | string) {
// TODO(paultag): Add in logic to determine the connection state and
// take actions if needed?
this.unreliableDataChannel?.send(
typeof message === 'string' ? message : JSON.stringify(message)
)
}
send(message: object | string) {
// TODO(paultag): Add in logic to determine the connection state and
// take actions if needed?
@ -654,14 +646,12 @@ export class EngineCommandManager {
commandType: command.commandType,
parentId: command.parentId ? command.parentId : undefined,
data: modelingResponse,
raw: message,
}
resolve({
id,
commandType: command.commandType,
range: command.range,
data: modelingResponse,
raw: message,
})
} else {
this.artifactMap[id] = {
@ -669,7 +659,6 @@ export class EngineCommandManager {
commandType: command?.commandType,
range: command?.range,
data: modelingResponse,
raw: message,
}
}
}
@ -789,7 +778,9 @@ export class EngineCommandManager {
) {
cmd.sequence = this.outSequence
this.outSequence++
this.engineConnection?.unreliableSend(command)
this.engineConnection?.unreliableDataChannel?.send(
JSON.stringify(command)
)
return Promise.resolve()
} else if (
cmd.type === 'highlight_set_entity' &&
@ -797,7 +788,9 @@ export class EngineCommandManager {
) {
cmd.sequence = this.outSequence
this.outSequence++
this.engineConnection?.unreliableSend(command)
this.engineConnection?.unreliableDataChannel?.send(
JSON.stringify(command)
)
return Promise.resolve()
} else if (
cmd.type === 'mouse_move' &&
@ -805,7 +798,9 @@ export class EngineCommandManager {
) {
cmd.sequence = this.outSequence
this.outSequence++
this.engineConnection?.unreliableSend(command)
this.engineConnection?.unreliableDataChannel?.send(
JSON.stringify(command)
)
return Promise.resolve()
}
// since it's not mouse drag or highlighting send over TCP and keep track of the command
@ -878,10 +873,7 @@ export class EngineCommandManager {
}
const range: SourceRange = JSON.parse(rangeStr)
// We only care about the modeling command response.
return this.sendModelingCommand({ id, range, command: commandStr }).then(
({ raw }) => JSON.stringify(raw)
)
return this.sendModelingCommand({ id, range, command: commandStr })
}
commandResult(id: string): Promise<any> {
const command = this.artifactMap[id]
@ -916,14 +908,14 @@ export class EngineCommandManager {
}
private async fixIdMappings(ast: Program, programMemory: ProgramMemory) {
/* This is a temporary solution since the cmd_ids that are sent through when
sending 'extend_path' ids are not used as the segment ids.
sending 'extend_path' ids are not used as the segment ids.
We have a way to back fill them with 'path_get_info', however this relies on one
the sketchGroup array and the segements array returned from the server to be in
the same length and order. plus it's super hacky, we first use the path_id to get
the source range of the pipe expression then use the name of the variable to get
the sketchGroup from programMemory.
I feel queezy about relying on all these steps to always line up.
We have also had to pollute this EngineCommandManager class with knowledge of both the ast and programMemory
We should get the cmd_ids to match with the segment ids and delete this method.
@ -951,6 +943,7 @@ export class EngineCommandManager {
pathInfos.forEach(({ originalId, segments }) => {
const originalArtifact = this.artifactMap[originalId]
if (!originalArtifact || originalArtifact.type === 'pending') {
console.log('problem')
return
}
const pipeExpPath = getNodePathFromSourceRange(
@ -963,20 +956,23 @@ export class EngineCommandManager {
'VariableDeclarator'
).node
if (pipeExp.type !== 'VariableDeclarator') {
console.log('problem', pipeExp, pipeExpPath, ast)
return
}
const variableName = pipeExp.id.name
const memoryItem = programMemory.root[variableName]
if (!memoryItem) {
console.log('problem', variableName, programMemory)
return
} else if (memoryItem.type !== 'SketchGroup') {
console.log('problem', memoryItem, programMemory)
return
}
const relevantSegments = segments.filter(
({ command_id }: { command_id: string | null }) => command_id
)
if (memoryItem.value.length !== relevantSegments.length) {
console.log('problem', memoryItem.value, relevantSegments)
return
}
for (let i = 0; i < relevantSegments.length; i++) {
@ -986,10 +982,8 @@ export class EngineCommandManager {
const artifact = this.artifactMap[oldId]
delete this.artifactMap[oldId]
delete this.sourceRangeMap[oldId]
if (artifact) {
this.artifactMap[engineSegment.command_id] = artifact
this.sourceRangeMap[engineSegment.command_id] = artifact.range
}
this.artifactMap[engineSegment.command_id] = artifact
this.sourceRangeMap[engineSegment.command_id] = artifact.range
}
})
}

View File

@ -117,7 +117,6 @@ show(mySketch001)
{
mode: 'sketch',
sketchMode: 'sketchEdit',
pathId: '',
rotation: [0, 0, 0, 1],
position: [0, 0, 0],
pathToNode: [

View File

@ -21,7 +21,7 @@ import {
getNodePathFromSourceRange,
} from '../queryAst'
import { isLiteralArrayOrStatic } from './sketchcombos'
import { GuiModes, toolTips, ToolTip } from '../../useStore'
import { GuiModes, toolTips, TooTip } from '../../useStore'
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
import { generateUuidFromHashSeed } from '../../lib/uuid'
@ -57,7 +57,7 @@ export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
}
export function createFirstArg(
sketchFn: ToolTip,
sketchFn: TooTip,
val: Value | [Value, Value] | [Value, Value, Value],
tag?: Value
): Value {
@ -943,7 +943,7 @@ interface CreateLineFnCallArgs {
programMemory: ProgramMemory
to: [number, number]
from: [number, number]
fnName: ToolTip
fnName: TooTip
pathToNode: PathToNode
}
@ -1029,7 +1029,7 @@ export function replaceSketchLine({
node: Program
programMemory: ProgramMemory
sourceRange: SourceRange
fnName: ToolTip
fnName: TooTip
to: [number, number]
from: [number, number]
createCallback: TransformCallback
@ -1208,7 +1208,7 @@ function getFirstArgValuesForAngleFns(callExpression: CallExpression): {
const tag = firstArg.properties.find((p) => p.key.name === 'tag')?.value
const angle = firstArg.properties.find((p) => p.key.name === 'angle')?.value
const secondArgName = ['angledLineToX', 'angledLineToY'].includes(
callExpression?.callee?.name as ToolTip
callExpression?.callee?.name as TooTip
)
? 'to'
: 'length'

View File

@ -1,4 +1,4 @@
import { ToolTip, toolTips } from '../../useStore'
import { TooTip, toolTips } from '../../useStore'
import {
Program,
VariableDeclarator,
@ -67,10 +67,7 @@ export function isSketchVariablesLinked(
return false
const firstCallExp = // first in pipe expression or just the call expression
init?.type === 'CallExpression' ? init : (init?.body[0] as CallExpression)
if (
!firstCallExp ||
!toolTips.includes(firstCallExp?.callee?.name as ToolTip)
)
if (!firstCallExp || !toolTips.includes(firstCallExp?.callee?.name as TooTip))
return false
// convention for sketch fns is that the second argument is the sketch group
const secondArg = firstCallExp?.arguments[1]

View File

@ -9,7 +9,7 @@ import {
getConstraintLevelFromSourceRange,
} from './sketchcombos'
import { initPromise } from '../rust'
import { Selections, ToolTip } from '../../useStore'
import { Selections, TooTip } from '../../useStore'
import { enginelessExecutor } from '../../lib/testHelpers'
import { recast } from '../../lang/recast'
@ -68,7 +68,7 @@ function getConstraintTypeFromSourceHelper(
Value,
Value
]
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
return getConstraintType(args, fnName)
}
function getConstraintTypeFromSourceHelper2(
@ -76,7 +76,7 @@ function getConstraintTypeFromSourceHelper2(
): ReturnType<typeof getConstraintType> {
const ast = parser_wasm(code)
const arg = (ast.body[0] as any).expression.arguments[0] as Value
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
const fnName = (ast.body[0] as any).expression.callee.name as TooTip
return getConstraintType(arg, fnName)
}

View File

@ -1,5 +1,5 @@
import { TransformCallback } from './stdTypes'
import { Selections, toolTips, ToolTip, Selection } from '../../useStore'
import { Selections, toolTips, TooTip, Selection } from '../../useStore'
import {
CallExpression,
Program,
@ -54,7 +54,7 @@ export type ConstraintType =
| 'setAngleBetween'
function createCallWrapper(
a: ToolTip,
a: TooTip,
val: [Value, Value] | Value,
tag?: Value,
valueUsedInTransform?: number
@ -101,7 +101,7 @@ function intersectCallWrapper({
}
export type TransformInfo = {
tooltip: ToolTip
tooltip: TooTip
createNode: (a: {
varValA: Value // x / angle
varValB: Value // y / length or x y for angledLineOfXlength etc
@ -112,7 +112,7 @@ export type TransformInfo = {
}
type TransformMap = {
[key in ToolTip]?: {
[key in TooTip]?: {
[key in LineInputsType | 'free']?: {
[key in ConstraintType]?: TransformInfo
}
@ -1095,12 +1095,12 @@ export function getRemoveConstraintsTransform(
sketchFnExp: CallExpression,
constraintType: ConstraintType
): TransformInfo | false {
let name = sketchFnExp.callee.name as ToolTip
let name = sketchFnExp.callee.name as TooTip
if (!toolTips.includes(name)) {
return false
}
const xyLineMap: {
[key in ToolTip]?: ToolTip
[key in TooTip]?: TooTip
} = {
xLine: 'line',
yLine: 'line',
@ -1167,12 +1167,12 @@ function getTransformMapPath(
constraintType: ConstraintType
):
| {
toolTip: ToolTip
toolTip: TooTip
lineInputType: LineInputsType | 'free'
constraintType: ConstraintType
}
| false {
const name = sketchFnExp.callee.name as ToolTip
const name = sketchFnExp.callee.name as TooTip
if (!toolTips.includes(name)) {
return false
}
@ -1225,7 +1225,7 @@ export function getTransformInfo(
export function getConstraintType(
val: Value | [Value, Value] | [Value, Value, Value],
fnName: ToolTip
fnName: TooTip
): LineInputsType | null {
// this function assumes that for two val sketch functions that one arg is locked down not both
// and for one val sketch functions that the arg is NOT locked down
@ -1445,7 +1445,7 @@ export function transformAstSketchLines({
programMemory,
sourceRange: range,
referencedSegment,
fnName: transformTo || (callExp.callee.name as ToolTip),
fnName: transformTo || (callExp.callee.name as TooTip),
to,
from,
createCallback: callBack({
@ -1511,7 +1511,7 @@ export function getConstraintLevelFromSourceRange(
getNodePathFromSourceRange(ast, cursorRange),
'CallExpression'
)
const name = sketchFnExp?.callee?.name as ToolTip
const name = sketchFnExp?.callee?.name as TooTip
if (!toolTips.includes(name)) return 'free'
const firstArg = getFirstArg(sketchFnExp)

View File

@ -1,6 +1,6 @@
import { ProgramMemory, Path, SourceRange } from '../executor'
import { Program, Value } from '../abstractSyntaxTreeTypes'
import { ToolTip } from '../../useStore'
import { TooTip } from '../../useStore'
import { PathToNode } from '../executor'
import { EngineCommandManager } from './engineConnection'
@ -45,7 +45,7 @@ export type TransformCallback = (
}
export type SketchCallTransfromMap = {
[key in ToolTip]: TransformCallback
[key in TooTip]: TransformCallback
}
export interface SketchLineHelper {

View File

@ -1,21 +0,0 @@
export const bracket = `// Material: 6061-T6 Aluminum
const sigmaAllow = 35000 // psi
const width = 9 // inch
const p = 150 // Force on shelf - lbs
const distance = 6 // inches
const FOS = 2
const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness = sqrt(distance * p * FOS * 6 / sigmaAllow / width) // inches
const bracket = startSketchAt([0, 0])
|> line([0, leg1], %)
|> line([leg2, 0], %)
|> line([0, -thickness], %)
|> line([-leg2 + thickness, 0], %)
|> line([0, -leg1 + thickness], %)
|> close(%)
|> extrude(width, %)
show(bracket)
`

View File

@ -57,7 +57,7 @@ export function throttle<T>(
}
// takes a function and executes it after the wait time, if the function is called again before the wait time is up, the timer is reset
export function deferExecution<T>(func: (args: T) => any, wait: number) {
export function defferExecution<T>(func: (args: T) => any, wait: number) {
let timeout: ReturnType<typeof setTimeout> | null
let latestArgs: T
@ -66,7 +66,7 @@ export function deferExecution<T>(func: (args: T) => any, wait: number) {
func(latestArgs)
}
function deferred(args: T) {
function deffered(args: T) {
latestArgs = args
if (timeout) {
clearTimeout(timeout)
@ -74,7 +74,7 @@ export function deferExecution<T>(func: (args: T) => any, wait: number) {
timeout = setTimeout(later, wait)
}
return deferred
return deffered
}
export function getNormalisedCoordinates({

View File

@ -2,9 +2,6 @@ import { createMachine, assign } from 'xstate'
import { Models } from '@kittycad/lib'
import withBaseURL from '../lib/withBaseURL'
import { CommandBarMeta } from '../lib/commands'
import { isTauri } from 'lib/isTauri'
import { invoke } from '@tauri-apps/api'
import { VITE_KC_API_BASE_URL } from 'env'
const SKIP_AUTH =
import.meta.env.VITE_KC_SKIP_AUTH === 'true' && import.meta.env.DEV
@ -118,26 +115,16 @@ async function getUser(context: UserContext) {
const headers: { [key: string]: string } = {
'Content-Type': 'application/json',
}
if (!context.token && isTauri()) throw new Error('No token found')
if (!context.token && '__TAURI__' in window) throw 'not log in'
if (context.token) headers['Authorization'] = `Bearer ${context.token}`
if (SKIP_AUTH) return LOCAL_USER
const response = await fetch(url, {
method: 'GET',
credentials: 'include',
headers,
})
const userPromise = !isTauri()
? fetch(url, {
method: 'GET',
credentials: 'include',
headers,
})
.then((res) => res.json())
.catch((err) => console.error('error from Browser getUser', err))
: invoke<Models['User_type'] | Record<'error_code', unknown>>('get_user', {
token: context.token,
hostname: VITE_KC_API_BASE_URL,
}).catch((err) => console.error('error from Tauri getUser', err))
const user = await userPromise
const user = await response.json()
if ('error_code' in user) throw new Error(user.message)
return user

View File

@ -0,0 +1,466 @@
import { Program } from 'lang/abstractSyntaxTreeTypes'
import { ProgramMemory } from 'lang/executor'
import { Axis, Selection, Selections } from 'useStore'
import { assign, createMachine } from 'xstate'
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
export const modelingMachine = createMachine(
{
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AFgCMAOgBMogOwyAzAE5hNABw0ZAVlGKANCACeQyQDZh4zTMWj5GmqfnDN8gL4v9aDDnzjsETGAEAMpg7AAEsFhg5JzctAxIICxssTyJAgiimuZOkqqSMjRqipKKcvpGCIKiqibiwrVqkjSiwpJiCm4e6Fh4UL7+gQAicFExYSx47PG8yRxcaaAZgvKqivUaJquywtYyohWIojbiqqr7NZprps2u7iCevT5+AQQjkQHjkDAziXOpvGWSnkUk0JhMVkkmmkbRMmkOVWE5mkighNHk7WaZzu3S8fQGr3eY3CJD42Fgv2YrHm3EBQhkpVBpm0q2EMi2qgRImROjRGLaahkqi6Dx63n6L0CIU+4UmuGm9Fm1IB6SE8kK4hku1MilUlgh4JkCNWIJMzQhThKiiukhFj3FBKlxLC3zAlKSyoWdKq8hMqnEinRtgaohoELB8MMiE0zikpUkpnR1sDijtYvxkuCztJ5Pd-y9qqq4PWKgjZ2yUJ0COcdXRNEUvvyMYxqfu9ozgyzMrCADMSJQ857aYWVpp1udRAVA1kTMckQiGTRxGHWtaYyYaA1NGm8c9OwBReVgABOEQA1qFyAALQcpAtLRBagNOIW7RuqRxcmz+hob-ItNoOhxUVdwlA8j1PWAL3Ya8qFEBIqTvYcHwQBll1sDRdhMZRRD9eQuTWKQVDkK5Wm0GMZB3J4wNefcAEcAFdsCYF0+HYY8GIwW8aUWfhEATGQ42tWRClwxRUWrfJ6jkK1hHkMckyoh1M3opiWLANiOK4+ClSQ3iMmkCRagxGpZDWdQJKjBAtiXBtBTBUw2VKJSO0JUZuz7AdFT+Id9LVBtxHBMQwTKLUCjZL8ynEWx1CFPYrEott0z3V5pWiElMEwbiVRQlZwWXMMxBKVZ1FKBE1kEqEtm0JxWlKEwXJS4Z3PSsISEy7L7z4n0oq0JErmyXZWiNKzRyXHQ1AafYqukRqaMCVTmN7bBMtCTrkO6yRHGi0ydAaTc1xGyprHWXVQ0sesdVtJLQMdAhFpYnsVoCaYdJ8vTvT204N1kbCajEYyEXE00NFw2dVDEDdEtxajxCCaDrwICBuDAXxcAAN1QC9xCIY8wBIdgwHPS8b28xCeO9VZBODaxVCyPIIS1BE5zjFQij9cSamUOa4YRq9iDISgsrJj0PsLSczlBeRhuaY5wq5K46jKetCnkdUIfOHn4ZJ8QAEkD0YpbvCJ9hUFQYWENFinxehSRxEnMR1QxZ2si5dVTvky65FacF0S1vm9c7NLxjlBVLfzDaMkDEEwS50plAKNQv0IrZarMaWkTphqbth7WYKvQPUudSIoAAWzAeV1r8hBrAkTc5JoaFZFaedRr9TVoRjc4rgaNpRH9nX9bcj5WtDqvvUaCxxLhfrrSFTlRvd6LwTNVY4V5LUB-zwvmpH8YS-LyuRYj6u9QkEo5F9JxG9WSQFZOaXShaWxZG3HOHTz68d4IZASAvCIwBlwruEdGJ5ODkHauPG2Y56hTk3CFRw+ErJWFOI3doatSiOH1FvL+Q9Ai-3-gfYBYQryoGPNgAAXtwdgkDj6+U+hWU4VwGxOA3C0D8bsQRq3TuqS4vpZw4ILgAGTwETAAKmbTASMUZo0xtjXG+NCZhGNlAlCWRcKBWUNfMGrJIyVBEFCAMZxlAtD9LOZwgjxAiNwOIyR4gAByqAwgAAVUBTFgAQAAghACAEw3FH3DvQ8W1ochrAbCvNQSgFb1ntrYEJF97BIksdY2x5txCuKmGEbxGAIBeJ8X4qYqjNrMgDFCa0RQtS1AhNE9YNh6zZASe0YQyTRFhAkWkkIQD5RZJ8ZAPJvix50LFihMpMdLDtE0PUjmR0hCTNqXEhpicmktJsW0uxnTD7hGyX0ogqBS5MBekTFRQzrYjPZBILIYZnDqA1iYN2ux7YxiuEUQMqdubv3xPuTSnFAjIxsbIrGqMFEEyJhpdiPyikZFZMua0pRmxjlREzKyvoQR6gSscBkvpSjQxArDL54KMD3UNupb5XETk5W6rqU6BRsiCg-HLBEEIJBQjkpMhQ0htDXRhg6fFWkFrEtYgSt0b1yYUoyAaU4id0QzgTGCRl+xQRyTkAUdodNgLth8Lyn539g7hE8m6clXUMh6lqZYMc3sFnCAXLUAMjgwZlPREkj5mrSWo11QsMIAAlfGEADBZnCBAckNCKAGsCcM7qDsQStD1LKjc8ZrW1gcHqX09ZsI4o1f0T+V4wi4CcfqrsrV9WQsQMIDcy5WiCmOM3TcMyfR232B0P6ZwV5gh5gAMWeqEaR-y8ByKBXjEFy1Vph10qcza4l6hjkcNYByeorVWVDDaxcJRSoRi0O2zt7AiVqSHS9YtNchSBQGkKWcWCWjMw0OOcxg0-RFG0Bu4d26lpPWHXBMNY7xU2taMNV8H5rB3wXZe76WQb3qBrQ+l6OrnSun3RVIizaDQlHBAvSoxwIbluOGGBkepNbOv6B24dcNiQeu9SQX1-qwjHjI9gBiFJDWRyOAUJWkylA2CxUiO5C7-oYdDBCPIVx03JXw5uojMoSM+r9bql00BQ2jrFUcWQ6wlCTOkJifYKHGPWlteoEySg41v3uLmjA8BEgZrk0a+kcgmTr1ZOyVYBEzCaj2uEtNuE37ctcmAczDGqiMh0BhSZ0IPxyHOAiUoyImVaPULbZpeHeYk286fdEpTL5mEC7fLkchqaomqhoLUVLLF4MS59DCUh9jTrEKWyw879EsvqBx9klgdCJlix5nwWarGtPaZgYr4sY322tKsRu6nKxcnTlIDkpRJzsmTCs1JmAHFOIyfKEzoqLOZEDONQb0WRvaDdicJwEZS2Z2lrhObay0lDBRr1kZ1glynYcE0PYSD9FIkEuoNoDIk1FDO3FjrKSLsLeW1s3pEAbubVwiCVE0tVjTlMXotUB31zZDMA0U72c2uZoDgD7rRGukg5yeDgypaJBqzDGY15DYEc+iR0d1H9LfuY-EFqjARPEBmn9PkQo0qHJVWrOqe20hwZ5AxB+VruKeWup3mz1CCrxnZGcFaW21rNCBXOHZ3CawbAY4l58qX7ruBeokzL0MGotSTjNGYD88kauPhQSasMRQ0dFHVUJ5nUvSO+pN0VJzWRxLZFwgoPQyC9Rq7kFsTX1hfSCJzXm-sXn3ofv4jGKeZQ0tyXMuUReII0EMjKPyBO7ndc+AIy9GXHJ7aVZC2j-9QMnCajyPkWWUfsIQdCNLxP8nULYSIsoYLNVsLU+OMoJhlopqlqhEXjN4hS-t4N7gI3ZHKhrZ86buodMGwYjy6nIf3G71olkO0FWbf2DiE98vq2XfWhKCkDUDEWosIJlt5kPfjcD8MhUGUNwbggA */
id: 'Modeling',
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
predictableActionArguments: true,
context: {
guiMode: 'default',
selection: [] as string[],
ast: null as Program | null,
selectionRanges: {
otherSelections: [],
codeBasedSelections: [],
} as Selections,
programMemory: { root: {}, pendingMemory: {} } as ProgramMemory,
// TODO: migrate engineCommandManager from useStore
// engineCommandManager?: EngineCommandManager
},
schema: {
events: {} as
| { type: 'Deselect all' }
| { type: 'Deselect edge'; data: Selection & { type: 'edge' } }
| { type: 'Deselect axis'; data: Axis }
| {
type: 'Deselect segment'
data: Selection & { type: 'line' | 'arc' }
}
| { type: 'Deselect face'; data: Selection & { type: 'face' } }
| {
type: 'Deselect point'
data: Selection & { type: 'point' | 'line-end' | 'line-mid' }
}
| { type: 'Equip extrude' }
| { type: 'Equip fillet' }
| { type: 'Enter sketch' }
| { type: 'Select all'; data: Selection & { type: 'all ' } }
| { type: 'Select edge'; data: Selection & { type: 'edge' } }
| { type: 'Select axis'; data: Axis }
| { type: 'Select segment'; data: Selection & { type: 'line' | 'arc' } }
| { type: 'Select face'; data: Selection & { type: 'face' } }
| { type: 'Set selection'; data: Selections }
| {
type: 'Select point'
data: Selection & { type: 'point' | 'line-end' | 'line-mid' }
}
| { type: 'Sketch no face' }
| { type: 'Toggle gui mode' }
| { type: 'Cancel' }
| { type: 'Add point' }
| { type: 'Equip line tool' }
| { type: 'Set radius' }
| { type: 'Make segment horizontal' }
| { type: 'Make segment vertical' }
| { type: 'Complete line' }
| { type: 'Set distance' },
},
states: {
idle: {
on: {
'Set selection': {
target: 'idle',
internal: true,
actions: 'Set selection',
},
'Deselect point': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains point',
},
'Deselect edge': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains edge',
},
'Deselect axis': {
target: 'idle',
internal: true,
actions: [
'Remove from other selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains axis',
},
'Select point': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Select edge': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Select axis': {
target: 'idle',
internal: true,
actions: [
'Add to other selection',
// 'Engine: add highlight',
],
},
'Select face': {
target: 'idle',
internal: true,
actions: [
'Add to code-based selection',
'Update code selection cursors',
// 'Engine: add highlight',
],
},
'Enter sketch': [
{
target: 'Sketch',
cond: 'Selection is one face',
},
'Sketch no face',
],
'Equip extrude': [
{
target: 'Extrude',
cond: 'Selection is empty',
},
{
target: 'Extrude',
cond: 'Selection is one face',
},
],
'Deselect face': {
target: 'idle',
internal: true,
actions: [
'Remove from code-based selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection contains face',
},
'Select all': {
target: 'idle',
internal: true,
actions: 'Add to code-based selection',
},
'Deselect all': {
target: 'idle',
internal: true,
actions: [
'Clear selection',
'Update code selection cursors',
// 'Engine: remove highlight',
],
cond: 'Selection is not empty',
},
'Equip fillet': [
{
target: 'Fillet',
cond: 'Selection is empty',
},
{
target: 'Fillet',
cond: 'Selection is one or more edges',
},
],
},
},
Sketch: {
states: {
Idle: {
on: {
'Equip line tool': 'Line Tool',
'Select point': {
target: 'Idle',
internal: true,
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Select segment': {
target: 'Idle',
internal: true,
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Deselect point': {
target: 'Idle',
internal: true,
cond: 'Selection contains point',
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Deselect segment': {
target: 'Idle',
internal: true,
cond: 'Selection contains line',
actions: [
'Update code selection cursors',
'Add to code-based selection',
],
},
'Make segment vertical': {
cond: 'Can make selection vertical',
target: 'Idle',
internal: true,
actions: ['Make selection vertical'],
},
'Make segment horizontal': {
target: 'Idle',
internal: true,
cond: 'Can make selection horizontal',
actions: ['Make selection horizontal'],
},
},
},
'Line Tool': {
states: {
'No Points': {
on: {
'Add point': {
target: 'Point Added',
actions: ['Modify AST', 'Update code selection cursors'],
},
},
},
Done: {
type: 'final',
},
'Point Added': {
on: {
'Add point': {
target: 'Segment Added',
actions: ['Modify AST', 'Update code selection cursors'],
},
},
},
'Segment Added': {
on: {
'Add point': {
target: 'Segment Added',
internal: true,
actions: ['Modify AST', 'Update code selection cursors'],
},
'Complete line': {
target: 'Done',
actions: ['Modify AST', 'Update code selection cursors'],
},
},
},
},
initial: 'No Points',
invoke: {
src: 'createLine',
id: 'Create line',
onDone: 'Idle',
},
},
},
initial: 'Idle',
on: {
Cancel: '.Idle',
},
invoke: {
src: 'createSketch',
id: 'Create sketch',
onDone: 'idle',
},
},
Extrude: {
states: {
Idle: {
on: {
'Select face': 'Selection Ready',
},
},
'Selection Ready': {
on: {
'Set distance': 'Ready',
},
},
Ready: {},
},
initial: 'Idle',
on: {
'Equip extrude': [
{
target: '.Selection Ready',
cond: 'Selection is one face',
},
'.Idle',
],
},
invoke: {
src: 'createExtrude',
id: 'Create extrude',
onDone: {
target: 'idle',
actions: ['Modify AST', 'Clear selection'],
},
},
},
'Sketch no face': {
on: {
'Select face': 'Sketch',
},
},
Fillet: {
states: {
Idle: {
on: {
'Select edge': 'Selection Ready',
},
},
'Selection Ready': {
on: {
'Set radius': 'Ready',
'Select edge': {
target: 'Selection Ready',
internal: true,
},
},
},
Ready: {},
},
initial: 'Ready',
on: {
'Equip fillet': [
{
target: '.Selection Ready',
cond: 'Selection is one or more edges',
},
'.Idle',
],
},
invoke: {
src: 'createFillet',
id: 'Create fillet',
onDone: {
target: 'idle',
actions: ['Modify AST', 'Clear selection'],
},
},
},
},
initial: 'idle',
on: {
Cancel: '.idle',
},
},
{
actions: {
'Set selection': assign({
selectionRanges: (_, event) => event.data,
}),
'Add to code-based selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
event.data,
],
}),
}),
'Add to other selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
otherSelections: [...selectionRanges.otherSelections, event.data],
}),
}),
'Remove from code-based selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
event.data,
],
}),
}),
'Remove from other selection': assign({
selectionRanges: ({ selectionRanges }, event) => ({
...selectionRanges,
otherSelections: [...selectionRanges.otherSelections, event.data],
}),
}),
'Clear selection': assign({
selectionRanges: () => ({
otherSelections: [],
codeBasedSelections: [],
}),
}),
},
}
)

View File

@ -0,0 +1,165 @@
// This file was automatically generated. Edits will be overwritten
export interface Typegen0 {
'@@xstate/typegen': true
internalEvents: {
'done.invoke.Create extrude': {
type: 'done.invoke.Create extrude'
data: unknown
__tip: 'See the XState TS docs to learn how to strongly type this.'
}
'done.invoke.Create fillet': {
type: 'done.invoke.Create fillet'
data: unknown
__tip: 'See the XState TS docs to learn how to strongly type this.'
}
'done.invoke.Create line': {
type: 'done.invoke.Create line'
data: unknown
__tip: 'See the XState TS docs to learn how to strongly type this.'
}
'done.invoke.Create sketch': {
type: 'done.invoke.Create sketch'
data: unknown
__tip: 'See the XState TS docs to learn how to strongly type this.'
}
'error.platform.Create extrude': {
type: 'error.platform.Create extrude'
data: unknown
}
'error.platform.Create fillet': {
type: 'error.platform.Create fillet'
data: unknown
}
'error.platform.Create line': {
type: 'error.platform.Create line'
data: unknown
}
'error.platform.Create sketch': {
type: 'error.platform.Create sketch'
data: unknown
}
'xstate.init': { type: 'xstate.init' }
}
invokeSrcNameMap: {
createExtrude: 'done.invoke.Create extrude'
createFillet: 'done.invoke.Create fillet'
createLine: 'done.invoke.Create line'
createSketch: 'done.invoke.Create sketch'
}
missingImplementations: {
actions:
| 'Make selection horizontal'
| 'Make selection vertical'
| 'Modify AST'
| 'Update code selection cursors'
delays: never
guards:
| 'Can make selection horizontal'
| 'Can make selection vertical'
| 'Selection contains axis'
| 'Selection contains edge'
| 'Selection contains face'
| 'Selection contains line'
| 'Selection contains point'
| 'Selection is empty'
| 'Selection is not empty'
| 'Selection is one face'
| 'Selection is one or more edges'
services: 'createExtrude' | 'createFillet' | 'createLine' | 'createSketch'
}
eventsCausingActions: {
'Add to code-based selection':
| 'Deselect point'
| 'Deselect segment'
| 'Select all'
| 'Select edge'
| 'Select face'
| 'Select point'
| 'Select segment'
'Add to other selection': 'Select axis'
'Clear selection':
| 'Deselect all'
| 'done.invoke.Create extrude'
| 'done.invoke.Create fillet'
'Make selection horizontal': 'Make segment horizontal'
'Make selection vertical': 'Make segment vertical'
'Modify AST':
| 'Add point'
| 'Complete line'
| 'done.invoke.Create extrude'
| 'done.invoke.Create fillet'
'Remove from code-based selection':
| 'Deselect edge'
| 'Deselect face'
| 'Deselect point'
'Remove from other selection': 'Deselect axis'
'Set selection': 'Set selection'
'Update code selection cursors':
| 'Add point'
| 'Complete line'
| 'Deselect all'
| 'Deselect axis'
| 'Deselect edge'
| 'Deselect face'
| 'Deselect point'
| 'Deselect segment'
| 'Select edge'
| 'Select face'
| 'Select point'
| 'Select segment'
}
eventsCausingDelays: {}
eventsCausingGuards: {
'Can make selection horizontal': 'Make segment horizontal'
'Can make selection vertical': 'Make segment vertical'
'Selection contains axis': 'Deselect axis'
'Selection contains edge': 'Deselect edge'
'Selection contains face': 'Deselect face'
'Selection contains line': 'Deselect segment'
'Selection contains point': 'Deselect point'
'Selection is empty': 'Equip extrude' | 'Equip fillet'
'Selection is not empty': 'Deselect all'
'Selection is one face': 'Enter sketch' | 'Equip extrude'
'Selection is one or more edges': 'Equip fillet'
}
eventsCausingServices: {
createExtrude: 'Equip extrude'
createFillet: 'Equip fillet'
createLine: 'Equip line tool'
createSketch: 'Enter sketch' | 'Select face'
}
matchesStates:
| 'Extrude'
| 'Extrude.Idle'
| 'Extrude.Ready'
| 'Extrude.Selection Ready'
| 'Fillet'
| 'Fillet.Idle'
| 'Fillet.Ready'
| 'Fillet.Selection Ready'
| 'Sketch'
| 'Sketch no face'
| 'Sketch.Idle'
| 'Sketch.Line Tool'
| 'Sketch.Line Tool.Done'
| 'Sketch.Line Tool.No Points'
| 'Sketch.Line Tool.Point Added'
| 'Sketch.Line Tool.Segment Added'
| 'idle'
| {
Extrude?: 'Idle' | 'Ready' | 'Selection Ready'
Fillet?: 'Idle' | 'Ready' | 'Selection Ready'
Sketch?:
| 'Idle'
| 'Line Tool'
| {
'Line Tool'?:
| 'Done'
| 'No Points'
| 'Point Added'
| 'Segment Added'
}
}
tags: never
}

View File

@ -2,78 +2,48 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { SettingsSection } from 'routes/Settings'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import {
CameraSystem,
cameraMouseDragGuards,
cameraSystems,
} from 'lib/cameraControls'
export default function Units() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.STREAMING)
const {
settings: {
send,
state: {
context: { cameraControls },
},
},
} = useGlobalStateContext()
const next = useNextClick(onboardingPaths.SKETCHING)
return (
<div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none">
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
<div
className={
'max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<SettingsSection
title="Camera Controls"
description="How you want to control the camera in the 3D view. Try them out above and choose the one that feels most comfortable to you."
className="my-4 last-of-type:mb-12"
>
<select
id="camera-controls"
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
value={cameraControls}
onChange={(e) => {
send({
type: 'Set Camera Controls',
data: { cameraControls: e.target.value as CameraSystem },
})
}}
>
{cameraSystems.map((program) => (
<option key={program} value={program}>
{program}
</option>
))}
</select>
<ul className="mx-4 my-2 text-sm leading-relaxed">
<li>
<strong>Pan:</strong>{' '}
{cameraMouseDragGuards[cameraControls].pan.description}
</li>
<li>
<strong>Zoom:</strong>{' '}
{cameraMouseDragGuards[cameraControls].zoom.description}
</li>
<li>
<strong>Rotate:</strong>{' '}
{cameraMouseDragGuards[cameraControls].rotate.description}
</li>
</ul>
</SettingsSection>
<div className="flex justify-between">
<h1 className="text-2xl font-bold">Camera</h1>
<p className="mt-6">
Moving the camera is easy! The controls are as you might expect:
</p>
<ul className="list-disc list-outside ms-8 mb-4">
<li>Click and drag anywhere in the scene to rotate the camera</li>
<li>
Hold down the <kbd>Shift</kbd> key while clicking and dragging to
pan the camera
</li>
<li>
Hold down the <kbd>Ctrl</kbd> key while dragging to zoom. You can
also use the scroll wheel to zoom in and out.
</li>
</ul>
<p>
What you're seeing here is just a video, and your interactions are
being sent to our Geometry Engine API, which sends back video frames
in real time. How cool is that? It means that you can use KittyCAD
Modeling App (or whatever you want to build) on any device, even a
cheap laptop with no graphics card!
</p>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
onClick={() => dismiss('../../')}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
@ -89,7 +59,7 @@ export default function Units() {
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Streaming
Next: Sketching
</ActionButton>
</div>
</div>

View File

@ -1,85 +0,0 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { Platform, platform } from '@tauri-apps/api/os'
import { useEffect, useState } from 'react'
export default function CmdK() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.USER_MENU)
const [platformName, setPlatformName] = useState<Platform | ''>('')
useEffect(() => {
async function getPlatform() {
setPlatformName(await platform())
}
getPlatform()
}, [setPlatformName])
return (
<div className="fixed inset-0 z-50 grid items-end justify-center pointer-events-none">
<div
className={
'max-w-full xl:max-w-4xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<h2 className="text-2xl">Command Bar</h2>
<p className="my-4">
Press{' '}
{platformName === 'win32' ? (
<>
<kbd>Win</kbd> + <kbd>/</kbd>
</>
) : (
<>
<kbd>OS</kbd> + <kbd>K</kbd>
</>
)}{' '}
to open the command bar. Try changing your theme with it.
</p>
<p className="my-4">
We are working on a command bar that will allow you to quickly see and
search for any available commands. We are building KittyCAD Modeling
App's state management system on top of{' '}
<a
href="https://xstate.js.org/"
rel="noreferrer noopener"
target="_blank"
>
XState
</a>
. Currently you can only control settings, authentication, and file
management from the command bar, but we will be powering modeling
commands with it soon.
</p>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: User Menu
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,85 +0,0 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
export default function CodeEditor() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.PARAMETRIC_MODELING)
return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
<div
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
style={{ clipPath: useBackdropHighlight('code-pane') }}
></div>
<div
className={
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">
Editing code with <code>kcl</code>
</h2>
<p className="my-4">
The left pane is where you write your code. It's a code editor with
syntax highlighting and autocompletion. We've decided to take the
difficult route of writing our own languagecalled <code>kcl</code>
for describing geometry, because don't want to inherit all the
other functionality from existing languages. We have a lot of ideas
about how <code>kcl</code> will evolve, and we want to hear your
thoughts on it.
</p>
<p className="my-4">
We've built a language server for <code>kcl</code> that provides
documentation and autocompletion automatically generated from our
compiler code. You can try it out by hovering over some of the
function names in the pane now. If you like using VSCode, you can
try out our{' '}
<a
href="https://marketplace.visualstudio.com/items?itemName=KittyCAD.kcl-language-server"
rel="noreferrer noopener"
target="_blank"
>
VSCode extension
</a>
.
</p>
<p className="my-4">
You can resize the pane by dragging the handle on the right, and you
can collapse it by clicking the title bar or pressing{' '}
<kbd>Shift</kbd> + <kbd>C</kbd>.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Parametric Modeling
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,65 +0,0 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
export default function Export() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.SKETCHING)
return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
<div
className={
'max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">Export</h2>
<p className="my-4">
Try opening the project menu and clicking "Export Model".
</p>
<p className="my-4">
KittyCAD Modeling App uses our open-source extension proposal for
the GLTF file format.{' '}
<a
href="https://kittycad.io/docs/api/convert-cad-file"
rel="noopener noreferrer"
target="_blank"
>
Our conversion API
</a>{' '}
can convert to and from most common CAD file formats, allowing
export to almost any CAD software.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Sketching
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,59 +0,0 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { useDismiss } from '.'
import { useEffect } from 'react'
import { useStore } from 'useStore'
import { bracket } from 'lib/exampleKcl'
export default function FutureWork() {
const dismiss = useDismiss()
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
useEffect(() => {
deferredSetCode(bracket)
}, [deferredSetCode])
return (
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
<div className="max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold">Future Work</h1>
<p className="my-4">
We have curves, cuts, and many more CAD features coming soon. We want
your feedback on this user interface, and we want to know what
features you want to see next. Please message us in the Discord server
and open issues on GitHub.
</p>
<p className="my-4">
If you make anything with the app we'd love to see it! Thank you for
taking time to try out KittyCAD Modeling App, and build the future of
hardware design with us 💚.
</p>
<p className="my-4">— The KittyCAD Team</p>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={dismiss}
icon={{ icon: faArrowRight }}
>
Finish
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,125 +0,0 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
export default function InteractiveNumbers() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.COMMAND_K)
return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
<div
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
style={{ clipPath: useBackdropHighlight('code-pane') }}
></div>
<div
className={
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1 overflow-y-auto mb-6">
<h2 className="text-2xl">Interactive Numbers</h2>
<p className="my-4">
Let's do a little bit of hybrid editing to this part.
</p>
<p className="my-4">
Try changing the value of <code>width</code> on line 3 by holding
the <kbd>Alt</kbd> (or <kbd>Option</kbd>) key and dragging the
number left and right. You can hold down different modifier keys to
change the value by different increments:
</p>
<table className="border-collapse text-sm mx-auto my-4">
<tbody>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt + Shift + Cmd/Win</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
0.01
</td>
</tr>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt + Cmd/Win</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
0.1
</td>
</tr>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
1
</td>
</tr>
<tr>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70">
<kbd>Alt + Shift</kbd>
</td>
<td className="border border-solid w-1/2 py-1 px-2 border-chalkboard-40 dark:border-chalkboard-70 text-right">
10
</td>
</tr>
</tbody>
</table>
<p className="my-4">
Our code editor is built with{' '}
<a
href="https://codemirror.net/"
target="_blank"
rel="noreferrer noopeneer"
>
CodeMirror
</a>
, a great open-source project with extensions that make it even more
dynamic and interactive, including{' '}
<a
href="https://github.com/replit/codemirror-interact/"
target="_blank"
rel="noreferrer noopeneer"
>
one by the Replit team
</a>{' '}
lets you interact with numbers in your code by dragging them around.
</p>
<p className="my-4">
Editing code should feel as interactive as point-and-click when you
want it to be, so that you can work in the way that feels most
natural to you. We're going to keep extending the text editor, and
we'd love to hear your ideas for how to make it better.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Command Bar
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,183 +1,29 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { Themes, getSystemTheme } from 'lib/theme'
import { bracket } from 'lib/exampleKcl'
import { useStore } from 'useStore'
import {
createNewProject,
getNextProjectIndex,
getProjectsInDir,
interpolateProjectNameWithIndex,
} from 'lib/tauriFS'
import { isTauri } from 'lib/isTauri'
import { useNavigate } from 'react-router-dom'
import { paths } from 'Router'
import { useEffect } from 'react'
function OnboardingWithNewFile() {
const navigate = useNavigate()
export default function Introduction() {
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.INDEX)
const { deferredSetCode } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
}))
const {
settings: {
context: { defaultDirectory, defaultProjectName },
},
} = useGlobalStateContext()
const next = useNextClick(onboardingPaths.UNITS)
async function createAndOpenNewProject() {
const projects = await getProjectsInDir(defaultDirectory)
const nextIndex = await getNextProjectIndex(defaultProjectName, projects)
const name = interpolateProjectNameWithIndex(defaultProjectName, nextIndex)
const newFile = await createNewProject(defaultDirectory + '/' + name)
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
}
return (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
{!isTauri() ? (
<>
<h1 className="text-2xl font-bold text-warn-80 dark:text-warn-10">
Replaying onboarding resets your code
</h1>
<p className="my-4">
We see you have some of your own code written in this project.
Please save it somewhere else before continuing the onboarding.
</p>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={() => {
deferredSetCode(bracket)
next()
}}
icon={{ icon: faArrowRight }}
>
Overwrite code and continue
</ActionButton>
</div>
</>
) : (
<>
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
Would you like to create a new project?
</h1>
<section className="my-12">
<p className="my-4">
You have some content in this project that we don't want to
overwrite. If you would like to create a new project, please
click the button below.
</p>
</section>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={createAndOpenNewProject}
icon={{ icon: faArrowRight }}
>
Make a new project
</ActionButton>
</div>
</>
)}
</div>
</div>
)
}
export default function Introduction() {
const { deferredSetCode, code } = useStore((s) => ({
code: s.code,
deferredSetCode: s.deferredSetCode,
}))
const {
settings: {
state: {
context: { theme },
},
},
} = useGlobalStateContext()
const getLogoTheme = () =>
theme === Themes.Light ||
(theme === Themes.System && getSystemTheme() === Themes.Light)
? '-dark'
: ''
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.CAMERA)
useEffect(() => {
if (code === '') deferredSetCode(bracket)
}, [code, deferredSetCode])
return !(code !== '' && code !== bracket) ? (
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
<img
src={`/kcma-logomark${getLogoTheme()}.svg`}
alt="KittyCAD Modeling App"
className="max-w-full h-20"
/>
<span className="bg-energy-10 text-energy-80 px-3 py-1 rounded-full text-base">
Alpha
</span>
<h1 className="text-2xl font-bold">
Welcome to the KittyCAD Modeling App
</h1>
<section className="my-12">
<p className="my-4">
Welcome to KittyCAD Modeling App! This is a hardware design tool
that lets you edit visually, with code, or both. It's powered by the
first API created for anyone to build hardware design tools. The 3D
view is not running on your computer, but is instead being streamed
to you from a remote GPU as video.
</p>
<p className="my-4">
This is an alpha release, so you will encounter bugs and missing
features. You can read our{' '}
<a
href="https://gist.github.com/jgomez720/5cd53fb7e8e54079f6dc0d2625de5393"
target="_blank"
rel="noreferrer noopener"
>
expectations for alpha users here
</a>
. Please give us feedback on your experience! We are trying to
release as early as possible to get feedback from users like you.
</p>
</section>
<p className="my-2">
A browser-first, GPU-streaming hardware design tool that lets you edit
visually, with code, or both.
</p>
<p className="my-2">
Powered by the first API created for anyone to build hardware design
tools.
</p>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
onClick={() => dismiss('../')}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
@ -198,7 +44,5 @@ export default function Introduction() {
</div>
</div>
</div>
) : (
<OnboardingWithNewFile />
)
}

View File

@ -1,85 +0,0 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { useBackdropHighlight } from 'hooks/useBackdropHighlight'
import { Themes, getSystemTheme } from 'lib/theme'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
export default function ParametricModeling() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const {
settings: {
context: { theme },
},
} = useGlobalStateContext()
const getImageTheme = () =>
theme === Themes.Light ||
(theme === Themes.System && getSystemTheme() === Themes.Light)
? '-dark'
: ''
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.INTERACTIVE_NUMBERS)
return (
<div className="fixed grid justify-end items-center inset-0 z-50 pointer-events-none">
<div
className="fixed inset-0 bg-black opacity-50 pointer-events-none"
style={{ clipPath: useBackdropHighlight('code-pane') }}
></div>
<div
className={
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1 overflow-y-auto mb-6">
<h2 className="text-2xl">Towards true parametric modeling</h2>
<p className="my-4">
This example script shows how having access to the code
representation of a part can allow us to do things that are tedious
or impossible in traditional CAD software. Here we are building a
simplified shelf bracket out of aluminum:
</p>
<figure className="my-4 w-3/4 mx-auto">
<img
src={`/onboarding-bracket${getImageTheme()}.png`}
alt="Bracket"
/>
<figcaption className="text-small italic text-center">
A simplified shelf bracket
</figcaption>
</figure>
<p className="my-4">
We are able to easily calculate the thickness of the material based
on the width of the bracket to meet a set safety factor on line 6.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Interactive Numbers
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,56 +0,0 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
import { isTauri } from 'lib/isTauri'
export default function ProjectMenu() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.EXPORT)
return (
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
<div
className={
'max-w-xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">Project Menu</h2>
<p className="my-4">
Click on Kitt in the upper left to open the project menu. You can
only {isTauri() && 'go home or '}export your modelwhich we'll talk
about next—for now. We'll add more options here soon, especially as
we add support for multi-file assemblies.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Export
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,44 +1,21 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from 'useStore'
import { useEffect } from 'react'
import { useDismiss } from '.'
export default function Sketching() {
const { deferredSetCode, buttonDownInStream } = useStore((s) => ({
deferredSetCode: s.deferredSetCode,
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.FUTURE_WORK)
useEffect(() => {
deferredSetCode('')
}, [deferredSetCode])
return (
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
<div
className={
'max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<div className="fixed grid justify-center items-end inset-0 bg-chalkboard-110/50 z-50">
<div className="max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold">Sketching</h1>
<p className="my-4">
Our 3D modeling tools are still very much a work in progress, but we
want to show you some early features. Try creating a sketch by
clicking Create Sketch in the top toolbar, then clicking the Line
tool, and clicking in the 3D view.
</p>
<p className="my-4">
Watch the code pane as you click. Point-and-click interactions are
always just modifying and generating code in KittyCAD Modeling App.
<p className="mt-6">
We still have to implement this step, and the rest of the tutorial!
</p>
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
onClick={() => dismiss('../../')}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
@ -51,10 +28,10 @@ export default function Sketching() {
</ActionButton>
<ActionButton
Element="button"
onClick={next}
onClick={() => dismiss('../../')}
icon={{ icon: faArrowRight }}
>
Next: Future Work
Finish
</ActionButton>
</div>
</div>

View File

@ -1,66 +0,0 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
export default function Streaming() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.EDITOR)
return (
<div className="fixed grid justify-start items-center inset-0 z-50 pointer-events-none">
<div
className={
'max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">Streaming Video</h2>
<p className="my-4">
The 3D view is not running on your computer. Instead, our
infrastructure spins up the KittyCAD Geometry Engine on a remote
GPU, KittyCAD Modeling App sends it a series of commands via
Websockets and WebRTC, and the Geometry Engine sends back a video
stream of the 3D view.
</p>
<p className="my-4">
This means that you could run KittyCAD Modeling App on a Chromebook,
a tablet, or even a phone, as long as you have a good internet
connection.
</p>
<p className="my-4">
It also means that whatever tools you build on top of the KittyCAD
Geometry Engine will be able to run on any device with a browser,
and you won't have to worry about the performance of the device.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Code Editing
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -66,7 +66,7 @@ export default function Units() {
<div className="flex justify-between mt-6">
<ActionButton
Element="button"
onClick={dismiss}
onClick={() => dismiss('../../')}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',

View File

@ -1,53 +0,0 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore'
export default function UserMenu() {
const { buttonDownInStream } = useStore((s) => ({
buttonDownInStream: s.buttonDownInStream,
}))
const dismiss = useDismiss()
const next = useNextClick(onboardingPaths.PROJECT_MENU)
return (
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
<div
className={
'max-w-xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto')
}
>
<section className="flex-1">
<h2 className="text-2xl">User Menu</h2>
<p className="my-4">
Click your avatar on the upper right to open the user menu. You can
change your settings, sign out, or request a feature.
</p>
</section>
<div className="flex justify-between">
<ActionButton
Element="button"
onClick={dismiss}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
iconClassName:
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
}}
className="hover:border-destroy-40"
>
Dismiss
</ActionButton>
<ActionButton
Element="button"
onClick={next}
icon={{ icon: faArrowRight }}
>
Next: Project Menu
</ActionButton>
</div>
</div>
</div>
)
}

View File

@ -1,36 +1,18 @@
import { useHotkeys } from 'react-hotkeys-hook'
import { Outlet, useRouteLoaderData, useNavigate } from 'react-router-dom'
import { Outlet, useNavigate } from 'react-router-dom'
import Introduction from './Introduction'
import Units from './Units'
import Camera from './Camera'
import Sketching from './Sketching'
import { useCallback } from 'react'
import makeUrlPathRelative from '../../lib/makeUrlPathRelative'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import Streaming from './Streaming'
import CodeEditor from './CodeEditor'
import ParametricModeling from './ParametricModeling'
import InteractiveNumbers from './InteractiveNumbers'
import CmdK from './CmdK'
import UserMenu from './UserMenu'
import ProjectMenu from './ProjectMenu'
import Export from './Export'
import FutureWork from './FutureWork'
import { IndexLoaderData, paths } from 'Router'
export const onboardingPaths = {
INDEX: '/',
UNITS: '/units',
CAMERA: '/camera',
STREAMING: '/streaming',
EDITOR: '/editor',
PARAMETRIC_MODELING: '/parametric-modeling',
INTERACTIVE_NUMBERS: '/interactive-numbers',
COMMAND_K: '/command-k',
USER_MENU: '/user-menu',
PROJECT_MENU: '/project-menu',
EXPORT: '/export',
MOVE: '/move',
SKETCHING: '/sketching',
FUTURE_WORK: '/future-work',
}
export const onboardingRoutes = [
@ -38,51 +20,18 @@ export const onboardingRoutes = [
index: true,
element: <Introduction />,
},
{
path: makeUrlPathRelative(onboardingPaths.UNITS),
element: <Units />,
},
{
path: makeUrlPathRelative(onboardingPaths.CAMERA),
element: <Camera />,
},
{
path: makeUrlPathRelative(onboardingPaths.STREAMING),
element: <Streaming />,
},
{
path: makeUrlPathRelative(onboardingPaths.EDITOR),
element: <CodeEditor />,
},
{
path: makeUrlPathRelative(onboardingPaths.PARAMETRIC_MODELING),
element: <ParametricModeling />,
},
{
path: makeUrlPathRelative(onboardingPaths.INTERACTIVE_NUMBERS),
element: <InteractiveNumbers />,
},
{
path: makeUrlPathRelative(onboardingPaths.COMMAND_K),
element: <CmdK />,
},
{
path: makeUrlPathRelative(onboardingPaths.USER_MENU),
element: <UserMenu />,
},
{
path: makeUrlPathRelative(onboardingPaths.PROJECT_MENU),
element: <ProjectMenu />,
},
{
path: makeUrlPathRelative(onboardingPaths.EXPORT),
element: <Export />,
},
// Export / conversion API
{
path: makeUrlPathRelative(onboardingPaths.SKETCHING),
element: <Sketching />,
},
{
path: makeUrlPathRelative(onboardingPaths.FUTURE_WORK),
element: <FutureWork />,
},
]
export function useNextClick(newStatus: string) {
@ -90,44 +39,37 @@ export function useNextClick(newStatus: string) {
settings: { send },
} = useGlobalStateContext()
const navigate = useNavigate()
const { project } = useRouteLoaderData(paths.FILE) as IndexLoaderData
return useCallback(() => {
send({
type: 'Set Onboarding Status',
data: { onboardingStatus: newStatus },
})
navigate(
paths.FILE +
'/' +
encodeURIComponent(project?.path || 'new') +
paths.ONBOARDING.INDEX.slice(0, -1) +
newStatus
)
}, [project, newStatus, send, navigate])
navigate((newStatus !== onboardingPaths.UNITS ? '..' : '.') + newStatus)
}, [newStatus, send, navigate])
}
export function useDismiss() {
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData
const {
settings: { send },
} = useGlobalStateContext()
const navigate = useNavigate()
return useCallback(() => {
send({
type: 'Set Onboarding Status',
data: { onboardingStatus: 'dismissed' },
})
navigate(
paths.FILE + '/' + encodeURIComponent(routeData?.project?.path || 'new')
)
}, [send, navigate, routeData])
return useCallback(
(path: string) => {
send({
type: 'Set Onboarding Status',
data: { onboardingStatus: 'dismissed' },
})
navigate(path)
},
[send, navigate]
)
}
const Onboarding = () => {
const dismiss = useDismiss()
useHotkeys('esc', dismiss)
useHotkeys('esc', () => dismiss('../'))
return (
<>

View File

@ -23,14 +23,12 @@ import {
cameraMouseDragGuards,
} from 'lib/cameraControls'
import { UnitSystem } from 'machines/settingsMachine'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
export const Settings = () => {
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData
const navigate = useNavigate()
const location = useLocation()
const dotDotSlash = useDotDotSlash()
useHotkeys('esc', () => navigate(dotDotSlash()))
useHotkeys('esc', () => navigate('../'))
const {
settings: {
send,
@ -64,11 +62,11 @@ export const Settings = () => {
}
return (
<div className="fixed inset-0 z-40 overflow-auto body-bg">
<div className="body-bg fixed inset-0 z-40 overflow-auto">
<AppHeader showToolbar={false} project={loaderData?.project}>
<ActionButton
Element="link"
to={location.pathname.replace(paths.SETTINGS, '')}
to={'../'}
icon={{
icon: faXmark,
bgClassName: 'bg-destroy-80',
@ -80,9 +78,9 @@ export const Settings = () => {
Close
</ActionButton>
</AppHeader>
<div className="max-w-5xl mx-auto my-24">
<div className="my-24 max-w-5xl mx-auto">
<h1 className="text-4xl font-bold">User Settings</h1>
<p className="max-w-2xl mt-6">
<p className="mt-6 max-w-2xl">
Don't see the feature you want? Check to see if it's on{' '}
<a
href="https://github.com/KittyCAD/modeling-app/discussions"
@ -100,7 +98,7 @@ export const Settings = () => {
>
<select
id="camera-controls"
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
value={cameraControls}
onChange={(e) => {
send({
@ -115,7 +113,7 @@ export const Settings = () => {
</option>
))}
</select>
<ul className="mx-4 my-2 text-sm leading-relaxed">
<ul className="text-sm my-2 mx-4 leading-relaxed">
<li>
<strong>Pan:</strong>{' '}
{cameraMouseDragGuards[cameraControls].pan.description}
@ -136,7 +134,7 @@ export const Settings = () => {
title="Default Directory"
description="Where newly-created projects are saved on your local computer"
>
<div className="flex w-full gap-4 p-1 border rounded border-chalkboard-30">
<div className="w-full flex gap-4 p-1 rounded border border-chalkboard-30">
<input
className="flex-1 px-2 bg-transparent"
value={defaultDirectory}
@ -163,7 +161,7 @@ export const Settings = () => {
description="Name template for new projects. Use $n to include an incrementing index"
>
<input
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
defaultValue={defaultProjectName}
onBlur={(e) => {
const newValue = e.target.value.trim() || DEFAULT_PROJECT_NAME
@ -207,7 +205,7 @@ export const Settings = () => {
>
<select
id="base-unit"
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
value={baseUnit}
onChange={(e) => {
send({
@ -241,7 +239,7 @@ export const Settings = () => {
>
<select
id="settings-theme"
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
value={theme}
onChange={(e) => {
send({
@ -269,7 +267,7 @@ export const Settings = () => {
type: 'Set Onboarding Status',
data: { onboardingStatus: '' },
})
navigate(dotDotSlash(1) + paths.ONBOARDING.INDEX)
navigate('..' + paths.ONBOARDING.INDEX)
}}
icon={{ icon: faArrowRotateBack }}
>
@ -285,22 +283,15 @@ export const Settings = () => {
interface SettingsSectionProps extends React.PropsWithChildren {
title: string
description?: string
className?: string
}
export function SettingsSection({
title,
description,
className,
children,
}: SettingsSectionProps) {
return (
<section
className={
'my-16 last-of-type:mb-24 grid grid-cols-2 gap-12 items-start ' +
className
}
>
<section className="my-16 last-of-type:mb-24 grid grid-cols-2 gap-12 items-start">
<div className="w-80">
<h2 className="text-2xl">{title}</h2>
<p className="mt-2 text-sm">{description}</p>

View File

@ -4,7 +4,6 @@ import { addLineHighlight, EditorView } from './editor/highlightextension'
import { parser_wasm } from './lang/abstractSyntaxTree'
import { Program } from './lang/abstractSyntaxTreeTypes'
import { getNodeFromPath } from './lang/queryAst'
import { enginelessExecutor } from './lib/testHelpers'
import {
ProgramMemory,
Position,
@ -14,21 +13,34 @@ import {
} from './lang/executor'
import { recast } from './lang/recast'
import { EditorSelection } from '@codemirror/state'
import { EngineCommandManager } from './lang/std/engineConnection'
import {
ArtifactMap,
SourceRangeMap,
EngineCommandManager,
} from './lang/std/engineConnection'
import { KCLError } from './lang/errors'
import { deferExecution } from 'lib/utils'
import { _executor } from './lang/executor'
import { bracket } from 'lib/exampleKcl'
import { defferExecution } from 'lib/utils'
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
export type Selection = {
type: 'default' | 'line-end' | 'line-mid'
type:
| 'default'
| 'line-end'
| 'line-mid'
| 'face'
| 'point'
| 'edge'
| 'line'
| 'arc'
| 'all'
range: SourceRange
}
export type Selections = {
otherSelections: ('y-axis' | 'x-axis' | 'z-axis')[]
otherSelections: Axis[]
codeBasedSelections: Selection[]
}
export type ToolTip =
export type TooTip =
| 'lineTo'
| 'line'
| 'angledLine'
@ -58,7 +70,7 @@ export const toolTips = [
'xLineTo',
'yLineTo',
'angledLineThatIntersects',
] as any as ToolTip[]
] as any as TooTip[]
export type GuiModes =
| {
@ -66,12 +78,12 @@ export type GuiModes =
}
| {
mode: 'sketch'
sketchMode: ToolTip
sketchMode: TooTip
isTooltip: true
waitingFirstClick: boolean
rotation: Rotation
position: Position
pathId: string
id?: string
pathToNode: PathToNode
}
| {
@ -80,15 +92,6 @@ export type GuiModes =
rotation: Rotation
position: Position
pathToNode: PathToNode
pathId: string
}
| {
mode: 'sketch'
sketchMode: 'enterSketchEdit'
rotation: Rotation
position: Position
pathToNode: PathToNode
pathId: string
}
| {
mode: 'sketch'
@ -131,37 +134,40 @@ export interface StoreState {
setGuiMode: (guiMode: GuiModes) => void
logs: string[]
addLog: (log: string) => void
setLogs: (logs: string[]) => void
resetLogs: () => void
kclErrors: KCLError[]
addKCLError: (err: KCLError) => void
setErrors: (errors: KCLError[]) => void
resetKCLErrors: () => void
ast: Program
setAst: (ast: Program) => void
executeAst: (ast?: Program) => void
executeAstMock: (ast?: Program) => void
updateAst: (
ast: Program,
execute: boolean,
optionalParams?: {
focusPath?: PathToNode
callBack?: (ast: Program) => void
}
) => void
updateAstAsync: (
ast: Program,
reexecute: boolean,
focusPath?: PathToNode
) => void
updateAstAsync: (ast: Program, focusPath?: PathToNode) => void
code: string
defferedCode: string
setCode: (code: string) => void
deferredSetCode: (code: string) => void
executeCode: (code?: string) => void
defferedSetCode: (code: string) => void
formatCode: () => void
errorState: {
isError: boolean
error: string
}
setError: (error?: string) => void
programMemory: ProgramMemory
setProgramMemory: (programMemory: ProgramMemory) => void
isShiftDown: boolean
setIsShiftDown: (isShiftDown: boolean) => void
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
setArtifactNSourceRangeMaps: (a: {
artifactMap: ArtifactMap
sourceRangeMap: SourceRangeMap
}) => void
engineCommandManager?: EngineCommandManager
setEngineCommandManager: (engineCommandManager: EngineCommandManager) => void
mediaStream?: MediaStream
@ -202,13 +208,10 @@ let pendingAstUpdates: number[] = []
export const useStore = create<StoreState>()(
persist(
(set, get) => {
// We defer this so that likely our ast has caught up to the code.
// If we are making changes that are not reflected in the ast, we
// should not be updating the ast.
const setDeferredCode = deferExecution((code: string) => {
set({ code })
get().executeCode(code)
}, 600)
const setDefferedCode = defferExecution(
(code: string) => set({ defferedCode: code }),
600
)
return {
editorView: null,
setEditorView: (editorView) => {
@ -222,22 +225,6 @@ export const useStore = create<StoreState>()(
editorView.dispatch({ effects: addLineHighlight.of(selection) })
}
},
executeCode: async (code) => {
const result = await executeCode({
code: code || get().code,
lastAst: get().ast,
engineCommandManager: get().engineCommandManager,
})
if (!result.isChange) {
return
}
set({
ast: result.ast,
logs: result.logs,
kclErrors: result.errors,
programMemory: result.programMemory,
})
},
setCursor: (selections) => {
const { editorView } = get()
if (!editorView) return
@ -267,10 +254,7 @@ export const useStore = create<StoreState>()(
get().setCursor({
otherSelections: currestSelections.otherSelections,
codeBasedSelections: [
{
range: [0, code.length ? code.length - 1 : 0],
type: 'default',
},
{ range: [0, code.length - 1], type: 'default' },
],
})
return
@ -304,8 +288,8 @@ export const useStore = create<StoreState>()(
set((state) => ({ logs: [...state.logs, log] }))
}
},
setLogs: (logs) => {
set({ logs })
resetLogs: () => {
set({ logs: [] })
},
kclErrors: [],
addKCLError: (e) => {
@ -314,62 +298,19 @@ export const useStore = create<StoreState>()(
resetKCLErrors: () => {
set({ kclErrors: [] })
},
setErrors: (errors) => {
set({ kclErrors: errors })
},
ast: {
start: 0,
end: 0,
body: [],
nonCodeMeta: {
nonCodeNodes: {},
noneCodeNodes: {},
start: null,
},
},
setAst: (ast) => {
set({ ast })
},
executeAst: async (ast) => {
const _ast = ast || get().ast
if (!get().isStreamReady) return
const engineCommandManager = get().engineCommandManager!
if (!engineCommandManager) return
set({ isExecuting: true })
const { logs, errors, programMemory } = await executeAst({
ast: _ast,
engineCommandManager,
})
set({
programMemory,
logs,
kclErrors: errors,
isExecuting: false,
})
},
executeAstMock: async (ast) => {
const _ast = ast || get().ast
if (!get().isStreamReady) return
const engineCommandManager = get().engineCommandManager!
if (!engineCommandManager) return
const { logs, errors, programMemory } = await executeAst({
ast: _ast,
engineCommandManager,
useFakeExecutor: true,
})
set({
programMemory,
logs,
kclErrors: errors,
isExecuting: false,
})
},
updateAst: async (
ast,
reexecute,
{ focusPath, callBack = () => {} } = {}
) => {
updateAst: async (ast, { focusPath, callBack = () => {} } = {}) => {
const newCode = recast(ast)
const astWithUpdatedSource = parser_wasm(newCode)
callBack(astWithUpdatedSource)
@ -377,6 +318,7 @@ export const useStore = create<StoreState>()(
set({
ast: astWithUpdatedSource,
code: newCode,
defferedCode: newCode,
})
if (focusPath) {
const { node } = getNodeFromPath<any>(
@ -397,33 +339,24 @@ export const useStore = create<StoreState>()(
})
})
}
if (reexecute) {
// Call execute on the set ast.
get().executeAst(astWithUpdatedSource)
} else {
// When we don't re-execute, we still want to update the program
// memory with the new ast. So we will hit the mock executor
// instead.
get().executeAstMock(astWithUpdatedSource)
}
},
updateAstAsync: async (ast, reexecute, focusPath) => {
updateAstAsync: async (ast, focusPath) => {
// clear any pending updates
pendingAstUpdates.forEach((id) => clearTimeout(id))
pendingAstUpdates = []
// setup a new update
pendingAstUpdates.push(
setTimeout(() => {
get().updateAst(ast, reexecute, { focusPath })
get().updateAst(ast, { focusPath })
}, 100) as unknown as number
)
},
code: bracket,
setCode: (code) => set({ code }),
deferredSetCode: (code) => {
code: '',
defferedCode: '',
setCode: (code) => set({ code, defferedCode: code }),
defferedSetCode: (code) => {
set({ code })
setDeferredCode(code)
setDefferedCode(code)
},
formatCode: async () => {
const code = get().code
@ -431,10 +364,20 @@ export const useStore = create<StoreState>()(
const newCode = recast(ast)
set({ code: newCode, ast })
},
errorState: {
isError: false,
error: '',
},
setError: (error = '') => {
set({ errorState: { isError: !!error, error } })
},
programMemory: { root: {}, return: null },
setProgramMemory: (programMemory) => set({ programMemory }),
isShiftDown: false,
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
artifactMap: {},
sourceRangeMap: {},
setArtifactNSourceRangeMaps: (maps) => set({ ...maps }),
setEngineCommandManager: (engineCommandManager) =>
set({ engineCommandManager }),
setMediaStream: (mediaStream) => set({ mediaStream }),
@ -477,165 +420,9 @@ export const useStore = create<StoreState>()(
partialize: (state) =>
Object.fromEntries(
Object.entries(state).filter(([key]) =>
['code', 'openPanes'].includes(key)
['code', 'defferedCode', 'openPanes'].includes(key)
)
),
}
)
)
const defaultProgramMemory: ProgramMemory['root'] = {
_0: {
type: 'UserVal',
value: 0,
__meta: [],
},
_90: {
type: 'UserVal',
value: 90,
__meta: [],
},
_180: {
type: 'UserVal',
value: 180,
__meta: [],
},
_270: {
type: 'UserVal',
value: 270,
__meta: [],
},
PI: {
type: 'UserVal',
value: Math.PI,
__meta: [],
},
}
async function executeCode({
engineCommandManager,
code,
lastAst,
}: {
code: string
lastAst: Program
engineCommandManager?: EngineCommandManager
}): Promise<
| {
logs: string[]
errors: KCLError[]
programMemory: ProgramMemory
ast: Program
isChange: true
}
| { isChange: false }
> {
let ast: Program
try {
ast = parser_wasm(code)
} catch (e) {
let errors: KCLError[] = []
let logs: string[] = [JSON.stringify(e)]
if (e instanceof KCLError) {
errors = [e]
logs = []
if (e.msg === 'file is empty') engineCommandManager?.endSession()
}
return {
isChange: true,
logs,
errors,
programMemory: {
root: {},
return: null,
},
ast: {
start: 0,
end: 0,
body: [],
nonCodeMeta: {
nonCodeNodes: {},
start: null,
},
},
}
}
// Check if the ast we have is equal to the ast in the storage.
// If it is, we don't need to update the ast.
if (!engineCommandManager || JSON.stringify(ast) === JSON.stringify(lastAst))
return { isChange: false }
const { logs, errors, programMemory } = await executeAst({
ast,
engineCommandManager,
})
return {
ast,
logs,
errors,
programMemory,
isChange: true,
}
}
async function executeAst({
ast,
engineCommandManager,
useFakeExecutor = false,
}: {
ast: Program
engineCommandManager: EngineCommandManager
useFakeExecutor?: boolean
}): Promise<{
logs: string[]
errors: KCLError[]
programMemory: ProgramMemory
}> {
try {
if (!useFakeExecutor) {
engineCommandManager.endSession()
engineCommandManager.startNewSession()
}
const programMemory = await (useFakeExecutor
? enginelessExecutor(ast, {
root: defaultProgramMemory,
return: null,
})
: _executor(
ast,
{
root: defaultProgramMemory,
return: null,
},
engineCommandManager
))
await engineCommandManager.waitForAllCommands(ast, programMemory)
return {
logs: [],
errors: [],
programMemory,
}
} catch (e: any) {
if (e instanceof KCLError) {
return {
errors: [e],
logs: [],
programMemory: {
root: {},
return: null,
},
}
} else {
console.log(e)
return {
logs: [e],
errors: [],
programMemory: {
root: {},
return: null,
},
}
}
}
}

View File

@ -1,22 +0,0 @@
# Each test can have at most 4 threads, but if its name contains "serial_test_", then it
# also requires 4 threads.
# This means such tests run one at a time, with 4 threads.
[test-groups]
serial-integration = { max-threads = 4 }
[profile.default]
slow-timeout = { period = "10s", terminate-after = 1 }
[profile.ci]
slow-timeout = { period = "60s", terminate-after = 10 }
[[profile.default.overrides]]
filter = "test(serial_test_)"
test-group = "serial-integration"
threads-required = 4
[[profile.ci.overrides]]
filter = "test(serial_test_)"
test-group = "serial-integration"
threads-required = 4

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

@ -41,9 +41,9 @@ dependencies = [
[[package]]
name = "aho-corasick"
version = "1.1.0"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f2135563fb5c609d2b2b87c1e8ce7bc41b0b45430fa9661f457981503dd5bf0"
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
dependencies = [
"memchr",
]
@ -79,9 +79,9 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.3"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46"
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
[[package]]
name = "anstyle-parse"
@ -150,7 +150,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -205,9 +205,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.21.4"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
[[package]]
name = "bigdecimal"
@ -306,15 +306,15 @@ dependencies = [
"serde",
"serde_bytes",
"serde_json",
"time",
"time 0.3.27",
"uuid",
]
[[package]]
name = "bumpalo"
version = "3.14.0"
version = "3.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
[[package]]
name = "bytemuck"
@ -330,9 +330,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
version = "1.5.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
dependencies = [
"serde",
]
@ -363,15 +363,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.31"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"windows-targets 0.48.5",
"time 0.1.45",
"wasm-bindgen",
"winapi",
]
[[package]]
@ -387,29 +390,59 @@ dependencies = [
[[package]]
name = "clap"
version = "4.4.4"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136"
checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123"
dependencies = [
"atty",
"bitflags 1.3.2",
"clap_derive 3.2.25",
"clap_lex 0.2.4",
"indexmap 1.9.3",
"once_cell",
"strsim",
"termcolor",
"textwrap",
"unicase",
]
[[package]]
name = "clap"
version = "4.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
dependencies = [
"clap_builder",
"clap_derive",
"clap_derive 4.4.2",
]
[[package]]
name = "clap_builder"
version = "4.4.4"
version = "4.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56"
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"clap_lex 0.5.1",
"strsim",
"terminal_size",
"unicase",
"unicode-width",
]
[[package]]
name = "clap_derive"
version = "3.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "clap_derive"
version = "4.4.2"
@ -419,7 +452,16 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]]
@ -593,7 +635,7 @@ checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
[[package]]
name = "derive-docs"
version = "0.1.4"
version = "0.1.3"
dependencies = [
"convert_case",
"expectorate",
@ -603,7 +645,21 @@ dependencies = [
"quote",
"serde",
"serde_tokenstream",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
name = "derive-docs"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fe5c5ea065cfabc5a7c5e8ed616e369fbf108c4be01e0e5609bc9846a732664"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"serde",
"serde_tokenstream",
"syn 2.0.33",
]
[[package]]
@ -678,9 +734,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
version = "0.3.3"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
dependencies = [
"errno-dragonfly",
"libc",
@ -710,9 +766,9 @@ dependencies = [
[[package]]
name = "exr"
version = "1.71.0"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8"
checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18"
dependencies = [
"bit_field",
"flume",
@ -776,10 +832,14 @@ dependencies = [
[[package]]
name = "flume"
version = "0.11.0"
version = "0.10.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
dependencies = [
"futures-core",
"futures-sink",
"nanorand",
"pin-project",
"spin 0.9.8",
]
@ -873,7 +933,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -925,7 +985,7 @@ dependencies = [
"cfg-if",
"js-sys",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen",
]
@ -1184,6 +1244,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]]
@ -1194,7 +1255,6 @@ checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
"serde",
]
[[package]]
@ -1281,14 +1341,13 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.1.30"
version = "0.1.26"
dependencies = [
"anyhow",
"async-trait",
"bson",
"clap",
"clap 4.4.3",
"dashmap",
"derive-docs",
"derive-docs 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"expectorate",
"futures",
"itertools 0.11.0",
@ -1310,16 +1369,17 @@ dependencies = [
"uuid",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "kittycad"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9cf962b1e81a0b4eb923a727e761b40672cbacc7f5f0b75e13579d346352bc7"
dependencies = [
"anyhow",
"async-trait",
"base64 0.21.4",
"base64 0.21.2",
"bytes",
"chrono",
"data-encoding",
@ -1333,7 +1393,7 @@ dependencies = [
"rand",
"reqwest",
"reqwest-conditional-middleware",
"reqwest-middleware",
"reqwest-middleware 0.2.3",
"reqwest-retry",
"reqwest-tracing",
"schemars",
@ -1367,9 +1427,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.148"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "libloading"
@ -1389,9 +1449,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.4.7"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]]
name = "lock_api"
@ -1442,9 +1502,9 @@ checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef"
[[package]]
name = "memchr"
version = "2.6.3"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
@ -1494,10 +1554,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
dependencies = [
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"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]]
name = "newline-converter"
version = "0.3.0"
@ -1585,9 +1654,9 @@ checksum = "049950a25a8f69e9673ed52fc58749548cee71194f6c3a8a04b80863637ce722"
[[package]]
name = "object"
version = "0.32.1"
version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe"
dependencies = [
"memchr",
]
@ -1606,22 +1675,22 @@ checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
[[package]]
name = "openapitor"
version = "0.0.9"
source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#0d121f6881da91b4a30bee18bbfe50e4a2096073"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "120168eae5b6485690af708bd1030547df62cca10a643763d416ab0e6831decb"
dependencies = [
"Inflector",
"anyhow",
"chrono",
"clap",
"clap 3.2.25",
"data-encoding",
"format_serde_error",
"futures-util",
"http",
"indexmap 2.0.0",
"indexmap 1.9.3",
"json-patch",
"log",
"numeral",
"once_cell",
"openapiv3",
"phonenumber",
"proc-macro2",
@ -1629,7 +1698,7 @@ dependencies = [
"rand",
"regex",
"reqwest",
"reqwest-middleware",
"reqwest-middleware 0.1.6",
"rustfmt-wrapper",
"schemars",
"serde",
@ -1642,17 +1711,18 @@ dependencies = [
"slog-stdlog",
"slog-term",
"thiserror",
"tokio",
"url",
"uuid",
]
[[package]]
name = "openapiv3"
version = "1.0.3"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75e56d5c441965b6425165b7e3223cc933ca469834f4a8b4786817a1f9dc4f13"
checksum = "7b1a9f106eb0a780abd17ba9fca8e0843e3461630bcbe2af0ad4d5d3ba4e9aa4"
dependencies = [
"indexmap 2.0.0",
"indexmap 1.9.3",
"serde",
"serde_json",
]
@ -1682,6 +1752,12 @@ dependencies = [
"thiserror",
]
[[package]]
name = "os_str_bytes"
version = "6.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac"
[[package]]
name = "parking_lot"
version = "0.11.2"
@ -1751,9 +1827,9 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"regex-syntax 0.7.5",
"regex-syntax 0.7.4",
"structmeta",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -1770,11 +1846,10 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pest"
version = "2.7.3"
version = "2.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33"
checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a"
dependencies = [
"memchr",
"thiserror",
"ucd-trie",
]
@ -1816,7 +1891,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -2015,25 +2090,25 @@ dependencies = [
[[package]]
name = "regex"
version = "1.9.5"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax 0.7.5",
"regex-syntax 0.7.4",
]
[[package]]
name = "regex-automata"
version = "0.3.8"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.7.5",
"regex-syntax 0.7.4",
]
[[package]]
@ -2056,9 +2131,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.7.5"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
name = "reqwest"
@ -2066,7 +2141,7 @@ version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
dependencies = [
"base64 0.21.4",
"base64 0.21.2",
"bytes",
"encoding_rs",
"futures-core",
@ -2108,10 +2183,26 @@ checksum = "59e50a2e70970896c99d1b8f20ddc30a70b30d3ac6e619a03a8353b64a49b277"
dependencies = [
"async-trait",
"reqwest",
"reqwest-middleware",
"reqwest-middleware 0.2.3",
"task-local-extensions",
]
[[package]]
name = "reqwest-middleware"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69539cea4148dce683bec9dc95be3f0397a9bb2c248a49c8296a9d21659a8cdd"
dependencies = [
"anyhow",
"async-trait",
"futures",
"http",
"reqwest",
"serde",
"task-local-extensions",
"thiserror",
]
[[package]]
name = "reqwest-middleware"
version = "0.2.3"
@ -2142,7 +2233,7 @@ dependencies = [
"hyper",
"parking_lot 0.11.2",
"reqwest",
"reqwest-middleware",
"reqwest-middleware 0.2.3",
"retry-policies",
"task-local-extensions",
"tokio",
@ -2162,7 +2253,7 @@ dependencies = [
"matchit",
"opentelemetry",
"reqwest",
"reqwest-middleware",
"reqwest-middleware 0.2.3",
"task-local-extensions",
"tracing",
"tracing-opentelemetry",
@ -2221,9 +2312,9 @@ dependencies = [
[[package]]
name = "rustix"
version = "0.38.13"
version = "0.38.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49"
dependencies = [
"bitflags 2.4.0",
"errno",
@ -2234,9 +2325,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.21.7"
version = "0.21.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8"
checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb"
dependencies = [
"log",
"ring",
@ -2262,14 +2353,14 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
dependencies = [
"base64 0.21.4",
"base64 0.21.2",
]
[[package]]
name = "rustls-webpki"
version = "0.101.5"
version = "0.101.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed"
checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d"
dependencies = [
"ring",
"untrusted",
@ -2307,9 +2398,9 @@ dependencies = [
[[package]]
name = "schemars"
version = "0.8.15"
version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161"
dependencies = [
"bigdecimal",
"bytes",
@ -2324,9 +2415,9 @@ dependencies = [
[[package]]
name = "schemars_derive"
version = "0.8.15"
version = "0.8.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737"
dependencies = [
"proc-macro2",
"quote",
@ -2417,7 +2508,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -2433,9 +2524,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.107"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
dependencies = [
"indexmap 2.0.0",
"itoa",
@ -2451,7 +2542,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -2463,7 +2554,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -2586,7 +2677,7 @@ dependencies = [
"serde",
"serde_json",
"slog",
"time",
"time 0.3.27",
]
[[package]]
@ -2621,7 +2712,7 @@ dependencies = [
"slog",
"term",
"thread_local",
"time",
"time 0.3.27",
]
[[package]]
@ -2642,9 +2733,9 @@ dependencies = [
[[package]]
name = "socket2"
version = "0.5.4"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
dependencies = [
"libc",
"windows-sys 0.48.0",
@ -2680,7 +2771,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta-derive",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -2691,7 +2782,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -2707,9 +2798,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.37"
version = "2.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
dependencies = [
"proc-macro2",
"quote",
@ -2771,13 +2862,12 @@ dependencies = [
]
[[package]]
name = "terminal_size"
version = "0.3.0"
name = "textwrap"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
dependencies = [
"rustix",
"windows-sys 0.48.0",
"unicode-width",
]
[[package]]
@ -2797,7 +2887,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -2823,9 +2913,20 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.28"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "time"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb39ee79a6d8de55f48f2293a830e040392f1c5f16e336bdd1788cd0aadce07"
dependencies = [
"deranged",
"itoa",
@ -2844,9 +2945,9 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
[[package]]
name = "time-macros"
version = "0.2.14"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572"
checksum = "733d258752e9303d392b94b75230d07b0b9c489350c69b851fc6c065fde3e8f9"
dependencies = [
"time-core",
]
@ -2880,7 +2981,7 @@ dependencies = [
"parking_lot 0.12.1",
"pin-project-lite",
"signal-hook-registry",
"socket2 0.5.4",
"socket2 0.5.3",
"tokio-macros",
"windows-sys 0.48.0",
]
@ -2893,7 +2994,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -3009,7 +3110,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -3038,7 +3139,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
]
[[package]]
@ -3124,7 +3225,7 @@ dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
"termcolor",
]
@ -3163,9 +3264,9 @@ dependencies = [
[[package]]
name = "typenum"
version = "1.17.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "ucd-trie"
@ -3190,9 +3291,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-ident"
version = "1.0.12"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "unicode-normalization"
@ -3229,9 +3330,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "2.4.1"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
dependencies = [
"form_urlencoded",
"idna",
@ -3282,9 +3383,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
dependencies = [
"same-file",
"winapi-util",
@ -3299,6 +3400,12 @@ dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@ -3326,7 +3433,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
"wasm-bindgen-shared",
]
@ -3361,7 +3468,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
"syn 2.0.33",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -3384,7 +3491,6 @@ dependencies = [
"js-sys",
"kcl-lib",
"kittycad",
"pretty_assertions",
"reqwest",
"serde_json",
"tokio",

View File

@ -11,19 +11,15 @@ crate-type = ["cdylib"]
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
gloo-utils = "0.2.0"
kcl-lib = { path = "kcl" }
#kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
kittycad = { git = "https://github.com/KittyCAD/kittycad.rs", branch = "achalmers/relative-path-segments" }
serde_json = "1.0.107"
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
serde_json = "1.0.106"
wasm-bindgen = "0.2.87"
wasm-bindgen-futures = "0.4.37"
[dev-dependencies]
anyhow = "1"
image = "0.24.7"
#kittycad = "0.2.25"
kittycad = { git = "https://github.com/KittyCAD/kittycad.rs", branch = "achalmers/relative-path-segments" }
pretty_assertions = "1.4.0"
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"
@ -54,11 +50,3 @@ members = [
"derive-docs",
"kcl",
]
[[test]]
name = "executor"
path = "tests/executor/main.rs"
[[test]]
name = "modify"
path = "tests/modify/main.rs"

View File

@ -1,7 +1,7 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.4"
version = "0.1.3"
edition = "2021"
license = "MIT"
@ -16,9 +16,9 @@ proc-macro2 = "1"
quote = "1"
serde = { version = "1.0.188", features = ["derive"] }
serde_tokenstream = "0.2"
syn = { version = "2.0.37", features = ["full"] }
syn = { version = "2.0.33", features = ["full"] }
[dev-dependencies]
expectorate = "1.0.7"
openapitor = { git = "https://github.com/KittyCAD/kittycad.rs", branch = "main" }
openapitor = "0.0.5"
pretty_assertions = "1.4.0"

View File

@ -212,12 +212,6 @@ fn do_stdlib_inner(
quote! {
Vec<#ty_ident>
}
} else if ty_string.starts_with("Box<") {
let ty_string = ty_string.trim_start_matches("Box<").trim_end_matches('>');
let ty_ident = format_ident!("{}", ty_string);
quote! {
#ty_ident
}
} else {
let ty_ident = format_ident!("{}", ty_string);
quote! {
@ -256,15 +250,7 @@ fn do_stdlib_inner(
.replace("Result < ", "")
.replace(", KclError >", "");
let return_type = if !ret_ty_string.is_empty() {
let ret_ty_string = if ret_ty_string.starts_with("Box <") {
ret_ty_string
.trim_start_matches("Box <")
.trim_end_matches('>')
.trim()
.to_string()
} else {
ret_ty_string.trim().to_string()
};
let ret_ty_string = ret_ty_string.trim().to_string();
let ret_ty_ident = format_ident!("{}", ret_ty_string);
let ret_ty_string = clean_type(&ret_ty_string);
quote! {
@ -489,9 +475,6 @@ fn clean_type(t: &str) -> String {
if t.starts_with("Vec<") {
t = t.replace("Vec<", "[").replace('>', "]");
}
if t.starts_with("Box<") {
t = t.replace("Box<", "").replace('>', "");
}
if t == "f64" {
return "number".to_string();
@ -581,26 +564,4 @@ mod tests {
assert!(errors.is_empty());
expectorate::assert_contents("tests/show.gen", &openapitor::types::get_text_fmt(&item).unwrap());
}
#[test]
fn test_stdlib_box() {
let (item, errors) = do_stdlib(
quote! {
name = "show",
},
quote! {
fn inner_show(
/// The args to do shit to.
args: Box<f64>
) -> Box<f64> {
args
}
},
)
.unwrap();
let _expected = quote! {};
assert!(errors.is_empty());
expectorate::assert_contents("tests/box.gen", &openapitor::types::get_text_fmt(&item).unwrap());
}
}

View File

@ -1,70 +0,0 @@
#[allow(non_camel_case_types, missing_docs)]
#[doc = "Std lib function: show"]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, schemars :: JsonSchema, ts_rs :: TS)]
#[ts(export)]
pub(crate) struct Show {}
#[allow(non_upper_case_globals, missing_docs)]
#[doc = "Std lib function: show"]
pub(crate) const Show: Show = Show {};
impl crate::docs::StdLibFn for Show {
fn name(&self) -> String {
"show".to_string()
}
fn summary(&self) -> String {
"".to_string()
}
fn description(&self) -> String {
"".to_string()
}
fn tags(&self) -> Vec<String> {
vec![]
}
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "number".to_string(),
schema: f64::json_schema(&mut generator),
required: true,
}]
}
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
Some(crate::docs::StdLibFnArg {
name: "".to_string(),
type_: "number".to_string(),
schema: f64::json_schema(&mut generator),
required: true,
})
}
fn unpublished(&self) -> bool {
false
}
fn deprecated(&self) -> bool {
false
}
fn std_lib_fn(&self) -> crate::std::StdFn {
show
}
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
Box::new(self.clone())
}
}
fn inner_show(#[doc = r" The args to do shit to."] args: Box<f64>) -> Box<f64> {
args
}

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language"
version = "0.1.30"
version = "0.1.26"
edition = "2021"
license = "MIT"
@ -9,18 +9,17 @@ license = "MIT"
[dependencies]
anyhow = { version = "1.0.75", features = ["backtrace"] }
async-trait = "0.1.73"
clap = { version = "4.4.3", features = ["cargo", "derive", "env", "unicode"], optional = true }
clap = { version = "4.4.3", features = ["cargo", "derive", "env", "unicode"] }
dashmap = "5.5.3"
#derive-docs = { version = "0.1.4" }
derive-docs = { path = "../derive-docs" }
kittycad = { git = "https://github.com/KittyCAD/kittycad.rs", branch = "achalmers/relative-path-segments", default-features = false, features = ["js"] }
derive-docs = { version = "0.1.3" }
#derive-docs = { path = "../derive-docs" }
kittycad = { version = "0.2.25", default-features = false, features = ["js"] }
lazy_static = "1.4.0"
parse-display = "0.8.2"
regex = "1.7.1"
schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] }
serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.107"
serde = {version = "1.0.188", features = ["derive"] }
serde_json = "1.0.106"
thiserror = "1.0.48"
ts-rs = { version = "7", package = "ts-rs-json-value", features = ["serde-json-impl", "schemars-impl", "uuid-impl"] }
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
@ -30,7 +29,6 @@ js-sys = { version = "0.3.64" }
tower-lsp = { version = "0.20.0", default-features = false, features = ["runtime-agnostic"] }
wasm-bindgen = "0.2.87"
wasm-bindgen-futures = "0.4.37"
web-sys = { version = "0.3.64", features = ["console"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
@ -42,7 +40,6 @@ tower-lsp = { version = "0.20.0", features = ["proposed"] }
[features]
default = ["engine"]
cli = ["dep:clap"]
engine = []
[profile.release]

View File

@ -23,7 +23,7 @@ pub struct Program {
pub start: usize,
pub end: usize,
pub body: Vec<BodyItem>,
pub non_code_meta: NonCodeMeta,
pub non_code_meta: NoneCodeMeta,
}
impl Program {
@ -81,7 +81,7 @@ impl Program {
"\n".to_string()
};
let custom_white_space_or_comment = match self.non_code_meta.non_code_nodes.get(&index) {
let custom_white_space_or_comment = match self.non_code_meta.none_code_nodes.get(&index) {
Some(custom_white_space_or_comment) => custom_white_space_or_comment.format(&indentation),
None => String::new(),
};
@ -237,47 +237,6 @@ impl Program {
}
}
}
/// Replace a variable declaration with the given name with a new one.
pub fn replace_variable(&mut self, name: &str, declarator: VariableDeclarator) {
for item in &mut self.body {
match item {
BodyItem::ExpressionStatement(_expression_statement) => {
continue;
}
BodyItem::VariableDeclaration(ref mut variable_declaration) => {
for declaration in &mut variable_declaration.declarations {
if declaration.id.name == name {
*declaration = declarator;
return;
}
}
}
BodyItem::ReturnStatement(_return_statement) => continue,
}
}
}
/// Get the variable declaration with the given name.
pub fn get_variable(&self, name: &str) -> Option<&VariableDeclarator> {
for item in &self.body {
match item {
BodyItem::ExpressionStatement(_expression_statement) => {
continue;
}
BodyItem::VariableDeclaration(variable_declaration) => {
for declaration in &variable_declaration.declarations {
if declaration.id.name == name {
return Some(declaration);
}
}
}
BodyItem::ReturnStatement(_return_statement) => continue,
}
}
None
}
}
pub trait ValueMeta {
@ -288,7 +247,7 @@ pub trait ValueMeta {
macro_rules! impl_value_meta {
{$name:ident} => {
impl crate::ast::types::ValueMeta for $name {
impl crate::abstract_syntax_tree_types::ValueMeta for $name {
fn start(&self) -> usize {
self.start
}
@ -467,26 +426,6 @@ impl Value {
Value::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
}
}
/// Get the constraint level for a value type.
pub fn get_constraint_level(&self) -> ConstraintLevel {
match self {
Value::Literal(literal) => literal.get_constraint_level(),
Value::Identifier(identifier) => identifier.get_constraint_level(),
Value::BinaryExpression(binary_expression) => binary_expression.get_constraint_level(),
Value::FunctionExpression(function_identifier) => function_identifier.get_constraint_level(),
Value::CallExpression(call_expression) => call_expression.get_constraint_level(),
Value::PipeExpression(pipe_expression) => pipe_expression.get_constraint_level(),
Value::PipeSubstitution(pipe_substitution) => ConstraintLevel::Ignore {
source_ranges: vec![pipe_substitution.into()],
},
Value::ArrayExpression(array_expression) => array_expression.get_constraint_level(),
Value::ObjectExpression(object_expression) => object_expression.get_constraint_level(),
Value::MemberExpression(member_expression) => member_expression.get_constraint_level(),
Value::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
}
}
}
impl From<Value> for crate::executor::SourceRange {
@ -526,18 +465,6 @@ impl From<&BinaryPart> for crate::executor::SourceRange {
}
impl BinaryPart {
/// Get the constraint level.
pub fn get_constraint_level(&self) -> ConstraintLevel {
match self {
BinaryPart::Literal(literal) => literal.get_constraint_level(),
BinaryPart::Identifier(identifier) => identifier.get_constraint_level(),
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_constraint_level(),
BinaryPart::CallExpression(call_expression) => call_expression.get_constraint_level(),
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
BinaryPart::MemberExpression(member_expression) => member_expression.get_constraint_level(),
}
}
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
match &self {
BinaryPart::Literal(literal) => literal.recast(),
@ -594,7 +521,11 @@ impl BinaryPart {
}
BinaryPart::CallExpression(call_expression) => call_expression.execute(memory, &mut new_pipe_info, engine),
BinaryPart::UnaryExpression(unary_expression) => {
unary_expression.get_result(memory, &mut new_pipe_info, engine)
// Return an error this should not happen.
Err(KclError::Semantic(KclErrorDetails {
message: format!("UnaryExpression should not be a BinaryPart: {:?}", unary_expression),
source_ranges: vec![unary_expression.into()],
}))
}
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(memory),
}
@ -640,26 +571,26 @@ impl BinaryPart {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub struct NonCodeNode {
pub struct NoneCodeNode {
pub start: usize,
pub end: usize,
pub value: NonCodeValue,
pub value: NoneCodeValue,
}
impl NonCodeNode {
impl NoneCodeNode {
pub fn value(&self) -> String {
match &self.value {
NonCodeValue::InlineComment { value } => value.clone(),
NonCodeValue::BlockComment { value } => value.clone(),
NonCodeValue::NewLineBlockComment { value } => value.clone(),
NonCodeValue::NewLine => "\n\n".to_string(),
NoneCodeValue::InlineComment { value } => value.clone(),
NoneCodeValue::BlockComment { value } => value.clone(),
NoneCodeValue::NewLineBlockComment { value } => value.clone(),
NoneCodeValue::NewLine => "\n\n".to_string(),
}
}
pub fn format(&self, indentation: &str) -> String {
match &self.value {
NonCodeValue::InlineComment { value } => format!(" // {}\n", value),
NonCodeValue::BlockComment { value } => {
NoneCodeValue::InlineComment { value } => format!(" // {}\n", value),
NoneCodeValue::BlockComment { value } => {
let add_start_new_line = if self.start == 0 { "" } else { "\n" };
if value.contains('\n') {
format!("{}{}/* {} */\n", add_start_new_line, indentation, value)
@ -667,7 +598,7 @@ impl NonCodeNode {
format!("{}{}// {}\n", add_start_new_line, indentation, value)
}
}
NonCodeValue::NewLineBlockComment { value } => {
NoneCodeValue::NewLineBlockComment { value } => {
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
if value.contains('\n') {
format!("{}{}/* {} */\n", add_start_new_line, indentation, value)
@ -675,7 +606,7 @@ impl NonCodeNode {
format!("{}{}// {}\n", add_start_new_line, indentation, value)
}
}
NonCodeValue::NewLine => "\n\n".to_string(),
NoneCodeValue::NewLine => "\n\n".to_string(),
}
}
}
@ -683,7 +614,7 @@ impl NonCodeNode {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum NonCodeValue {
pub enum NoneCodeValue {
/// An inline comment.
/// An example of this is the following: `1 + 1 // This is an inline comment`.
InlineComment {
@ -712,35 +643,35 @@ pub enum NonCodeValue {
NewLine,
}
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct NonCodeMeta {
pub non_code_nodes: HashMap<usize, NonCodeNode>,
pub start: Option<NonCodeNode>,
pub struct NoneCodeMeta {
pub none_code_nodes: HashMap<usize, NoneCodeNode>,
pub start: Option<NoneCodeNode>,
}
// implement Deserialize manually because we to force the keys of non_code_nodes to be usize
// and by default the ts type { [statementIndex: number]: NonCodeNode } serializes to a string i.e. "0", "1", etc.
impl<'de> Deserialize<'de> for NonCodeMeta {
fn deserialize<D>(deserializer: D) -> Result<NonCodeMeta, D::Error>
// implement Deserialize manually because we to force the keys of none_code_nodes to be usize
// and by default the ts type { [statementIndex: number]: NoneCodeNode } serializes to a string i.e. "0", "1", etc.
impl<'de> Deserialize<'de> for NoneCodeMeta {
fn deserialize<D>(deserializer: D) -> Result<NoneCodeMeta, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct NonCodeMetaHelper {
non_code_nodes: HashMap<String, NonCodeNode>,
start: Option<NonCodeNode>,
struct NoneCodeMetaHelper {
none_code_nodes: HashMap<String, NoneCodeNode>,
start: Option<NoneCodeNode>,
}
let helper = NonCodeMetaHelper::deserialize(deserializer)?;
let mut non_code_nodes = HashMap::new();
for (key, value) in helper.non_code_nodes {
non_code_nodes.insert(key.parse().map_err(serde::de::Error::custom)?, value);
let helper = NoneCodeMetaHelper::deserialize(deserializer)?;
let mut none_code_nodes = HashMap::new();
for (key, value) in helper.none_code_nodes {
none_code_nodes.insert(key.parse().map_err(serde::de::Error::custom)?, value);
}
Ok(NonCodeMeta {
non_code_nodes,
Ok(NoneCodeMeta {
none_code_nodes,
start: helper.start,
})
}
@ -771,33 +702,7 @@ pub struct CallExpression {
impl_value_meta!(CallExpression);
impl From<CallExpression> for Value {
fn from(call_expression: CallExpression) -> Self {
Value::CallExpression(Box::new(call_expression))
}
}
impl CallExpression {
pub fn new(name: &str, arguments: Vec<Value>) -> Result<Self, KclError> {
// Create our stdlib.
let stdlib = crate::std::StdLib::new();
let func = stdlib.get(name).ok_or_else(|| {
KclError::UndefinedValue(KclErrorDetails {
message: format!("Function {} is not defined", name),
source_ranges: vec![],
})
})?;
Ok(Self {
start: 0,
end: 0,
callee: Identifier::new(name),
arguments,
optional: false,
function: Function::StdLib { func },
})
}
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
format!(
"{}({})",
@ -871,11 +776,6 @@ impl CallExpression {
match &self.function {
Function::StdLib { func } => {
/* let source_range: SourceRange = self.into();
println!(
"Calling stdlib function: {}, source_range: {:?}, args: {:?}",
fn_name, source_range, fn_args
);*/
// Attempt to call the function.
let mut args = crate::std::Args::new(fn_args, self.into(), engine);
let result = func.std_lib_fn()(&mut args)?;
@ -943,23 +843,6 @@ impl CallExpression {
arg.rename_identifiers(old_name, new_name);
}
}
/// Return the constraint level for this call expression.
pub fn get_constraint_level(&self) -> ConstraintLevel {
if self.arguments.is_empty() {
return ConstraintLevel::Ignore {
source_ranges: vec![self.into()],
};
}
// Iterate over the arguments and get the constraint level for each one.
let mut constraint_levels = ConstraintLevels::new();
for arg in &self.arguments {
constraint_levels.push(arg.get_constraint_level());
}
constraint_levels.get_constraint_level(self.into())
}
}
/// A function declaration.
@ -1000,15 +883,6 @@ pub struct VariableDeclaration {
impl_value_meta!(VariableDeclaration);
impl VariableDeclaration {
pub fn new(declarations: Vec<VariableDeclarator>, kind: VariableKind) -> Self {
Self {
start: 0,
end: 0,
declarations,
kind,
}
}
/// Returns a value that includes the given character position.
pub fn get_value_for_position(&self, pos: usize) -> Option<&Value> {
for declaration in &self.declarations {
@ -1189,21 +1063,6 @@ pub struct VariableDeclarator {
impl_value_meta!(VariableDeclarator);
impl VariableDeclarator {
pub fn new(name: &str, init: Value) -> Self {
Self {
start: 0,
end: 0,
id: Identifier::new(name),
init,
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
self.init.get_constraint_level()
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
@ -1216,30 +1075,7 @@ pub struct Literal {
impl_value_meta!(Literal);
impl From<Literal> for Value {
fn from(literal: Literal) -> Self {
Value::Literal(Box::new(literal))
}
}
impl Literal {
pub fn new(value: serde_json::Value) -> Self {
Self {
start: 0,
end: 0,
raw: value.to_string(),
value,
}
}
/// Get the constraint level for this literal.
/// Literals are always not constrained.
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::None {
source_ranges: vec![self.into()],
}
}
fn recast(&self) -> String {
if let serde_json::Value::String(value) = &self.value {
let quote = if self.raw.trim().starts_with('"') { '"' } else { '\'' };
@ -1284,22 +1120,6 @@ pub struct Identifier {
impl_value_meta!(Identifier);
impl Identifier {
pub fn new(name: &str) -> Self {
Self {
start: 0,
end: 0,
name: name.to_string(),
}
}
/// Get the constraint level for this identifier.
/// Identifier are always fully constrained.
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::Full {
source_ranges: vec![self.into()],
}
}
/// Rename all identifiers that have the old name to the new given name.
fn rename(&mut self, old_name: &str, new_name: &str) {
if self.name == old_name {
@ -1318,24 +1138,6 @@ pub struct PipeSubstitution {
impl_value_meta!(PipeSubstitution);
impl PipeSubstitution {
pub fn new() -> Self {
Self { start: 0, end: 0 }
}
}
impl Default for PipeSubstitution {
fn default() -> Self {
Self::new()
}
}
impl From<PipeSubstitution> for Value {
fn from(pipe_substitution: PipeSubstitution) -> Self {
Value::PipeSubstitution(Box::new(pipe_substitution))
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
@ -1347,36 +1149,7 @@ pub struct ArrayExpression {
impl_value_meta!(ArrayExpression);
impl From<ArrayExpression> for Value {
fn from(array_expression: ArrayExpression) -> Self {
Value::ArrayExpression(Box::new(array_expression))
}
}
impl ArrayExpression {
pub fn new(elements: Vec<Value>) -> Self {
Self {
start: 0,
end: 0,
elements,
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
if self.elements.is_empty() {
return ConstraintLevel::Ignore {
source_ranges: vec![self.into()],
};
}
let mut constraint_levels = ConstraintLevels::new();
for element in &self.elements {
constraint_levels.push(element.get_constraint_level());
}
constraint_levels.get_constraint_level(self.into())
}
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
let flat_recast = format!(
"[{}]",
@ -1499,29 +1272,6 @@ pub struct ObjectExpression {
}
impl ObjectExpression {
pub fn new(properties: Vec<ObjectProperty>) -> Self {
Self {
start: 0,
end: 0,
properties,
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
if self.properties.is_empty() {
return ConstraintLevel::Ignore {
source_ranges: vec![self.into()],
};
}
let mut constraint_levels = ConstraintLevels::new();
for property in &self.properties {
constraint_levels.push(property.value.get_constraint_level());
}
constraint_levels.get_constraint_level(self.into())
}
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
let flat_recast = format!(
"{{ {} }}",
@ -1777,14 +1527,6 @@ pub struct MemberExpression {
impl_value_meta!(MemberExpression);
impl MemberExpression {
/// Get the constraint level for a member expression.
/// This is always fully constrained.
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::Full {
source_ranges: vec![self.into()],
}
}
fn recast(&self) -> String {
let key_str = match &self.property {
LiteralIdentifier::Identifier(identifier) => {
@ -1820,11 +1562,10 @@ impl MemberExpression {
let value = memory.get(&identifier.name, identifier.into())?;
value.clone()
}
};
}
.get_json_value()?;
let array_json = array.get_json_value()?;
if let serde_json::Value::Array(array) = array_json {
if let serde_json::Value::Array(array) = array {
if let Some(value) = array.get(index) {
Ok(MemoryItem::UserVal(UserVal {
value: value.clone(),
@ -1872,11 +1613,10 @@ impl MemberExpression {
let value = memory.get(&identifier.name, identifier.into())?;
value.clone()
}
};
}
.get_json_value()?;
let object_json = object.get_json_value()?;
if let serde_json::Value::Object(map) = object_json {
if let serde_json::Value::Object(map) = object {
if let Some(value) = map.get(&property_name) {
Ok(MemoryItem::UserVal(UserVal {
value: value.clone(),
@ -1936,26 +1676,6 @@ pub struct BinaryExpression {
impl_value_meta!(BinaryExpression);
impl BinaryExpression {
pub fn new(operator: BinaryOperator, left: BinaryPart, right: BinaryPart) -> Self {
Self {
start: left.start(),
end: right.end(),
operator,
left,
right,
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
let left_constraint_level = self.left.get_constraint_level();
let right_constraint_level = self.right.get_constraint_level();
let mut constraint_levels = ConstraintLevels::new();
constraint_levels.push(left_constraint_level);
constraint_levels.push(right_constraint_level);
constraint_levels.get_constraint_level(self.into())
}
pub fn precedence(&self) -> u8 {
self.operator.precedence()
}
@ -1971,9 +1691,7 @@ impl BinaryExpression {
let should_wrap_right = match &self.right {
BinaryPart::BinaryExpression(bin_exp) => {
self.precedence() > bin_exp.precedence()
|| self.operator == BinaryOperator::Sub
|| self.operator == BinaryOperator::Div
self.precedence() > bin_exp.precedence() || self.operator == BinaryOperator::Sub
}
_ => false,
};
@ -2156,19 +1874,6 @@ pub struct UnaryExpression {
impl_value_meta!(UnaryExpression);
impl UnaryExpression {
pub fn new(operator: UnaryOperator, argument: BinaryPart) -> Self {
Self {
start: 0,
end: argument.end(),
operator,
argument,
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
self.argument.get_constraint_level()
}
fn recast(&self, options: &FormatOptions) -> String {
format!("{}{}", &self.operator, self.argument.recast(options, 0))
}
@ -2238,43 +1943,12 @@ pub struct PipeExpression {
pub start: usize,
pub end: usize,
pub body: Vec<Value>,
pub non_code_meta: NonCodeMeta,
pub non_code_meta: NoneCodeMeta,
}
impl_value_meta!(PipeExpression);
impl From<PipeExpression> for Value {
fn from(pipe_expression: PipeExpression) -> Self {
Value::PipeExpression(Box::new(pipe_expression))
}
}
impl PipeExpression {
pub fn new(body: Vec<Value>) -> Self {
Self {
start: 0,
end: 0,
body,
non_code_meta: Default::default(),
}
}
pub fn get_constraint_level(&self) -> ConstraintLevel {
if self.body.is_empty() {
return ConstraintLevel::Ignore {
source_ranges: vec![self.into()],
};
}
// Iterate over all body expressions.
let mut constraint_levels = ConstraintLevels::new();
for expression in &self.body {
constraint_levels.push(expression.get_constraint_level());
}
constraint_levels.get_constraint_level(self.into())
}
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
self.body
.iter()
@ -2283,7 +1957,7 @@ impl PipeExpression {
let indentation = options.get_indentation(indentation_level + 1);
let mut s = statement.recast(options, indentation_level + 1, true);
let non_code_meta = self.non_code_meta.clone();
if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
if let Some(non_code_meta_value) = non_code_meta.none_code_nodes.get(&index) {
s += non_code_meta_value.format(&indentation).trim_end_matches('\n')
}
@ -2393,13 +2067,6 @@ pub struct FunctionExpression {
impl_value_meta!(FunctionExpression);
impl FunctionExpression {
/// Function expressions don't really apply.
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::Ignore {
source_ranges: vec![self.into()],
}
}
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();
@ -2504,149 +2171,10 @@ impl FormatOptions {
}
}
/// The constraint level.
#[derive(Debug, Clone, Deserialize, Serialize, ts_rs::TS, JsonSchema, Display)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
#[display(style = "snake_case")]
pub enum ConstraintLevel {
/// Ignore constraints.
/// This is useful for stuff like pipe substitutions where we don't want it to
/// factor into the overall constraint level.
/// Like empty arrays or objects, etc.
#[display("ignore")]
Ignore { source_ranges: Vec<SourceRange> },
/// No constraints.
#[display("none")]
None { source_ranges: Vec<SourceRange> },
/// Partially constrained.
#[display("partial")]
Partial {
source_ranges: Vec<SourceRange>,
levels: ConstraintLevels,
},
/// Fully constrained.
#[display("full")]
Full { source_ranges: Vec<SourceRange> },
}
impl From<ConstraintLevel> for Vec<SourceRange> {
fn from(constraint_level: ConstraintLevel) -> Self {
match constraint_level {
ConstraintLevel::Ignore { source_ranges } => source_ranges,
ConstraintLevel::None { source_ranges } => source_ranges,
ConstraintLevel::Partial {
source_ranges,
levels: _,
} => source_ranges,
ConstraintLevel::Full { source_ranges } => source_ranges,
}
}
}
impl PartialEq for ConstraintLevel {
fn eq(&self, other: &Self) -> bool {
// Just check the variant.
std::mem::discriminant(self) == std::mem::discriminant(other)
}
}
impl ConstraintLevel {
pub fn update_source_ranges(&self, source_range: SourceRange) -> Self {
match self {
ConstraintLevel::Ignore { source_ranges: _ } => ConstraintLevel::Ignore {
source_ranges: vec![source_range],
},
ConstraintLevel::None { source_ranges: _ } => ConstraintLevel::None {
source_ranges: vec![source_range],
},
ConstraintLevel::Partial {
source_ranges: _,
levels,
} => ConstraintLevel::Partial {
source_ranges: vec![source_range],
levels: levels.clone(),
},
ConstraintLevel::Full { source_ranges: _ } => ConstraintLevel::Full {
source_ranges: vec![source_range],
},
}
}
}
/// A vector of constraint levels.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
pub struct ConstraintLevels(pub Vec<ConstraintLevel>);
impl Default for ConstraintLevels {
fn default() -> Self {
Self::new()
}
}
impl ConstraintLevels {
pub fn new() -> Self {
Self(vec![])
}
pub fn push(&mut self, constraint_level: ConstraintLevel) {
self.0.push(constraint_level);
}
/// Get the overall constraint level.
pub fn get_constraint_level(&self, source_range: SourceRange) -> ConstraintLevel {
if self.0.is_empty() {
return ConstraintLevel::Ignore {
source_ranges: vec![source_range],
};
}
// Check if all the constraint levels are the same.
if self
.0
.iter()
.all(|level| *level == self.0[0] || matches!(level, ConstraintLevel::Ignore { .. }))
{
self.0[0].clone()
} else {
ConstraintLevel::Partial {
source_ranges: vec![source_range],
levels: self.clone(),
}
}
}
pub fn get_all_partial_or_full_source_ranges(&self) -> Vec<SourceRange> {
let mut source_ranges = Vec::new();
// Add to our source ranges anything that is not none or ignore.
for level in &self.0 {
match level {
ConstraintLevel::None { source_ranges: _ } => {}
ConstraintLevel::Ignore { source_ranges: _ } => {}
ConstraintLevel::Partial {
source_ranges: _,
levels,
} => {
source_ranges.extend(levels.get_all_partial_or_full_source_ranges());
}
ConstraintLevel::Full {
source_ranges: full_source_ranges,
} => {
source_ranges.extend(full_source_ranges);
}
}
}
source_ranges
}
}
#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;
use super::*;
use pretty_assertions::assert_eq;
// We have this as a test so we can ensure it never panics with an unwrap in the server.
#[test]
@ -2680,8 +2208,9 @@ show(part001)"#;
#[test]
fn test_recast_with_std_and_non_stdlib() {
let some_program_string = r#"{"body":[{"type":"VariableDeclaration","start":0,"end":0,"declarations":[{"type":"VariableDeclarator","start":0,"end":0,"id":{"type":"Identifier","start":0,"end":0,"name":"part001"},"init":{"type":"PipeExpression","start":0,"end":0,"body":[{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"startSketchAt"},"function":{"type":"StdLib","func":{"name":"startSketchAt","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"ry"},"function":{"type":"InMemory"},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":90,"raw":"90"},{"type":"PipeSubstitution","start":0,"end":0}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"line"},"function":{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"},{"type":"PipeSubstitution","start":0,"end":0}]}],"nonCodeMeta":{"nonCodeNodes":{},"start":null}}}],"kind":"const"},{"type":"ExpressionStatement","start":0,"end":0,"expression":{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"show"},"function":{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Identifier","start":0,"end":0,"name":"part001"}]}}],"start":0,"end":0,"nonCodeMeta":{"nonCodeNodes":{},"start":null}}"#;
let some_program: crate::ast::types::Program = serde_json::from_str(some_program_string).unwrap();
let some_program_string = r#"{"body":[{"type":"VariableDeclaration","start":0,"end":0,"declarations":[{"type":"VariableDeclarator","start":0,"end":0,"id":{"type":"Identifier","start":0,"end":0,"name":"part001"},"init":{"type":"PipeExpression","start":0,"end":0,"body":[{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"startSketchAt"},"function":{"type":"StdLib","func":{"name":"startSketchAt","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"ry"},"function":{"type":"InMemory"},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":90,"raw":"90"},{"type":"PipeSubstitution","start":0,"end":0}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"line"},"function":{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"},{"type":"PipeSubstitution","start":0,"end":0}]}],"nonCodeMeta":{"noneCodeNodes":{},"start":null}}}],"kind":"const"},{"type":"ExpressionStatement","start":0,"end":0,"expression":{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"show"},"function":{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Identifier","start":0,"end":0,"name":"part001"}]}}],"start":0,"end":0,"nonCodeMeta":{"noneCodeNodes":{},"start":null}}"#;
let some_program: crate::abstract_syntax_tree_types::Program =
serde_json::from_str(some_program_string).unwrap();
let recasted = some_program.recast(&Default::default(), 0);
assert_eq!(
@ -2896,7 +2425,7 @@ const things = "things"
let some_program_string = r#"let b = {
"end": 141,
"start": 125,
"type": "NonCodeNode",
"type": "NoneCodeNode",
"value": "
// a comment
"
@ -3076,20 +2605,4 @@ show(firstExtrude)
let recasted = program.recast(&Default::default(), 0);
assert_eq!(recasted.trim(), some_program_string);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_recast_math_nested_parens() {
let some_program_string = r#"const distance = 5
const p = 3
const FOS = 2
const sigmaAllow = 8
const width = 20
const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
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.trim(), some_program_string);
}
}

View File

@ -1,2 +0,0 @@
pub mod modify;
pub mod types;

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