Compare commits
44 Commits
v0.7.1
...
paultag/us
Author | SHA1 | Date | |
---|---|---|---|
e578b961d8 | |||
b67c16cc9d | |||
ad482641ef | |||
9ee24845a1 | |||
e69d263252 | |||
111738f38e | |||
e34501cc5a | |||
c767c1c3a6 | |||
e399a8f938 | |||
59d5f2524a | |||
b47ebd14d2 | |||
e74bcd0695 | |||
22161ec386 | |||
ada46c4317 | |||
6675fa8d1e | |||
075d2debce | |||
488e41ac0e | |||
8147f5f1eb | |||
bc7e9d9789 | |||
8d493d6517 | |||
9fa98d6f3f | |||
24a31c94e7 | |||
76e3207251 | |||
e2237fa9f6 | |||
ae4aa82129 | |||
14b287a746 | |||
dd1b7631fa | |||
f98f782b40 | |||
01f5ecdc36 | |||
5297d3e142 | |||
f71f44968b | |||
7b79998c40 | |||
4632d407c1 | |||
58d7e59ca4 | |||
f592d8db84 | |||
31eca3728e | |||
c5d8779af4 | |||
cf686bdeb0 | |||
ae7143a94f | |||
f2b24849b3 | |||
35d6530406 | |||
01208221c7 | |||
fbbed3fbfb | |||
ce51f26701 |
@ -1,6 +1,6 @@
|
||||
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_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_SKIP_AUTH=false
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS=15000
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||
VITE_KC_SENTRY_DSN=
|
||||
|
2
.github/workflows/cargo-build.yml
vendored
2
.github/workflows/cargo-build.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
matrix:
|
||||
dir: ['src/wasm-lib']
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install latest rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
|
4
.github/workflows/cargo-clippy.yml
vendored
4
.github/workflows/cargo-clippy.yml
vendored
@ -24,7 +24,7 @@ jobs:
|
||||
matrix:
|
||||
dir: ['src/wasm-lib']
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install latest rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@ -54,4 +54,4 @@ jobs:
|
||||
- name: Run clippy
|
||||
run: |
|
||||
cd "${{ matrix.dir }}"
|
||||
cargo clippy --all --tests -- -D warnings
|
||||
cargo clippy --all --tests --benches -- -D warnings
|
||||
|
37
.github/workflows/cargo-criterion.yml
vendored
Normal file
37
.github/workflows/cargo-criterion.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-criterion.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- '**.rs'
|
||||
- '**/Cargo.toml'
|
||||
- '**/Cargo.lock'
|
||||
- '**/rust-toolchain.toml'
|
||||
- .github/workflows/cargo-criterion.yml
|
||||
workflow_dispatch:
|
||||
permissions: read-all
|
||||
name: cargo criterion
|
||||
jobs:
|
||||
cargocriterion:
|
||||
name: cargo criterion
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cargo install cargo-criterion
|
||||
- name: Rust Cache
|
||||
uses: Swatinem/rust-cache@v2.6.1
|
||||
- name: Benchmark kcl library
|
||||
shell: bash
|
||||
run: |-
|
||||
cd src/wasm-lib/kcl; cargo criterion
|
||||
|
2
.github/workflows/cargo-fmt.yml
vendored
2
.github/workflows/cargo-fmt.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
matrix:
|
||||
dir: ['src/wasm-lib', 'src-tauri']
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install latest rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
|
4
.github/workflows/cargo-test.yml
vendored
4
.github/workflows/cargo-test.yml
vendored
@ -26,7 +26,7 @@ jobs:
|
||||
matrix:
|
||||
dir: ['src/wasm-lib']
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install latest rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
@ -55,7 +55,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |-
|
||||
cd "${{ matrix.dir }}"
|
||||
cargo test --all
|
||||
cargo nextest run --workspace --no-fail-fast -P ci
|
||||
env:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||
|
||||
|
121
.github/workflows/ci.yml
vendored
121
.github/workflows/ci.yml
vendored
@ -4,16 +4,15 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- main
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
|
||||
check-format:
|
||||
runs-on: 'ubuntu-20.04'
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@ -25,7 +24,7 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
@ -33,19 +32,17 @@ 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@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
@ -56,7 +53,7 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "./src/wasm-lib"
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- run: yarn build:wasm
|
||||
|
||||
@ -69,7 +66,6 @@ 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 }}
|
||||
@ -77,8 +73,7 @@ jobs:
|
||||
matrix:
|
||||
os: [macos-latest, ubuntu-20.04, windows-latest]
|
||||
steps:
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: install ubuntu system dependencies
|
||||
if: matrix.os == 'ubuntu-20.04'
|
||||
@ -104,7 +99,7 @@ jobs:
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: "./src/wasm-lib"
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- name: wasm prep
|
||||
shell: bash
|
||||
@ -114,18 +109,6 @@ 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
|
||||
|
||||
@ -134,11 +117,42 @@ jobs:
|
||||
run: |
|
||||
rustup target add aarch64-apple-darwin
|
||||
|
||||
- name: Build the app for the current platform (no upload)
|
||||
- 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
|
||||
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' || '' }}
|
||||
|
||||
@ -146,68 +160,15 @@ 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, sign-windows-msi]
|
||||
needs: [build-test-web, build-apps]
|
||||
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
|
||||
|
2
.github/workflows/update-dev-branch.yml
vendored
2
.github/workflows/update-dev-branch.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.0
|
||||
- uses: actions/checkout@v4
|
||||
- shell: bash
|
||||
run: |
|
||||
# checkout our branch
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -22,6 +22,8 @@ npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
src/wasm-lib/.idea
|
||||
|
||||
# rust
|
||||
src/wasm-lib/target
|
||||
src/wasm-lib/bindings
|
||||
|
@ -1,4 +1,60 @@
|
||||
[
|
||||
{
|
||||
"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.",
|
||||
@ -7473,6 +7529,62 @@
|
||||
"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.",
|
||||
@ -8446,6 +8558,34 @@
|
||||
"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.",
|
||||
@ -9350,6 +9490,24 @@
|
||||
"unpublished": false,
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "e",
|
||||
"summary": "Return the value of Euler’s 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.",
|
||||
@ -9944,6 +10102,34 @@
|
||||
"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.",
|
||||
@ -13015,9 +13201,161 @@
|
||||
"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": "Returns the minimum of the given arguments.",
|
||||
"summary": "Computes the minimum of the given arguments.",
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"args": [
|
||||
@ -13048,7 +13386,7 @@
|
||||
},
|
||||
{
|
||||
"name": "pi",
|
||||
"summary": "Return the value of `pi`.",
|
||||
"summary": "Return the value of `pi`. Archimedes’ constant (π).",
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"args": [],
|
||||
@ -13064,6 +13402,43 @@
|
||||
"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.",
|
||||
@ -15376,6 +15751,34 @@
|
||||
"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.",
|
||||
@ -15891,6 +16294,24 @@
|
||||
"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.",
|
||||
|
319
docs/kcl/std.md
319
docs/kcl/std.md
@ -5,6 +5,8 @@
|
||||
## Table of Contents
|
||||
|
||||
* [Functions](#functions)
|
||||
* [`abs`](#abs)
|
||||
* [`acos`](#acos)
|
||||
* [`angleToMatchLengthX`](#angleToMatchLengthX)
|
||||
* [`angleToMatchLengthY`](#angleToMatchLengthY)
|
||||
* [`angledLine`](#angledLine)
|
||||
@ -14,10 +16,15 @@
|
||||
* [`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)
|
||||
@ -26,16 +33,24 @@
|
||||
* [`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)
|
||||
@ -44,6 +59,46 @@
|
||||
|
||||
## 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.
|
||||
@ -1332,6 +1387,46 @@ 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.
|
||||
@ -1497,6 +1592,26 @@ 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.
|
||||
@ -1661,6 +1776,25 @@ cos(num: number) -> number
|
||||
|
||||
|
||||
|
||||
### e
|
||||
|
||||
Return the value of Euler’s number `e`.
|
||||
|
||||
|
||||
|
||||
```
|
||||
e() -> number
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
|
||||
#### Returns
|
||||
|
||||
* `number`
|
||||
|
||||
|
||||
|
||||
### extrude
|
||||
|
||||
Extrudes by a given amount.
|
||||
@ -1770,6 +1904,26 @@ 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.
|
||||
@ -2358,9 +2512,110 @@ 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
|
||||
|
||||
Returns the minimum of the given arguments.
|
||||
Computes the minimum of the given arguments.
|
||||
|
||||
|
||||
|
||||
@ -2380,7 +2635,7 @@ min(args: [number]) -> number
|
||||
|
||||
### pi
|
||||
|
||||
Return the value of `pi`.
|
||||
Return the value of `pi`. Archimedes’ constant (π).
|
||||
|
||||
|
||||
|
||||
@ -2397,6 +2652,27 @@ 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.
|
||||
@ -2827,6 +3103,26 @@ 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.
|
||||
@ -2938,6 +3234,25 @@ 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.
|
||||
|
12
package.json
12
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "untitled-app",
|
||||
"version": "0.7.1",
|
||||
"version": "0.8.2",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.9.0",
|
||||
@ -62,15 +62,17 @@
|
||||
"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:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
|
||||
"test:cov": "vitest run --coverage --mode development",
|
||||
"simpleserver:ci": "http-server ./public --cors -p 3000 &",
|
||||
"simpleserver": "http-server ./public --cors -p 3000",
|
||||
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
||||
"fmt": "prettier --write ./src",
|
||||
"fmt-check": "prettier --check ./src",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
|
46
public/kcma-logomark-dark.svg
Normal file
46
public/kcma-logomark-dark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 16 KiB |
46
public/kcma-logomark.svg
Normal file
46
public/kcma-logomark.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 16 KiB |
BIN
public/onboarding-bracket-dark.png
Normal file
BIN
public/onboarding-bracket-dark.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 148 KiB |
BIN
public/onboarding-bracket.png
Normal file
BIN
public/onboarding-bracket.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 142 KiB |
30
src-tauri/Cargo.lock
generated
30
src-tauri/Cargo.lock
generated
@ -2182,6 +2182,17 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "006e42d5b888366f1880eda20371fedde764ed2213dc8496f49622fa0c99cd5e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
@ -3152,9 +3163,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.106"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
||||
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||
dependencies = [
|
||||
"itoa 1.0.6",
|
||||
"ryu",
|
||||
@ -3469,6 +3480,19 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sys-locale"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8a11bd9c338fdba09f7881ab41551932ad42e405f61d01e8406baea71c07aee"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "5.0.0"
|
||||
@ -3604,6 +3628,7 @@ dependencies = [
|
||||
"objc",
|
||||
"once_cell",
|
||||
"open",
|
||||
"os_info",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"raw-window-handle",
|
||||
@ -3616,6 +3641,7 @@ dependencies = [
|
||||
"serde_repr",
|
||||
"serialize-to-javascript",
|
||||
"state",
|
||||
"sys-locale",
|
||||
"tar",
|
||||
"tauri-macros",
|
||||
"tauri-runtime",
|
||||
|
@ -20,7 +20,7 @@ kittycad = "0.2.25"
|
||||
oauth2 = "4.4.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tauri = { version = "1.4.1", features = ["dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
|
||||
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" }
|
||||
tokio = { version = "1.32.0", features = ["time"] }
|
||||
toml = "0.8.0"
|
||||
|
@ -6,6 +6,7 @@ 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]
|
||||
@ -88,11 +89,34 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
|
||||
///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>) -> Result<kittycad::types::User, InvokeError> {
|
||||
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 client = kittycad::Client::new(token.unwrap());
|
||||
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()
|
||||
|
@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "kittycad-modeling",
|
||||
"version": "0.7.1"
|
||||
"version": "0.8.2"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
@ -36,6 +36,9 @@
|
||||
"https://api.dev.kittycad.io/*"
|
||||
]
|
||||
},
|
||||
"os": {
|
||||
"all": true
|
||||
},
|
||||
"shell": {
|
||||
"open": true
|
||||
},
|
||||
@ -71,9 +74,9 @@
|
||||
"shortDescription": "",
|
||||
"targets": "all",
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"certificateThumbprint": "F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D",
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": ""
|
||||
"timestampUrl": "http://timestamp.digicert.com"
|
||||
}
|
||||
},
|
||||
"security": {
|
||||
|
30
src/App.tsx
30
src/App.tsx
@ -1,7 +1,6 @@
|
||||
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
|
||||
import { DebugPanel } from './components/DebugPanel'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { _executor } from './lang/executor'
|
||||
import { PaneType, useStore } from './useStore'
|
||||
import { Logs, KCLErrors } from './components/Logs'
|
||||
import { CollapsiblePanel } from './components/CollapsiblePanel'
|
||||
@ -48,6 +47,7 @@ export function App() {
|
||||
streamDimensions,
|
||||
guiMode,
|
||||
setGuiMode,
|
||||
executeAst,
|
||||
} = useStore((s) => ({
|
||||
guiMode: s.guiMode,
|
||||
setGuiMode: s.setGuiMode,
|
||||
@ -58,6 +58,7 @@ export function App() {
|
||||
setOpenPanes: s.setOpenPanes,
|
||||
didDragInStream: s.didDragInStream,
|
||||
streamDimensions: s.streamDimensions,
|
||||
executeAst: s.executeAst,
|
||||
}))
|
||||
|
||||
const {
|
||||
@ -88,12 +89,23 @@ export function App() {
|
||||
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',
|
||||
@ -109,6 +121,7 @@ export function App() {
|
||||
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
|
||||
})
|
||||
}
|
||||
@ -117,12 +130,13 @@ export function App() {
|
||||
}
|
||||
})
|
||||
|
||||
const paneOpacity =
|
||||
onboardingStatus === onboardingPaths.CAMERA
|
||||
? 'opacity-20'
|
||||
: didDragInStream
|
||||
? 'opacity-40'
|
||||
: ''
|
||||
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
|
||||
(p) => p === onboardingStatus
|
||||
)
|
||||
? 'opacity-20'
|
||||
: didDragInStream
|
||||
? 'opacity-40'
|
||||
: ''
|
||||
|
||||
// Use file code loaded from disk
|
||||
// on mount, and overwrite any locally-stored code
|
||||
@ -255,7 +269,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 className="h-full flex flex-col justify-between">
|
||||
<div id="code-pane" className="h-full flex flex-col justify-between">
|
||||
<CollapsiblePanel
|
||||
title="Code"
|
||||
icon={faCode}
|
||||
|
@ -6,9 +6,9 @@ export const Auth = ({ children }: React.PropsWithChildren) => {
|
||||
const {
|
||||
auth: { state },
|
||||
} = useGlobalStateContext()
|
||||
const isLoggedIn = state.matches('checkIfLoggedIn')
|
||||
const isLoggingIn = state.matches('checkIfLoggedIn')
|
||||
|
||||
return isLoggedIn ? (
|
||||
return isLoggingIn ? (
|
||||
<Loading>Loading KittyCAD Modeling App...</Loading>
|
||||
) : (
|
||||
<>{children}</>
|
||||
|
@ -130,6 +130,7 @@ const router = createBrowserRouter(
|
||||
path: paths.INDEX,
|
||||
loader: () =>
|
||||
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
|
||||
errorElement: <ErrorPage />,
|
||||
},
|
||||
{
|
||||
path: paths.FILE + '/:id',
|
||||
@ -140,7 +141,6 @@ const router = createBrowserRouter(
|
||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||
</Auth>
|
||||
),
|
||||
errorElement: <ErrorPage />,
|
||||
id: paths.FILE,
|
||||
loader: async ({
|
||||
request,
|
||||
|
@ -47,15 +47,52 @@
|
||||
@apply hover:bg-cool-20;
|
||||
}
|
||||
|
||||
.smallScrollbar::-webkit-scrollbar {
|
||||
.toolbarButtons::-webkit-scrollbar {
|
||||
@apply h-0.5;
|
||||
}
|
||||
|
||||
.smallScrollbar {
|
||||
@apply overflow-x-auto;
|
||||
.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;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useStore, toolTips, Selections } from './useStore'
|
||||
import { useStore, toolTips, ToolTip } from './useStore'
|
||||
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
||||
import { getNodePathFromSourceRange } from './lang/queryAst'
|
||||
import { HorzVert } from './components/Toolbar/HorzVert'
|
||||
@ -17,6 +17,30 @@ 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 {
|
||||
@ -44,9 +68,9 @@ export const Toolbar = () => {
|
||||
console.log('guiMode', guiMode)
|
||||
}, [guiMode])
|
||||
|
||||
function ToolbarButtons() {
|
||||
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
|
||||
return (
|
||||
<span className={styles.smallScrollbar}>
|
||||
<span className={styles.toolbarButtons + ' ' + className}>
|
||||
{guiMode.mode === 'default' && (
|
||||
<button
|
||||
onClick={() => {
|
||||
@ -55,7 +79,9 @@ export const Toolbar = () => {
|
||||
sketchMode: 'selectFace',
|
||||
})
|
||||
}}
|
||||
className="group"
|
||||
>
|
||||
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||
Start Sketch
|
||||
</button>
|
||||
)}
|
||||
@ -74,31 +100,31 @@ export const Toolbar = () => {
|
||||
)
|
||||
updateAst(modifiedAst, true)
|
||||
}}
|
||||
className="group"
|
||||
>
|
||||
SketchOnFace
|
||||
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||
Sketch on Face
|
||||
</button>
|
||||
)}
|
||||
{guiMode.mode === 'canEditSketch' && (
|
||||
<button
|
||||
onClick={() => {
|
||||
console.log('guiMode.pathId', guiMode.pathId)
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'edit_mode_enter',
|
||||
target: guiMode.pathId,
|
||||
},
|
||||
})
|
||||
const pathToNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
selectionRanges.codeBasedSelections[0].range
|
||||
)
|
||||
setGuiMode({
|
||||
mode: 'sketch',
|
||||
sketchMode: 'sketchEdit',
|
||||
pathToNode: guiMode.pathToNode,
|
||||
rotation: guiMode.rotation,
|
||||
position: guiMode.position,
|
||||
sketchMode: 'enterSketchEdit',
|
||||
pathToNode: pathToNode,
|
||||
rotation: [0, 0, 0, 1],
|
||||
position: [0, 0, 0],
|
||||
pathId: guiMode.pathId,
|
||||
})
|
||||
}}
|
||||
className="group"
|
||||
>
|
||||
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||
Edit Sketch
|
||||
</button>
|
||||
)}
|
||||
@ -117,8 +143,10 @@ export const Toolbar = () => {
|
||||
)
|
||||
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
||||
}}
|
||||
className="group"
|
||||
>
|
||||
ExtrudeSketch
|
||||
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
|
||||
Extrude
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
@ -134,8 +162,10 @@ export const Toolbar = () => {
|
||||
)
|
||||
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
||||
}}
|
||||
className="group"
|
||||
>
|
||||
ExtrudeSketch (w/o pipe)
|
||||
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
|
||||
Extrude as new
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
@ -157,7 +187,15 @@ export const Toolbar = () => {
|
||||
setGuiMode({ mode: 'default' })
|
||||
executeAst()
|
||||
}}
|
||||
className="group"
|
||||
>
|
||||
<ActionIcon
|
||||
icon="exit"
|
||||
className="!p-0.5"
|
||||
bgClassName={sketchButtonClassnames.background}
|
||||
iconClassName={sketchButtonClassnames.icon}
|
||||
size="md"
|
||||
/>
|
||||
Exit sketch
|
||||
</button>
|
||||
)}
|
||||
@ -198,12 +236,25 @@ 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'
|
||||
: '')
|
||||
}
|
||||
>
|
||||
{sketchFnName}
|
||||
{guiMode.sketchMode === sketchFnName && '✅'}
|
||||
<ActionIcon
|
||||
icon={sketchFnName.includes('line') ? 'line' : 'move'}
|
||||
className="!p-0.5"
|
||||
bgClassName={sketchButtonClassnames.background}
|
||||
iconClassName={sketchButtonClassnames.icon}
|
||||
size="md"
|
||||
/>
|
||||
{sketchFnLabels[sketchFnName]}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
@ -234,7 +285,7 @@ export const Toolbar = () => {
|
||||
<span className={styles.toolbarCap + ' ' + styles.label}>
|
||||
{guiMode.mode === 'sketch' ? '2D' : '3D'}
|
||||
</span>
|
||||
<menu className="flex flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
|
||||
<menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
|
||||
<ToolbarButtons />
|
||||
</menu>
|
||||
<Popover.Button
|
||||
@ -275,7 +326,7 @@ export const Toolbar = () => {
|
||||
</Popover.Button>
|
||||
</section>
|
||||
<section>
|
||||
<ToolbarButtons />
|
||||
<ToolbarButtons className="flex-wrap" />
|
||||
</section>
|
||||
</Popover.Panel>
|
||||
</Transition>
|
||||
|
@ -4,6 +4,7 @@ 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,
|
||||
@ -13,7 +14,7 @@ const iconSizes = {
|
||||
}
|
||||
|
||||
export interface ActionIconProps extends React.PropsWithChildren {
|
||||
icon?: SolidIconDefinition | BrandIconDefinition
|
||||
icon?: SolidIconDefinition | BrandIconDefinition | CustomIconName
|
||||
className?: string
|
||||
bgClassName?: string
|
||||
iconClassName?: string
|
||||
@ -28,25 +29,39 @@ 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} ` +
|
||||
(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')
|
||||
computedBgClassName
|
||||
}
|
||||
>
|
||||
{children || (
|
||||
{children ? (
|
||||
children
|
||||
) : typeof icon === 'string' ? (
|
||||
<CustomIcon
|
||||
name={icon}
|
||||
width={iconSizes[size]}
|
||||
height={iconSizes[size]}
|
||||
className={computedIconClassName}
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={icon}
|
||||
width={iconSizes[size]}
|
||||
height={iconSizes[size]}
|
||||
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'
|
||||
}
|
||||
className={computedIconClassName}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -29,7 +29,7 @@ export const AppHeader = ({
|
||||
return (
|
||||
<header
|
||||
className={
|
||||
(showToolbar ? 'grid ' : 'flex justify-between ') +
|
||||
(showToolbar ? 'w-full 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-4xl">
|
||||
<div className="max-w-lg md:max-w-xl lg:max-w-2xl xl:max-w-4xl 2xl:max-w-5xl">
|
||||
<Toolbar />
|
||||
</div>
|
||||
)}
|
||||
|
@ -23,7 +23,8 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||
<div
|
||||
className="relative"
|
||||
onClick={(e) => {
|
||||
if (e.eventPhase === 3) {
|
||||
const target = e.target as HTMLElement
|
||||
if (e.eventPhase === 3 && target.closest('a') === null) {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ export const CommandBarProvider = ({
|
||||
|
||||
const CommandBar = () => {
|
||||
const { commands, commandBarOpen, setCommandBarOpen } = useCommandsContext()
|
||||
useHotkeys('meta+k', () => {
|
||||
useHotkeys(['meta+k', 'meta+/'], () => {
|
||||
if (commands.length === 0) return
|
||||
setCommandBarOpen(!commandBarOpen)
|
||||
})
|
||||
@ -221,10 +221,10 @@ const CommandBar = () => {
|
||||
<Combobox
|
||||
value={selectedCommand}
|
||||
onChange={handleCommandSelection}
|
||||
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"
|
||||
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"
|
||||
as="div"
|
||||
>
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
<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="bg-transparent focus:outline-none w-full"
|
||||
className="w-full bg-transparent focus:outline-none"
|
||||
onKeyDown={(event) => {
|
||||
if (event.metaKey && event.key === 'k')
|
||||
setCommandBarOpen(false)
|
||||
@ -264,12 +264,12 @@ const CommandBar = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Combobox.Options static className="max-h-96 overflow-y-auto">
|
||||
<Combobox.Options static className="overflow-y-auto max-h-96">
|
||||
{filteredCommands?.map((commandResult) => (
|
||||
<Combobox.Option
|
||||
key={commandResult.item.name}
|
||||
value={commandResult}
|
||||
className="my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90 py-1 px-2"
|
||||
className="px-2 py-1 my-2 first:mt-4 last:mb-4 ui-active:bg-liquid-10 dark:ui-active:bg-liquid-90"
|
||||
>
|
||||
<p>{commandResult.item.name}</p>
|
||||
{(commandResult.item as SubCommand).description && (
|
||||
|
161
src/components/CustomIcon.tsx
Normal file
161
src/components/CustomIcon.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
}
|
@ -30,7 +30,11 @@ export const DebugPanel = ({ className, ...props }: CollapsiblePanelProps) => {
|
||||
return (
|
||||
<CollapsiblePanel
|
||||
{...props}
|
||||
className={'!absolute !h-auto bottom-5 right-5 ' + className}
|
||||
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)' }}
|
||||
>
|
||||
<section className="p-4 flex flex-col gap-4">
|
||||
<Xyz
|
||||
|
@ -40,12 +40,12 @@ const DownloadAppBanner = () => {
|
||||
</code>
|
||||
, and isn't backed up anywhere! Visit{' '}
|
||||
<a
|
||||
href="https://github.com/KittyCAD/modeling-app/releases"
|
||||
href="https://kittycad.io/modeling-app/download"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="text-warn-80 dark:text-warn-80 dark:hover:text-warn-70 underline"
|
||||
>
|
||||
our GitHub repository
|
||||
our website
|
||||
</a>{' '}
|
||||
to download the app for the best experience.
|
||||
</p>
|
||||
|
@ -1,8 +1,62 @@
|
||||
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">
|
||||
<h1 className="text-4xl font-bold">404</h1>
|
||||
<p className="text-2xl font-bold">Page not found</p>
|
||||
<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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -24,7 +24,11 @@ export const MemoryPanel = ({
|
||||
<CollapsiblePanel {...props}>
|
||||
<div className="h-full relative">
|
||||
<div className="absolute inset-0 flex flex-col items-start">
|
||||
<div className=" h-full console-tile w-full">
|
||||
<div
|
||||
className="overflow-y-auto h-full console-tile w-full"
|
||||
style={{ marginBottom: 36 }}
|
||||
>
|
||||
{/* 36px is the height of PanelHeader */}
|
||||
<ReactJson
|
||||
src={ProcessedMemory}
|
||||
collapsed={1}
|
||||
|
@ -1,42 +0,0 @@
|
||||
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>
|
||||
}
|
@ -16,8 +16,8 @@ const ProjectSidebarMenu = ({
|
||||
}) => {
|
||||
return renderAsLink ? (
|
||||
<Link
|
||||
to={'../'}
|
||||
className="flex items-center gap-4 my-2"
|
||||
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"
|
||||
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 min-w-max"
|
||||
className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block"
|
||||
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="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="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"
|
||||
data-testid="project-sidebar-toggle"
|
||||
>
|
||||
<img
|
||||
src="/kitt-8bit-winking.svg"
|
||||
alt="KittyCAD App"
|
||||
className="h-9 w-auto"
|
||||
className="h-full w-auto"
|
||||
/>
|
||||
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 min-w-max">
|
||||
<span className="text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap hidden lg:block">
|
||||
{isTauri() && project?.name ? project.name : 'KittyCAD Modeling App'}
|
||||
</span>
|
||||
</Popover.Button>
|
||||
|
@ -21,6 +21,10 @@ 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)
|
||||
@ -211,14 +215,9 @@ export const Stream = ({ className = '' }) => {
|
||||
}
|
||||
}
|
||||
engineCommandManager?.sendSceneCommand(command).then(async (resp) => {
|
||||
if (command?.cmd?.type !== 'mouse_click' || !ast) return
|
||||
if (
|
||||
!(
|
||||
guiMode.mode === 'sketch' &&
|
||||
guiMode.sketchMode === ('sketch_line' as any as 'line')
|
||||
)
|
||||
)
|
||||
return
|
||||
if (!(guiMode.mode === 'sketch')) return
|
||||
|
||||
if (guiMode.sketchMode === 'selectFace') return
|
||||
|
||||
// Check if the sketch group already exists.
|
||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||
@ -230,6 +229,56 @@ export const Stream = ({ className = '' }) => {
|
||||
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 (
|
||||
resp?.data?.data?.entities_modified?.length &&
|
||||
@ -257,6 +306,16 @@ 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,
|
||||
@ -315,9 +374,12 @@ export const Stream = ({ className = '' }) => {
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'sketch_mode_disable',
|
||||
},
|
||||
cmd: { type: 'edit_mode_exit' },
|
||||
})
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||
})
|
||||
updateAst(_modifiedAst, true)
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ 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 } =
|
||||
@ -87,9 +89,17 @@ export const EqualAngle = () => {
|
||||
})
|
||||
}}
|
||||
disabled={!enableEqual}
|
||||
title="yo dawg"
|
||||
title="Parallel (or equal angle)"
|
||||
className="group"
|
||||
>
|
||||
parallel
|
||||
<ActionIcon
|
||||
icon="parallel"
|
||||
className="!p-0.5"
|
||||
bgClassName={sketchButtonClassnames.background}
|
||||
iconClassName={sketchButtonClassnames.icon}
|
||||
size="md"
|
||||
/>
|
||||
Parallel
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -12,6 +12,8 @@ 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 } =
|
||||
@ -87,9 +89,17 @@ export const EqualLength = () => {
|
||||
})
|
||||
}}
|
||||
disabled={!enableEqual}
|
||||
title="yo dawg"
|
||||
className="group"
|
||||
title="Equal Length"
|
||||
>
|
||||
EqualLength
|
||||
<ActionIcon
|
||||
icon="equal"
|
||||
className="!p-0.5"
|
||||
bgClassName={sketchButtonClassnames.background}
|
||||
iconClassName={sketchButtonClassnames.icon}
|
||||
size="md"
|
||||
/>
|
||||
Equal Length
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ import {
|
||||
transformAstSketchLines,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { updateCursors } from '../../lang/util'
|
||||
import { ActionIcon } from 'components/ActionIcon'
|
||||
import { sketchButtonClassnames } from 'Toolbar'
|
||||
|
||||
export const HorzVert = ({
|
||||
horOrVert,
|
||||
@ -66,9 +68,17 @@ export const HorzVert = ({
|
||||
})
|
||||
}}
|
||||
disabled={!enableHorz}
|
||||
title="yo dawg"
|
||||
className="group"
|
||||
title={horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
|
||||
>
|
||||
{horOrVert === 'horizontal' ? 'Horz' : 'Vert'}
|
||||
<ActionIcon
|
||||
icon={horOrVert === 'horizontal' ? 'horizontal' : 'vertical'}
|
||||
className="!p-0.5"
|
||||
bgClassName={sketchButtonClassnames.background}
|
||||
iconClassName={sketchButtonClassnames.icon}
|
||||
size="md"
|
||||
/>
|
||||
{horOrVert === 'horizontal' ? 'Horizontal' : 'Vertical'}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -188,8 +188,9 @@ export const Intersect = () => {
|
||||
}
|
||||
}}
|
||||
disabled={!enable}
|
||||
title="Set Perpendicular Distance"
|
||||
>
|
||||
perpendicularDistance
|
||||
Set Perpendicular Distance
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -70,9 +70,9 @@ export const RemoveConstrainingValues = () => {
|
||||
})
|
||||
}}
|
||||
disabled={!enableHorz}
|
||||
title="yo dawg"
|
||||
title="Remove Constraining Values"
|
||||
>
|
||||
RemoveConstrainingValues
|
||||
Remove Constraining Values
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -22,11 +22,16 @@ import { updateCursors } from '../../lang/util'
|
||||
|
||||
const getModalInfo = create(SetAngleLengthModal as any)
|
||||
|
||||
export const SetAbsDistance = ({
|
||||
buttonType,
|
||||
}: {
|
||||
buttonType: 'xAbs' | 'yAbs' | 'snapToYAxis' | 'snapToXAxis'
|
||||
}) => {
|
||||
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 }) => {
|
||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||
useStore((s) => ({
|
||||
guiMode: s.guiMode,
|
||||
@ -132,8 +137,9 @@ export const SetAbsDistance = ({
|
||||
}
|
||||
}}
|
||||
disabled={!enableAngLen}
|
||||
title={buttonLabels[buttonType]}
|
||||
>
|
||||
{buttonType}
|
||||
{buttonLabels[buttonType]}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -147,8 +147,9 @@ export const SetAngleBetween = () => {
|
||||
}
|
||||
}}
|
||||
disabled={!enable}
|
||||
title="Set Angle Between"
|
||||
>
|
||||
angleBetween
|
||||
Set Angle Between
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -21,17 +21,28 @@ 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:
|
||||
| 'setHorzDistance'
|
||||
| 'setVertDistance'
|
||||
| 'alignEndsHorizontally'
|
||||
| 'alignEndsVertically'
|
||||
buttonType: ButtonType
|
||||
}) => {
|
||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||
useStore((s) => ({
|
||||
@ -169,8 +180,9 @@ export const SetHorzVertDistance = ({
|
||||
}
|
||||
}}
|
||||
disabled={!enable}
|
||||
title={buttonLabels[buttonType]}
|
||||
>
|
||||
{buttonType}
|
||||
{buttonLabels[buttonType]}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -23,10 +23,17 @@ 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: 'setAngle' | 'setLength'
|
||||
angleOrLength: ButtonType
|
||||
}) => {
|
||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
||||
useStore((s) => ({
|
||||
@ -144,8 +151,9 @@ export const SetAngleLength = ({
|
||||
}
|
||||
}}
|
||||
disabled={!enableAngLen}
|
||||
title={buttonLabels[angleOrLength]}
|
||||
>
|
||||
{angleOrLength}
|
||||
{buttonLabels[angleOrLength]}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
@ -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 { useNavigate } from 'react-router-dom'
|
||||
import { useLocation, useNavigate } from 'react-router-dom'
|
||||
import { Fragment, useState } from 'react'
|
||||
import { paths } from '../Router'
|
||||
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
|
||||
@ -12,6 +12,7 @@ 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()
|
||||
@ -38,7 +39,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
<Popover className="relative">
|
||||
{user?.image && !imageLoadFailed ? (
|
||||
<Popover.Button
|
||||
className="border-0 rounded-full w-fit p-0 focus:outline-none group"
|
||||
className="border-0 rounded-full w-fit min-w-max 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">
|
||||
@ -126,7 +127,11 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||
// since /settings is a nested route the sidebar doesn't close
|
||||
// automatically when navigating to it
|
||||
close()
|
||||
navigate(makeUrlPathRelative(paths.SETTINGS))
|
||||
navigate(
|
||||
(location.pathname.endsWith('/')
|
||||
? location.pathname.slice(0, -1)
|
||||
: location.pathname) + paths.SETTINGS
|
||||
)
|
||||
}}
|
||||
>
|
||||
Settings
|
||||
|
@ -5,8 +5,6 @@ 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
|
||||
@ -26,12 +24,8 @@ export default class Server {
|
||||
intoServer: IntoServer,
|
||||
fromServer: FromServer
|
||||
): Promise<Server> {
|
||||
if (null == server) {
|
||||
const initOutput = await init()
|
||||
server = new Server(initOutput, intoServer, fromServer)
|
||||
} else {
|
||||
console.warn('Server already initialized; ignoring')
|
||||
}
|
||||
const initOutput = await init()
|
||||
const server = new Server(initOutput, intoServer, fromServer)
|
||||
return server
|
||||
}
|
||||
|
||||
|
@ -37,27 +37,62 @@ export function useAppMode() {
|
||||
guiMode.sketchMode === 'selectFace' &&
|
||||
engineCommandManager
|
||||
) {
|
||||
if (!defaultPlanes) {
|
||||
const xy = createPlane(engineCommandManager, {
|
||||
x_axis: { x: 1, y: 0, z: 0 },
|
||||
y_axis: { x: 0, y: 1, z: 0 },
|
||||
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
||||
})
|
||||
// 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 },
|
||||
// })
|
||||
setDefaultPlanes({ xy })
|
||||
} else {
|
||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, false)
|
||||
const createAndShowPlanes = async () => {
|
||||
let localDefaultPlanes: DefaultPlanes
|
||||
if (!defaultPlanes) {
|
||||
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
|
||||
setDefaultPlanes(localDefaultPlanes)
|
||||
} else {
|
||||
localDefaultPlanes = defaultPlanes
|
||||
}
|
||||
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, false)
|
||||
}
|
||||
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({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'sketch_mode_enable',
|
||||
plane_id: localDefaultPlanes.xy,
|
||||
ortho: true,
|
||||
animated: !isReducedMotion(),
|
||||
},
|
||||
})
|
||||
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)
|
||||
@ -151,6 +186,7 @@ export function useAppMode() {
|
||||
rotation: [0, 0, 0, 1],
|
||||
position: [0, 0, 0],
|
||||
pathToNode: [],
|
||||
pathId: sketchUuid,
|
||||
})
|
||||
|
||||
console.log('sketchModeResponse', sketchModeResponse)
|
||||
@ -160,7 +196,7 @@ export function useAppMode() {
|
||||
}, [engineCommandManager, defaultPlanes])
|
||||
}
|
||||
|
||||
function createPlane(
|
||||
async function createPlane(
|
||||
engineCommandManager: EngineCommandManager,
|
||||
{
|
||||
x_axis,
|
||||
@ -173,7 +209,7 @@ function createPlane(
|
||||
}
|
||||
) {
|
||||
const planeId = uuidv4()
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
await engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'make_plane',
|
||||
@ -185,7 +221,7 @@ function createPlane(
|
||||
},
|
||||
cmd_id: planeId,
|
||||
})
|
||||
engineCommandManager?.sendSceneCommand({
|
||||
await engineCommandManager?.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'plane_set_color',
|
||||
@ -215,6 +251,28 @@ function setDefaultPlanesHidden(
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
|
13
src/hooks/useDotDotSlash.ts
Normal file
13
src/hooks/useDotDotSlash.ts
Normal file
@ -0,0 +1,13 @@
|
||||
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
|
||||
}
|
@ -3,6 +3,55 @@ import { _executor } from '../lang/executor'
|
||||
import { useStore } from '../useStore'
|
||||
import { EngineCommandManager } from '../lang/std/engineConnection'
|
||||
|
||||
class EngineCommandManagerSingleton {
|
||||
width: number
|
||||
height: number
|
||||
engineCommandManager?: EngineCommandManager
|
||||
|
||||
constructor() {
|
||||
this.width = 0
|
||||
this.height = 0
|
||||
}
|
||||
|
||||
get({
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
width,
|
||||
height,
|
||||
token,
|
||||
}: {
|
||||
setMediaStream: (stream: MediaStream) => void
|
||||
setIsStreamReady: (isStreamReady: boolean) => void
|
||||
width: number
|
||||
height: number
|
||||
token?: string
|
||||
}): EngineCommandManager {
|
||||
if (
|
||||
this.engineCommandManager !== undefined &&
|
||||
this.width == width &&
|
||||
this.height == height
|
||||
) {
|
||||
return this.engineCommandManager
|
||||
}
|
||||
|
||||
const ecm = new EngineCommandManager({
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
width: width,
|
||||
height: height,
|
||||
token,
|
||||
})
|
||||
|
||||
this.engineCommandManager = ecm
|
||||
this.width = width
|
||||
this.height = height
|
||||
|
||||
return ecm
|
||||
}
|
||||
}
|
||||
|
||||
const engineManagerSingleton = new EngineCommandManagerSingleton()
|
||||
|
||||
export function useSetupEngineManager(
|
||||
streamRef: React.RefObject<HTMLDivElement>,
|
||||
token?: string
|
||||
@ -35,7 +84,7 @@ export function useSetupEngineManager(
|
||||
streamHeight: quadHeight,
|
||||
})
|
||||
if (!width || !height) return
|
||||
const eng = new EngineCommandManager({
|
||||
const eng = engineManagerSingleton.get({
|
||||
setMediaStream,
|
||||
setIsStreamReady,
|
||||
width: quadWidth,
|
||||
|
@ -31,6 +31,14 @@ 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;
|
||||
|
@ -139,54 +139,6 @@ 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', () => {
|
||||
@ -1560,7 +1512,7 @@ const yo = { a: { b: { c: '123' } } }
|
||||
// this is a comment
|
||||
const key = 'c'`
|
||||
const nonCodeMetaInstance = {
|
||||
type: 'NoneCodeNode',
|
||||
type: 'NonCodeNode',
|
||||
start: code.indexOf('\n// this is a comment'),
|
||||
end: code.indexOf('const key'),
|
||||
value: {
|
||||
@ -1569,17 +1521,17 @@ const key = 'c'`
|
||||
},
|
||||
}
|
||||
const { nonCodeMeta } = parser_wasm(code)
|
||||
expect(nonCodeMeta.noneCodeNodes[0]).toEqual(nonCodeMetaInstance)
|
||||
expect(nonCodeMeta.nonCodeNodes[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.noneCodeNodes[0].value).toStrictEqual(
|
||||
expect(nonCodeMeta2.nonCodeNodes[0].value).toStrictEqual(
|
||||
nonCodeMetaInstance.value
|
||||
)
|
||||
expect(nonCodeMeta2.noneCodeNodes[0].start).not.toBe(
|
||||
expect(nonCodeMeta2.nonCodeNodes[0].start).not.toBe(
|
||||
nonCodeMetaInstance.start
|
||||
)
|
||||
})
|
||||
@ -1596,9 +1548,9 @@ const key = 'c'`
|
||||
const { body } = parser_wasm(code)
|
||||
const indexOfSecondLineToExpression = 2
|
||||
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
|
||||
.noneCodeNodes
|
||||
.nonCodeNodes
|
||||
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
|
||||
type: 'NoneCodeNode',
|
||||
type: 'NonCodeNode',
|
||||
start: 106,
|
||||
end: 166,
|
||||
value: {
|
||||
@ -1619,9 +1571,9 @@ const key = 'c'`
|
||||
|
||||
const { body } = parser_wasm(code)
|
||||
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
|
||||
.noneCodeNodes
|
||||
.nonCodeNodes
|
||||
expect(sketchNonCodeMeta[3]).toEqual({
|
||||
type: 'NoneCodeNode',
|
||||
type: 'NonCodeNode',
|
||||
start: 125,
|
||||
end: 141,
|
||||
value: {
|
||||
|
@ -33,5 +33,5 @@ export type SyntaxType =
|
||||
| 'PipeExpression'
|
||||
| 'PipeSubstitution'
|
||||
| 'Literal'
|
||||
| 'NoneCodeNode'
|
||||
| 'NonCodeNode'
|
||||
| 'UnaryExpression'
|
||||
|
@ -106,7 +106,7 @@ describe('Testing addSketchTo', () => {
|
||||
body: [],
|
||||
start: 0,
|
||||
end: 0,
|
||||
nonCodeMeta: { noneCodeNodes: {}, start: null },
|
||||
nonCodeMeta: { nonCodeNodes: {}, start: null },
|
||||
},
|
||||
'yz'
|
||||
)
|
||||
|
@ -537,7 +537,7 @@ export function createPipeExpression(
|
||||
start: 0,
|
||||
end: 0,
|
||||
body,
|
||||
nonCodeMeta: { noneCodeNodes: {}, start: null },
|
||||
nonCodeMeta: { nonCodeNodes: {}, start: null },
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -15,9 +15,13 @@ 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'
|
||||
@ -37,8 +41,6 @@ interface NewTrackArgs {
|
||||
mediaStream: MediaStream
|
||||
}
|
||||
|
||||
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
||||
|
||||
type ClientMetrics = Models['ClientMetrics_type']
|
||||
|
||||
// EngineConnection encapsulates the connection(s) to the Engine
|
||||
@ -474,6 +476,13 @@ 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?
|
||||
@ -645,12 +654,14 @@ 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] = {
|
||||
@ -658,6 +669,7 @@ export class EngineCommandManager {
|
||||
commandType: command?.commandType,
|
||||
range: command?.range,
|
||||
data: modelingResponse,
|
||||
raw: message,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -777,9 +789,7 @@ export class EngineCommandManager {
|
||||
) {
|
||||
cmd.sequence = this.outSequence
|
||||
this.outSequence++
|
||||
this.engineConnection?.unreliableDataChannel?.send(
|
||||
JSON.stringify(command)
|
||||
)
|
||||
this.engineConnection?.unreliableSend(command)
|
||||
return Promise.resolve()
|
||||
} else if (
|
||||
cmd.type === 'highlight_set_entity' &&
|
||||
@ -787,9 +797,7 @@ export class EngineCommandManager {
|
||||
) {
|
||||
cmd.sequence = this.outSequence
|
||||
this.outSequence++
|
||||
this.engineConnection?.unreliableDataChannel?.send(
|
||||
JSON.stringify(command)
|
||||
)
|
||||
this.engineConnection?.unreliableSend(command)
|
||||
return Promise.resolve()
|
||||
} else if (
|
||||
cmd.type === 'mouse_move' &&
|
||||
@ -797,9 +805,7 @@ export class EngineCommandManager {
|
||||
) {
|
||||
cmd.sequence = this.outSequence
|
||||
this.outSequence++
|
||||
this.engineConnection?.unreliableDataChannel?.send(
|
||||
JSON.stringify(command)
|
||||
)
|
||||
this.engineConnection?.unreliableSend(command)
|
||||
return Promise.resolve()
|
||||
}
|
||||
// since it's not mouse drag or highlighting send over TCP and keep track of the command
|
||||
@ -872,7 +878,10 @@ export class EngineCommandManager {
|
||||
}
|
||||
const range: SourceRange = JSON.parse(rangeStr)
|
||||
|
||||
return this.sendModelingCommand({ id, range, command: commandStr })
|
||||
// We only care about the modeling command response.
|
||||
return this.sendModelingCommand({ id, range, command: commandStr }).then(
|
||||
({ raw }) => JSON.stringify(raw)
|
||||
)
|
||||
}
|
||||
commandResult(id: string): Promise<any> {
|
||||
const command = this.artifactMap[id]
|
||||
@ -942,7 +951,6 @@ export class EngineCommandManager {
|
||||
pathInfos.forEach(({ originalId, segments }) => {
|
||||
const originalArtifact = this.artifactMap[originalId]
|
||||
if (!originalArtifact || originalArtifact.type === 'pending') {
|
||||
console.log('problem')
|
||||
return
|
||||
}
|
||||
const pipeExpPath = getNodePathFromSourceRange(
|
||||
@ -955,23 +963,20 @@ 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++) {
|
||||
@ -981,8 +986,10 @@ export class EngineCommandManager {
|
||||
const artifact = this.artifactMap[oldId]
|
||||
delete this.artifactMap[oldId]
|
||||
delete this.sourceRangeMap[oldId]
|
||||
this.artifactMap[engineSegment.command_id] = artifact
|
||||
this.sourceRangeMap[engineSegment.command_id] = artifact.range
|
||||
if (artifact) {
|
||||
this.artifactMap[engineSegment.command_id] = artifact
|
||||
this.sourceRangeMap[engineSegment.command_id] = artifact.range
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -117,6 +117,7 @@ show(mySketch001)
|
||||
{
|
||||
mode: 'sketch',
|
||||
sketchMode: 'sketchEdit',
|
||||
pathId: '',
|
||||
rotation: [0, 0, 0, 1],
|
||||
position: [0, 0, 0],
|
||||
pathToNode: [
|
||||
|
21
src/lib/exampleKcl.ts
Normal file
21
src/lib/exampleKcl.ts
Normal file
@ -0,0 +1,21 @@
|
||||
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)
|
||||
`
|
@ -4,6 +4,7 @@ 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
|
||||
@ -132,6 +133,7 @@ async function getUser(context: UserContext) {
|
||||
.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
|
||||
|
@ -2,48 +2,78 @@ 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.SKETCHING)
|
||||
const next = useNextClick(onboardingPaths.STREAMING)
|
||||
const {
|
||||
settings: {
|
||||
send,
|
||||
state: {
|
||||
context: { cameraControls },
|
||||
},
|
||||
},
|
||||
} = useGlobalStateContext()
|
||||
|
||||
return (
|
||||
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
||||
<div className="fixed inset-0 z-50 grid items-end justify-start px-4 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')
|
||||
}
|
||||
>
|
||||
<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">
|
||||
<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">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => dismiss('../../')}
|
||||
onClick={dismiss}
|
||||
icon={{
|
||||
icon: faXmark,
|
||||
bgClassName: 'bg-destroy-80',
|
||||
@ -59,7 +89,7 @@ export default function Units() {
|
||||
onClick={next}
|
||||
icon={{ icon: faArrowRight }}
|
||||
>
|
||||
Next: Sketching
|
||||
Next: Streaming
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
|
85
src/routes/Onboarding/CmdK.tsx
Normal file
85
src/routes/Onboarding/CmdK.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
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>
|
||||
)
|
||||
}
|
85
src/routes/Onboarding/CodeEditor.tsx
Normal file
85
src/routes/Onboarding/CodeEditor.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
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 language—called <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>
|
||||
)
|
||||
}
|
65
src/routes/Onboarding/Export.tsx
Normal file
65
src/routes/Onboarding/Export.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
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>
|
||||
)
|
||||
}
|
59
src/routes/Onboarding/FutureWork.tsx
Normal file
59
src/routes/Onboarding/FutureWork.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
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>
|
||||
)
|
||||
}
|
125
src/routes/Onboarding/InteractiveNumbers.tsx
Normal file
125
src/routes/Onboarding/InteractiveNumbers.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
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>
|
||||
)
|
||||
}
|
@ -1,29 +1,183 @@
|
||||
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'
|
||||
|
||||
export default function Introduction() {
|
||||
function OnboardingWithNewFile() {
|
||||
const navigate = useNavigate()
|
||||
const dismiss = useDismiss()
|
||||
const next = useNextClick(onboardingPaths.UNITS)
|
||||
const next = useNextClick(onboardingPaths.INDEX)
|
||||
const { deferredSetCode } = useStore((s) => ({
|
||||
deferredSetCode: s.deferredSetCode,
|
||||
}))
|
||||
const {
|
||||
settings: {
|
||||
context: { defaultDirectory, defaultProjectName },
|
||||
},
|
||||
} = useGlobalStateContext()
|
||||
|
||||
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">
|
||||
<h1 className="text-2xl font-bold">
|
||||
Welcome to the KittyCAD Modeling App
|
||||
{!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>
|
||||
<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>
|
||||
<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>
|
||||
<div className="flex justify-between mt-6">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => dismiss('../')}
|
||||
onClick={dismiss}
|
||||
icon={{
|
||||
icon: faXmark,
|
||||
bgClassName: 'bg-destroy-80',
|
||||
@ -44,5 +198,7 @@ export default function Introduction() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<OnboardingWithNewFile />
|
||||
)
|
||||
}
|
||||
|
85
src/routes/Onboarding/ParametricModeling.tsx
Normal file
85
src/routes/Onboarding/ParametricModeling.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
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>
|
||||
)
|
||||
}
|
56
src/routes/Onboarding/ProjectMenu.tsx
Normal file
56
src/routes/Onboarding/ProjectMenu.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
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 model—which 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>
|
||||
)
|
||||
}
|
@ -1,21 +1,44 @@
|
||||
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||
import { ActionButton } from '../../components/ActionButton'
|
||||
import { useDismiss } from '.'
|
||||
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||
import { useStore } from 'useStore'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
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 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">
|
||||
<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')
|
||||
}
|
||||
>
|
||||
<h1 className="text-2xl font-bold">Sketching</h1>
|
||||
<p className="mt-6">
|
||||
We still have to implement this step, and the rest of the tutorial!
|
||||
<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>
|
||||
<div className="flex justify-between mt-6">
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => dismiss('../../')}
|
||||
onClick={dismiss}
|
||||
icon={{
|
||||
icon: faXmark,
|
||||
bgClassName: 'bg-destroy-80',
|
||||
@ -28,10 +51,10 @@ export default function Sketching() {
|
||||
</ActionButton>
|
||||
<ActionButton
|
||||
Element="button"
|
||||
onClick={() => dismiss('../../')}
|
||||
onClick={next}
|
||||
icon={{ icon: faArrowRight }}
|
||||
>
|
||||
Finish
|
||||
Next: Future Work
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
|
66
src/routes/Onboarding/Streaming.tsx
Normal file
66
src/routes/Onboarding/Streaming.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
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>
|
||||
)
|
||||
}
|
@ -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',
|
||||
|
53
src/routes/Onboarding/UserMenu.tsx
Normal file
53
src/routes/Onboarding/UserMenu.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
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>
|
||||
)
|
||||
}
|
@ -1,18 +1,36 @@
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { Outlet, useNavigate } from 'react-router-dom'
|
||||
import { Outlet, useRouteLoaderData, 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 = [
|
||||
@ -20,18 +38,51 @@ 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) {
|
||||
@ -39,37 +90,44 @@ 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((newStatus !== onboardingPaths.UNITS ? '..' : '.') + newStatus)
|
||||
}, [newStatus, send, navigate])
|
||||
navigate(
|
||||
paths.FILE +
|
||||
'/' +
|
||||
encodeURIComponent(project?.path || 'new') +
|
||||
paths.ONBOARDING.INDEX.slice(0, -1) +
|
||||
newStatus
|
||||
)
|
||||
}, [project, newStatus, send, navigate])
|
||||
}
|
||||
|
||||
export function useDismiss() {
|
||||
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
||||
const {
|
||||
settings: { send },
|
||||
} = useGlobalStateContext()
|
||||
const navigate = useNavigate()
|
||||
|
||||
return useCallback(
|
||||
(path: string) => {
|
||||
send({
|
||||
type: 'Set Onboarding Status',
|
||||
data: { onboardingStatus: 'dismissed' },
|
||||
})
|
||||
navigate(path)
|
||||
},
|
||||
[send, navigate]
|
||||
)
|
||||
return useCallback(() => {
|
||||
send({
|
||||
type: 'Set Onboarding Status',
|
||||
data: { onboardingStatus: 'dismissed' },
|
||||
})
|
||||
navigate(
|
||||
paths.FILE + '/' + encodeURIComponent(routeData?.project?.path || 'new')
|
||||
)
|
||||
}, [send, navigate, routeData])
|
||||
}
|
||||
|
||||
const Onboarding = () => {
|
||||
const dismiss = useDismiss()
|
||||
useHotkeys('esc', () => dismiss('../'))
|
||||
useHotkeys('esc', dismiss)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -23,12 +23,14 @@ 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()
|
||||
useHotkeys('esc', () => navigate('../'))
|
||||
const dotDotSlash = useDotDotSlash()
|
||||
useHotkeys('esc', () => navigate(dotDotSlash()))
|
||||
const {
|
||||
settings: {
|
||||
send,
|
||||
@ -62,11 +64,11 @@ export const Settings = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="body-bg fixed inset-0 z-40 overflow-auto">
|
||||
<div className="fixed inset-0 z-40 overflow-auto body-bg">
|
||||
<AppHeader showToolbar={false} project={loaderData?.project}>
|
||||
<ActionButton
|
||||
Element="link"
|
||||
to={'../'}
|
||||
to={location.pathname.replace(paths.SETTINGS, '')}
|
||||
icon={{
|
||||
icon: faXmark,
|
||||
bgClassName: 'bg-destroy-80',
|
||||
@ -78,9 +80,9 @@ export const Settings = () => {
|
||||
Close
|
||||
</ActionButton>
|
||||
</AppHeader>
|
||||
<div className="my-24 max-w-5xl mx-auto">
|
||||
<div className="max-w-5xl mx-auto my-24">
|
||||
<h1 className="text-4xl font-bold">User Settings</h1>
|
||||
<p className="mt-6 max-w-2xl">
|
||||
<p className="max-w-2xl mt-6">
|
||||
Don't see the feature you want? Check to see if it's on{' '}
|
||||
<a
|
||||
href="https://github.com/KittyCAD/modeling-app/discussions"
|
||||
@ -98,7 +100,7 @@ export const Settings = () => {
|
||||
>
|
||||
<select
|
||||
id="camera-controls"
|
||||
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
|
||||
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
||||
value={cameraControls}
|
||||
onChange={(e) => {
|
||||
send({
|
||||
@ -113,7 +115,7 @@ export const Settings = () => {
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<ul className="text-sm my-2 mx-4 leading-relaxed">
|
||||
<ul className="mx-4 my-2 text-sm leading-relaxed">
|
||||
<li>
|
||||
<strong>Pan:</strong>{' '}
|
||||
{cameraMouseDragGuards[cameraControls].pan.description}
|
||||
@ -134,7 +136,7 @@ export const Settings = () => {
|
||||
title="Default Directory"
|
||||
description="Where newly-created projects are saved on your local computer"
|
||||
>
|
||||
<div className="w-full flex gap-4 p-1 rounded border border-chalkboard-30">
|
||||
<div className="flex w-full gap-4 p-1 border rounded border-chalkboard-30">
|
||||
<input
|
||||
className="flex-1 px-2 bg-transparent"
|
||||
value={defaultDirectory}
|
||||
@ -161,7 +163,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 border border-chalkboard-30 bg-transparent"
|
||||
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
||||
defaultValue={defaultProjectName}
|
||||
onBlur={(e) => {
|
||||
const newValue = e.target.value.trim() || DEFAULT_PROJECT_NAME
|
||||
@ -205,7 +207,7 @@ export const Settings = () => {
|
||||
>
|
||||
<select
|
||||
id="base-unit"
|
||||
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
|
||||
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
||||
value={baseUnit}
|
||||
onChange={(e) => {
|
||||
send({
|
||||
@ -239,7 +241,7 @@ export const Settings = () => {
|
||||
>
|
||||
<select
|
||||
id="settings-theme"
|
||||
className="block w-full px-3 py-1 border border-chalkboard-30 bg-transparent"
|
||||
className="block w-full px-3 py-1 bg-transparent border border-chalkboard-30"
|
||||
value={theme}
|
||||
onChange={(e) => {
|
||||
send({
|
||||
@ -267,7 +269,7 @@ export const Settings = () => {
|
||||
type: 'Set Onboarding Status',
|
||||
data: { onboardingStatus: '' },
|
||||
})
|
||||
navigate('..' + paths.ONBOARDING.INDEX)
|
||||
navigate(dotDotSlash(1) + paths.ONBOARDING.INDEX)
|
||||
}}
|
||||
icon={{ icon: faArrowRotateBack }}
|
||||
>
|
||||
@ -283,15 +285,22 @@ 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">
|
||||
<section
|
||||
className={
|
||||
'my-16 last-of-type:mb-24 grid grid-cols-2 gap-12 items-start ' +
|
||||
className
|
||||
}
|
||||
>
|
||||
<div className="w-80">
|
||||
<h2 className="text-2xl">{title}</h2>
|
||||
<p className="mt-2 text-sm">{description}</p>
|
||||
|
@ -18,6 +18,7 @@ import { 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'
|
||||
|
||||
export type Selection = {
|
||||
type: 'default' | 'line-end' | 'line-mid'
|
||||
@ -70,7 +71,7 @@ export type GuiModes =
|
||||
waitingFirstClick: boolean
|
||||
rotation: Rotation
|
||||
position: Position
|
||||
id?: string
|
||||
pathId: string
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
| {
|
||||
@ -79,6 +80,15 @@ 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'
|
||||
@ -312,7 +322,7 @@ export const useStore = create<StoreState>()(
|
||||
end: 0,
|
||||
body: [],
|
||||
nonCodeMeta: {
|
||||
noneCodeNodes: {},
|
||||
nonCodeNodes: {},
|
||||
start: null,
|
||||
},
|
||||
},
|
||||
@ -409,7 +419,7 @@ export const useStore = create<StoreState>()(
|
||||
}, 100) as unknown as number
|
||||
)
|
||||
},
|
||||
code: '',
|
||||
code: bracket,
|
||||
setCode: (code) => set({ code }),
|
||||
deferredSetCode: (code) => {
|
||||
set({ code })
|
||||
@ -544,7 +554,7 @@ async function executeCode({
|
||||
end: 0,
|
||||
body: [],
|
||||
nonCodeMeta: {
|
||||
noneCodeNodes: {},
|
||||
nonCodeNodes: {},
|
||||
start: null,
|
||||
},
|
||||
},
|
||||
|
22
src/wasm-lib/.config/nextest.toml
Normal file
22
src/wasm-lib/.config/nextest.toml
Normal file
@ -0,0 +1,22 @@
|
||||
# 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
|
392
src/wasm-lib/Cargo.lock
generated
392
src/wasm-lib/Cargo.lock
generated
@ -41,9 +41,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.0.4"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
|
||||
checksum = "0f2135563fb5c609d2b2b87c1e8ce7bc41b0b45430fa9661f457981503dd5bf0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@ -63,6 +63,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anes"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.5.0"
|
||||
@ -79,9 +85,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
|
||||
checksum = "b84bf0a05bbb2a83e5eb6fa36bb6e87baa08193c35ff52bbf6b38d8af2890e46"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
@ -150,7 +156,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -205,9 +211,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.21.2"
|
||||
version = "0.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d"
|
||||
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
|
||||
|
||||
[[package]]
|
||||
name = "bigdecimal"
|
||||
@ -312,9 +318,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.13.0"
|
||||
version = "3.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
|
||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
@ -330,13 +336,19 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.4.0"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
|
||||
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cast"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.83"
|
||||
@ -363,9 +375,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.30"
|
||||
version = "0.4.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877"
|
||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@ -374,6 +386,33 @@ dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"ciborium-ll",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-io"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656"
|
||||
|
||||
[[package]]
|
||||
name = "ciborium-ll"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b"
|
||||
dependencies = [
|
||||
"ciborium-io",
|
||||
"half 1.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.6.1"
|
||||
@ -387,9 +426,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.4.3"
|
||||
version = "4.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84ed82781cea27b43c9b106a979fe450a13a31aab0500595fb3fc06616de08e6"
|
||||
checksum = "b1d7b8d5ec32af0fadc644bf1fd509a688c2103b185644bb1e29d164e0703136"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@ -397,9 +436,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.4.2"
|
||||
version = "4.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bb9faaa7c2ef94b2743a21f5a29e6f0010dff4caa69ac8e9d6cf8b6fa74da08"
|
||||
checksum = "5179bb514e4d7c2051749d8fcefa2ed6d06a9f4e6d69faf3805f5d80b8cf8d56"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@ -419,7 +458,7 @@ dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -507,6 +546,42 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
|
||||
dependencies = [
|
||||
"anes",
|
||||
"cast",
|
||||
"ciborium",
|
||||
"clap",
|
||||
"criterion-plot",
|
||||
"is-terminal",
|
||||
"itertools 0.10.5",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
"rayon",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"tinytemplate",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "criterion-plot"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
|
||||
dependencies = [
|
||||
"cast",
|
||||
"itertools 0.10.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
@ -593,7 +668,7 @@ checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
|
||||
|
||||
[[package]]
|
||||
name = "derive-docs"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"expectorate",
|
||||
@ -603,21 +678,7 @@ dependencies = [
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_tokenstream",
|
||||
"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",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -692,9 +753,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
|
||||
checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
@ -724,13 +785,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "exr"
|
||||
version = "1.7.0"
|
||||
version = "1.71.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18"
|
||||
checksum = "832a761f35ab3e6664babfbdc6cef35a4860e816ec3916dcfd0882954e98a8a8"
|
||||
dependencies = [
|
||||
"bit_field",
|
||||
"flume",
|
||||
"half",
|
||||
"half 2.2.1",
|
||||
"lebe",
|
||||
"miniz_oxide",
|
||||
"rayon-core",
|
||||
@ -790,14 +851,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.10.14"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
|
||||
checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"nanorand",
|
||||
"pin-project",
|
||||
"spin 0.9.8",
|
||||
]
|
||||
|
||||
@ -891,7 +948,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1001,6 +1058,12 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.2.1"
|
||||
@ -1227,17 +1290,6 @@ dependencies = [
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.2",
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.8.0"
|
||||
@ -1251,7 +1303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.2",
|
||||
"rustix 0.38.9",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -1310,13 +1362,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.1.26"
|
||||
version = "0.1.30"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bson",
|
||||
"clap",
|
||||
"criterion",
|
||||
"dashmap",
|
||||
"derive-docs 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"derive-docs",
|
||||
"expectorate",
|
||||
"futures",
|
||||
"itertools 0.11.0",
|
||||
@ -1338,6 +1392,7 @@ dependencies = [
|
||||
"uuid",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1348,7 +1403,7 @@ checksum = "d9cf962b1e81a0b4eb923a727e761b40672cbacc7f5f0b75e13579d346352bc7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.4",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"data-encoding",
|
||||
@ -1396,9 +1451,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.147"
|
||||
version = "0.2.148"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
|
||||
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
@ -1418,15 +1473,9 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.8"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
|
||||
checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
@ -1477,9 +1526,9 @@ checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
version = "2.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
@ -1533,15 +1582,6 @@ dependencies = [
|
||||
"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"
|
||||
@ -1629,9 +1669,9 @@ checksum = "049950a25a8f69e9673ed52fc58749548cee71194f6c3a8a04b80863637ce722"
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.0"
|
||||
version = "0.32.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe"
|
||||
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
@ -1648,10 +1688,16 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d11de466f4a3006fe8a5e7ec84e93b79c70cb992ae0aa0eb631ad2df8abfe2"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
version = "11.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||
|
||||
[[package]]
|
||||
name = "openapitor"
|
||||
version = "0.0.9"
|
||||
source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#3d74c1dfb41146a268a644e9fde2c19a8cd66895"
|
||||
source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#0d121f6881da91b4a30bee18bbfe50e4a2096073"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"anyhow",
|
||||
@ -1795,9 +1841,9 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"regex-syntax 0.7.4",
|
||||
"regex-syntax 0.7.5",
|
||||
"structmeta",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1814,10 +1860,11 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.7.2"
|
||||
version = "2.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1acb4a4365a13f749a93f1a094a7805e5cfa0955373a9de860d962eaa3a5fe5a"
|
||||
checksum = "d7a4d085fd991ac8d5b05a147b437791b4260b76326baf0fc60cf7c9c27ecd33"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
@ -1859,7 +1906,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1880,6 +1927,34 @@ version = "0.3.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||
|
||||
[[package]]
|
||||
name = "plotters"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "plotters-backend"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609"
|
||||
|
||||
[[package]]
|
||||
name = "plotters-svg"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab"
|
||||
dependencies = [
|
||||
"plotters-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.10"
|
||||
@ -2058,25 +2133,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.9.3"
|
||||
version = "1.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
|
||||
checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax 0.7.4",
|
||||
"regex-syntax 0.7.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.3.6"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
|
||||
checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax 0.7.4",
|
||||
"regex-syntax 0.7.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2099,9 +2174,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.4"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
@ -2109,7 +2184,7 @@ version = "0.11.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.4",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
@ -2264,36 +2339,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.23"
|
||||
version = "0.38.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys 0.3.8",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9bfe0f2582b4931a45d1fa608f8a8722e8b3c7ac54dd6d5f3b3212791fedef49"
|
||||
checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.5",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.6"
|
||||
version = "0.21.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb"
|
||||
checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
@ -2319,14 +2380,14 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2"
|
||||
dependencies = [
|
||||
"base64 0.21.2",
|
||||
"base64 0.21.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.101.4"
|
||||
version = "0.101.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d"
|
||||
checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
@ -2364,9 +2425,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.13"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "763f8cd0d4c71ed8389c90cb8100cba87e763bd01a8e614d4f0af97bcd50a161"
|
||||
checksum = "1f7b0ce13155372a76ee2e1c5ffba1fe61ede73fbea5630d61eee6fac4929c0c"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"bytes",
|
||||
@ -2381,9 +2442,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.13"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0f696e21e10fa546b7ffb1c9672c6de8fbc7a81acf59524386d8639bf12737"
|
||||
checksum = "e85e2a16b12bdb763244c69ab79363d71db2b4b918a2def53f80b02e0574b13c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2474,7 +2535,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2490,9 +2551,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.106"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2"
|
||||
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||
dependencies = [
|
||||
"indexmap 2.0.0",
|
||||
"itoa",
|
||||
@ -2508,7 +2569,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2520,7 +2581,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2699,9 +2760,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.3"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877"
|
||||
checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.48.0",
|
||||
@ -2737,7 +2798,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"structmeta-derive",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2748,7 +2809,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2764,9 +2825,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.33"
|
||||
version = "2.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9caece70c63bfba29ec2fed841a09851b14a235c60010fa4de58089b6c025668"
|
||||
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2803,7 +2864,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall 0.3.5",
|
||||
"rustix 0.38.9",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -2829,11 +2890,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.2.6"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e6bf6f19e9f8ed8d4048dc22981458ebcf406d67e94cd422e5ecd73d63b3237"
|
||||
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7"
|
||||
dependencies = [
|
||||
"rustix 0.37.23",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@ -2854,7 +2915,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2880,9 +2941,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.27"
|
||||
version = "0.3.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bb39ee79a6d8de55f48f2293a830e040392f1c5f16e336bdd1788cd0aadce07"
|
||||
checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@ -2901,13 +2962,23 @@ checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "733d258752e9303d392b94b75230d07b0b9c489350c69b851fc6c065fde3e8f9"
|
||||
checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572"
|
||||
dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinytemplate"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
@ -2937,7 +3008,7 @@ dependencies = [
|
||||
"parking_lot 0.12.1",
|
||||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2 0.5.3",
|
||||
"socket2 0.5.4",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
@ -2950,7 +3021,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3066,7 +3137,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3095,7 +3166,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3181,7 +3252,7 @@ dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
@ -3220,9 +3291,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.16.0"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
@ -3247,9 +3318,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.11"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
@ -3286,9 +3357,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.4.0"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb"
|
||||
checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
@ -3339,9 +3410,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.3"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
|
||||
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi-util",
|
||||
@ -3383,7 +3454,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -3418,7 +3489,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.33",
|
||||
"syn 2.0.37",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -3441,6 +3512,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"kcl-lib",
|
||||
"kittycad",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
|
@ -12,7 +12,8 @@ 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"] }
|
||||
serde_json = "1.0.106"
|
||||
serde_json = "1.0.107"
|
||||
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
||||
wasm-bindgen = "0.2.87"
|
||||
wasm-bindgen-futures = "0.4.37"
|
||||
|
||||
@ -20,6 +21,7 @@ wasm-bindgen-futures = "0.4.37"
|
||||
anyhow = "1"
|
||||
image = "0.24.7"
|
||||
kittycad = "0.2.25"
|
||||
pretty_assertions = "1.4.0"
|
||||
reqwest = { version = "0.11.20", default-features = false }
|
||||
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
twenty-twenty = "0.6.1"
|
||||
@ -50,3 +52,11 @@ members = [
|
||||
"derive-docs",
|
||||
"kcl",
|
||||
]
|
||||
|
||||
[[test]]
|
||||
name = "executor"
|
||||
path = "tests/executor/main.rs"
|
||||
|
||||
[[test]]
|
||||
name = "modify"
|
||||
path = "tests/modify/main.rs"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "derive-docs"
|
||||
description = "A tool for generating documentation from Rust derive macros"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
@ -16,7 +16,7 @@ proc-macro2 = "1"
|
||||
quote = "1"
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
serde_tokenstream = "0.2"
|
||||
syn = { version = "2.0.33", features = ["full"] }
|
||||
syn = { version = "2.0.37", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
expectorate = "1.0.7"
|
||||
|
@ -212,6 +212,12 @@ 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! {
|
||||
@ -250,7 +256,15 @@ fn do_stdlib_inner(
|
||||
.replace("Result < ", "")
|
||||
.replace(", KclError >", "");
|
||||
let return_type = if !ret_ty_string.is_empty() {
|
||||
let ret_ty_string = ret_ty_string.trim().to_string();
|
||||
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_ident = format_ident!("{}", ret_ty_string);
|
||||
let ret_ty_string = clean_type(&ret_ty_string);
|
||||
quote! {
|
||||
@ -475,6 +489,9 @@ 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();
|
||||
@ -564,4 +581,26 @@ 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());
|
||||
}
|
||||
}
|
||||
|
70
src/wasm-lib/derive-docs/tests/box.gen
Normal file
70
src/wasm-lib/derive-docs/tests/box.gen
Normal file
@ -0,0 +1,70 @@
|
||||
#[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
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-lib"
|
||||
description = "KittyCAD Language"
|
||||
version = "0.1.26"
|
||||
version = "0.1.30"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
@ -9,17 +9,18 @@ license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
anyhow = { version = "1.0.75", features = ["backtrace"] }
|
||||
clap = { version = "4.4.3", features = ["cargo", "derive", "env", "unicode"] }
|
||||
async-trait = "0.1.73"
|
||||
clap = { version = "4.4.3", features = ["cargo", "derive", "env", "unicode"], optional = true }
|
||||
dashmap = "5.5.3"
|
||||
derive-docs = { version = "0.1.3" }
|
||||
#derive-docs = { path = "../derive-docs" }
|
||||
#derive-docs = { version = "0.1.4" }
|
||||
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.106"
|
||||
serde = { version = "1.0.188", features = ["derive"] }
|
||||
serde_json = "1.0.107"
|
||||
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"] }
|
||||
@ -29,6 +30,7 @@ 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"] }
|
||||
@ -40,6 +42,7 @@ tower-lsp = { version = "0.20.0", features = ["proposed"] }
|
||||
|
||||
[features]
|
||||
default = ["engine"]
|
||||
cli = ["dep:clap"]
|
||||
engine = []
|
||||
|
||||
[profile.release]
|
||||
@ -47,7 +50,12 @@ panic = "abort"
|
||||
debug = true
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5.1"
|
||||
expectorate = "1.0.7"
|
||||
itertools = "0.11.0"
|
||||
pretty_assertions = "1.4.0"
|
||||
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
|
||||
[[bench]]
|
||||
name = "compiler_benchmark"
|
||||
harness = false
|
||||
|
27
src/wasm-lib/kcl/benches/compiler_benchmark.rs
Normal file
27
src/wasm-lib/kcl/benches/compiler_benchmark.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("parse + lex cube program", |b| b.iter(lex_and_parse_cube));
|
||||
}
|
||||
|
||||
fn lex_and_parse_cube() {
|
||||
let program = r#"
|
||||
fn cube = (pos, scale) => {
|
||||
const sg = startSketchAt(pos)
|
||||
|> line([0, scale], %)
|
||||
|> line([scale, 0], %)
|
||||
|> line([0, -scale], %)
|
||||
|
||||
return sg
|
||||
}
|
||||
|
||||
const b1 = cube([0,0], 10)
|
||||
const pt1 = b1[0]
|
||||
show(b1)"#;
|
||||
let tokens = kcl_lib::tokeniser::lexer(program);
|
||||
let parser = kcl_lib::parser::Parser::new(tokens);
|
||||
parser.ast().unwrap();
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
2
src/wasm-lib/kcl/src/ast/mod.rs
Normal file
2
src/wasm-lib/kcl/src/ast/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod modify;
|
||||
pub mod types;
|
294
src/wasm-lib/kcl/src/ast/modify.rs
Normal file
294
src/wasm-lib/kcl/src/ast/modify.rs
Normal file
@ -0,0 +1,294 @@
|
||||
use kittycad::types::{ModelingCmd, Point3D};
|
||||
|
||||
use super::types::ConstraintLevel;
|
||||
use crate::{
|
||||
ast::types::{
|
||||
ArrayExpression, CallExpression, FormatOptions, Literal, PipeExpression, PipeSubstitution, Program,
|
||||
VariableDeclarator,
|
||||
},
|
||||
engine::{EngineConnection, EngineManager},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{Point2d, SourceRange},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
/// The control point data for a curve or line.
|
||||
pub struct ControlPointData {
|
||||
/// The control points for the curve or line.
|
||||
pub points: Vec<kittycad::types::Point3D>,
|
||||
/// The command that created this curve or line.
|
||||
pub command: kittycad::types::PathCommand,
|
||||
/// The id of the curve or line.
|
||||
pub id: uuid::Uuid,
|
||||
}
|
||||
|
||||
const EPSILON: f64 = 0.015625; // or 2^-6
|
||||
|
||||
/// Update the AST to reflect the new state of the program after something like
|
||||
/// a move or a new line.
|
||||
pub async fn modify_ast_for_sketch(
|
||||
engine: &mut EngineConnection,
|
||||
program: &mut Program,
|
||||
// The name of the sketch.
|
||||
sketch_name: &str,
|
||||
// The ID of the parent sketch.
|
||||
sketch_id: uuid::Uuid,
|
||||
) -> Result<String, KclError> {
|
||||
// First we need to check if this sketch is constrained (even partially).
|
||||
// If it is, we cannot modify it.
|
||||
|
||||
// Get the information about the sketch.
|
||||
if let Some(ast_sketch) = program.get_variable(sketch_name) {
|
||||
let constraint_level = ast_sketch.get_constraint_level();
|
||||
match &constraint_level {
|
||||
ConstraintLevel::None { source_ranges: _ } => {}
|
||||
ConstraintLevel::Ignore { source_ranges: _ } => {}
|
||||
ConstraintLevel::Partial {
|
||||
source_ranges: _,
|
||||
levels,
|
||||
} => {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!(
|
||||
"Sketch {} is constrained `{}` and cannot be modified",
|
||||
sketch_name, constraint_level
|
||||
),
|
||||
source_ranges: levels.get_all_partial_or_full_source_ranges(),
|
||||
}));
|
||||
}
|
||||
ConstraintLevel::Full { source_ranges } => {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!(
|
||||
"Sketch {} is constrained `{}` and cannot be modified",
|
||||
sketch_name, constraint_level
|
||||
),
|
||||
source_ranges: source_ranges.clone(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Let's start by getting the path info.
|
||||
|
||||
// Let's get the path info.
|
||||
let resp = engine
|
||||
.send_modeling_cmd_get_response(
|
||||
uuid::Uuid::new_v4(),
|
||||
SourceRange::default(),
|
||||
ModelingCmd::PathGetInfo { path_id: sketch_id },
|
||||
)
|
||||
.await?;
|
||||
|
||||
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||
modeling_response: kittycad::types::OkModelingCmdResponse::PathGetInfo { data: path_info },
|
||||
} = &resp
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Get path info response was not as expected: {:?}", resp),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
};
|
||||
|
||||
/* // Let's try to get the children of the sketch.
|
||||
let resp = engine
|
||||
.send_modeling_cmd_get_response(
|
||||
uuid::Uuid::new_v4(),
|
||||
SourceRange::default(),
|
||||
ModelingCmd::EntityGetAllChildUuids { entity_id: sketch_id },
|
||||
)
|
||||
.await?;
|
||||
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||
modeling_response: kittycad::types::OkModelingCmdResponse::EntityGetAllChildUuids { data: children_info },
|
||||
} = &resp
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Get child info response was not as expected: {:?}", resp),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
};
|
||||
|
||||
println!("children_info: {:#?}", children_info);
|
||||
|
||||
// Let's try to get the parent id.
|
||||
let resp = engine
|
||||
.send_modeling_cmd_get_response(
|
||||
uuid::Uuid::new_v4(),
|
||||
SourceRange::default(),
|
||||
ModelingCmd::EntityGetParentId { entity_id: sketch_id },
|
||||
)
|
||||
.await?;
|
||||
|
||||
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||
modeling_response: kittycad::types::OkModelingCmdResponse::EntityGetParentId { data: parent_info },
|
||||
} = &resp
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Get parent id response was not as expected: {:?}", resp),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
};
|
||||
|
||||
println!("parent_info: {:#?}", parent_info);*/
|
||||
|
||||
// Now let's get the control points for all the segments.
|
||||
// TODO: We should probably await all these at once so we aren't going one by one.
|
||||
// But I guess this is fine for now.
|
||||
// We absolutely have to preserve the order of the control points.
|
||||
let mut control_points = Vec::new();
|
||||
for segment in &path_info.segments {
|
||||
if let Some(command_id) = &segment.command_id {
|
||||
let h = engine.send_modeling_cmd_get_response(
|
||||
uuid::Uuid::new_v4(),
|
||||
SourceRange::default(),
|
||||
ModelingCmd::CurveGetControlPoints { curve_id: *command_id },
|
||||
);
|
||||
|
||||
let kittycad::types::OkWebSocketResponseData::Modeling {
|
||||
modeling_response: kittycad::types::OkModelingCmdResponse::CurveGetControlPoints { data },
|
||||
} = h.await?
|
||||
else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Curve get control points response was not as expected: {:?}", resp),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
};
|
||||
|
||||
control_points.push(ControlPointData {
|
||||
points: data.control_points.clone(),
|
||||
command: segment.command.clone(),
|
||||
id: *command_id,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if control_points.is_empty() {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("No control points found for sketch {}", sketch_name),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
}));
|
||||
}
|
||||
|
||||
let first_control_points = control_points.first().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("No control points found for sketch {}", sketch_name),
|
||||
source_ranges: vec![SourceRange::default()],
|
||||
})
|
||||
})?;
|
||||
|
||||
let mut additional_lines = Vec::new();
|
||||
let mut last_point = first_control_points.points[1].clone();
|
||||
for control_point in control_points[1..].iter() {
|
||||
additional_lines.push([
|
||||
(control_point.points[1].x - last_point.x),
|
||||
(control_point.points[1].y - last_point.y),
|
||||
]);
|
||||
last_point = Point3D {
|
||||
x: control_point.points[1].x,
|
||||
y: control_point.points[1].y,
|
||||
z: control_point.points[1].z,
|
||||
};
|
||||
}
|
||||
|
||||
// Okay now let's recalculate the sketch from the control points.
|
||||
let start_sketch_at_end = Point3D {
|
||||
x: (first_control_points.points[1].x - first_control_points.points[0].x),
|
||||
y: (first_control_points.points[1].y - first_control_points.points[0].y),
|
||||
z: (first_control_points.points[1].z - first_control_points.points[0].z),
|
||||
};
|
||||
let sketch = create_start_sketch_at(
|
||||
sketch_name,
|
||||
[first_control_points.points[0].x, first_control_points.points[0].y],
|
||||
[start_sketch_at_end.x, start_sketch_at_end.y],
|
||||
additional_lines,
|
||||
)?;
|
||||
|
||||
// Add the sketch back to the program.
|
||||
program.replace_variable(sketch_name, sketch);
|
||||
|
||||
let recasted = program.recast(&FormatOptions::default(), 0);
|
||||
|
||||
// Re-parse the ast so we get the correct source ranges.
|
||||
let tokens = crate::tokeniser::lexer(&recasted);
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
*program = parser.ast()?;
|
||||
|
||||
Ok(recasted)
|
||||
}
|
||||
|
||||
/// Create a pipe expression that starts a sketch at the given point and draws a line to the given point.
|
||||
fn create_start_sketch_at(
|
||||
name: &str,
|
||||
start: [f64; 2],
|
||||
end: [f64; 2],
|
||||
additional_lines: Vec<[f64; 2]>,
|
||||
) -> Result<VariableDeclarator, KclError> {
|
||||
let start_sketch_at = CallExpression::new(
|
||||
"startSketchAt",
|
||||
vec![ArrayExpression::new(vec![
|
||||
Literal::new(round_before_recast(start[0]).into()).into(),
|
||||
Literal::new(round_before_recast(start[1]).into()).into(),
|
||||
])
|
||||
.into()],
|
||||
)?;
|
||||
|
||||
// Keep track of where we are so we can close the sketch if we need to.
|
||||
let mut current_position = Point2d {
|
||||
x: start[0],
|
||||
y: start[1],
|
||||
};
|
||||
current_position.x += end[0];
|
||||
current_position.y += end[1];
|
||||
|
||||
let initial_line = CallExpression::new(
|
||||
"line",
|
||||
vec![
|
||||
ArrayExpression::new(vec![
|
||||
Literal::new(round_before_recast(end[0]).into()).into(),
|
||||
Literal::new(round_before_recast(end[1]).into()).into(),
|
||||
])
|
||||
.into(),
|
||||
PipeSubstitution::new().into(),
|
||||
],
|
||||
)?;
|
||||
|
||||
let mut pipe_body = vec![start_sketch_at.into(), initial_line.into()];
|
||||
|
||||
for (index, line) in additional_lines.iter().enumerate() {
|
||||
current_position.x += line[0];
|
||||
current_position.y += line[1];
|
||||
|
||||
// If we are on the last line, check if we have to close the sketch.
|
||||
if index == additional_lines.len() - 1 {
|
||||
let diff_x = (current_position.x - start[0]).abs();
|
||||
let diff_y = (current_position.y - start[1]).abs();
|
||||
// Compare the end of the last line to the start of the first line.
|
||||
// This is a bit more lenient if you look at the value of epsilon.
|
||||
if diff_x <= EPSILON && diff_y <= EPSILON {
|
||||
// We have to close the sketch.
|
||||
let close = CallExpression::new("close", vec![PipeSubstitution::new().into()])?;
|
||||
pipe_body.push(close.into());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: we should check if we should close the sketch.
|
||||
let line = CallExpression::new(
|
||||
"line",
|
||||
vec![
|
||||
ArrayExpression::new(vec![
|
||||
Literal::new(round_before_recast(line[0]).into()).into(),
|
||||
Literal::new(round_before_recast(line[1]).into()).into(),
|
||||
])
|
||||
.into(),
|
||||
PipeSubstitution::new().into(),
|
||||
],
|
||||
)?;
|
||||
pipe_body.push(line.into());
|
||||
}
|
||||
|
||||
Ok(VariableDeclarator::new(name, PipeExpression::new(pipe_body).into()))
|
||||
}
|
||||
|
||||
fn round_before_recast(num: f64) -> f64 {
|
||||
// We use 2 decimal places.
|
||||
(num * 100.0).round() / 100.0
|
||||
}
|
@ -23,7 +23,7 @@ pub struct Program {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub body: Vec<BodyItem>,
|
||||
pub non_code_meta: NoneCodeMeta,
|
||||
pub non_code_meta: NonCodeMeta,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
@ -81,7 +81,7 @@ impl Program {
|
||||
"\n".to_string()
|
||||
};
|
||||
|
||||
let custom_white_space_or_comment = match self.non_code_meta.none_code_nodes.get(&index) {
|
||||
let custom_white_space_or_comment = match self.non_code_meta.non_code_nodes.get(&index) {
|
||||
Some(custom_white_space_or_comment) => custom_white_space_or_comment.format(&indentation),
|
||||
None => String::new(),
|
||||
};
|
||||
@ -237,6 +237,47 @@ 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 {
|
||||
@ -247,7 +288,7 @@ pub trait ValueMeta {
|
||||
|
||||
macro_rules! impl_value_meta {
|
||||
{$name:ident} => {
|
||||
impl crate::abstract_syntax_tree_types::ValueMeta for $name {
|
||||
impl crate::ast::types::ValueMeta for $name {
|
||||
fn start(&self) -> usize {
|
||||
self.start
|
||||
}
|
||||
@ -426,6 +467,26 @@ 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 {
|
||||
@ -465,6 +526,18 @@ 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(),
|
||||
@ -521,11 +594,7 @@ impl BinaryPart {
|
||||
}
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.execute(memory, &mut new_pipe_info, engine),
|
||||
BinaryPart::UnaryExpression(unary_expression) => {
|
||||
// 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()],
|
||||
}))
|
||||
unary_expression.get_result(memory, &mut new_pipe_info, engine)
|
||||
}
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(memory),
|
||||
}
|
||||
@ -571,26 +640,26 @@ impl BinaryPart {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub struct NoneCodeNode {
|
||||
pub struct NonCodeNode {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub value: NoneCodeValue,
|
||||
pub value: NonCodeValue,
|
||||
}
|
||||
|
||||
impl NoneCodeNode {
|
||||
impl NonCodeNode {
|
||||
pub fn value(&self) -> String {
|
||||
match &self.value {
|
||||
NoneCodeValue::InlineComment { value } => value.clone(),
|
||||
NoneCodeValue::BlockComment { value } => value.clone(),
|
||||
NoneCodeValue::NewLineBlockComment { value } => value.clone(),
|
||||
NoneCodeValue::NewLine => "\n\n".to_string(),
|
||||
NonCodeValue::InlineComment { value } => value.clone(),
|
||||
NonCodeValue::BlockComment { value } => value.clone(),
|
||||
NonCodeValue::NewLineBlockComment { value } => value.clone(),
|
||||
NonCodeValue::NewLine => "\n\n".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn format(&self, indentation: &str) -> String {
|
||||
match &self.value {
|
||||
NoneCodeValue::InlineComment { value } => format!(" // {}\n", value),
|
||||
NoneCodeValue::BlockComment { value } => {
|
||||
NonCodeValue::InlineComment { value } => format!(" // {}\n", value),
|
||||
NonCodeValue::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)
|
||||
@ -598,7 +667,7 @@ impl NoneCodeNode {
|
||||
format!("{}{}// {}\n", add_start_new_line, indentation, value)
|
||||
}
|
||||
}
|
||||
NoneCodeValue::NewLineBlockComment { value } => {
|
||||
NonCodeValue::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)
|
||||
@ -606,7 +675,7 @@ impl NoneCodeNode {
|
||||
format!("{}{}// {}\n", add_start_new_line, indentation, value)
|
||||
}
|
||||
}
|
||||
NoneCodeValue::NewLine => "\n\n".to_string(),
|
||||
NonCodeValue::NewLine => "\n\n".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -614,7 +683,7 @@ impl NoneCodeNode {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
pub enum NoneCodeValue {
|
||||
pub enum NonCodeValue {
|
||||
/// An inline comment.
|
||||
/// An example of this is the following: `1 + 1 // This is an inline comment`.
|
||||
InlineComment {
|
||||
@ -643,35 +712,35 @@ pub enum NoneCodeValue {
|
||||
NewLine,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NoneCodeMeta {
|
||||
pub none_code_nodes: HashMap<usize, NoneCodeNode>,
|
||||
pub start: Option<NoneCodeNode>,
|
||||
pub struct NonCodeMeta {
|
||||
pub non_code_nodes: HashMap<usize, NonCodeNode>,
|
||||
pub start: Option<NonCodeNode>,
|
||||
}
|
||||
|
||||
// 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>
|
||||
// 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>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct NoneCodeMetaHelper {
|
||||
none_code_nodes: HashMap<String, NoneCodeNode>,
|
||||
start: Option<NoneCodeNode>,
|
||||
struct NonCodeMetaHelper {
|
||||
non_code_nodes: HashMap<String, NonCodeNode>,
|
||||
start: Option<NonCodeNode>,
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
Ok(NoneCodeMeta {
|
||||
none_code_nodes,
|
||||
Ok(NonCodeMeta {
|
||||
non_code_nodes,
|
||||
start: helper.start,
|
||||
})
|
||||
}
|
||||
@ -702,7 +771,33 @@ 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!(
|
||||
"{}({})",
|
||||
@ -776,6 +871,11 @@ 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)?;
|
||||
@ -843,6 +943,23 @@ 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.
|
||||
@ -883,6 +1000,15 @@ 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 {
|
||||
@ -1063,6 +1189,21 @@ 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")]
|
||||
@ -1075,7 +1216,30 @@ 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 { '\'' };
|
||||
@ -1120,6 +1284,22 @@ 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 {
|
||||
@ -1138,6 +1318,24 @@ 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")]
|
||||
@ -1149,7 +1347,36 @@ 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!(
|
||||
"[{}]",
|
||||
@ -1272,6 +1499,29 @@ 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!(
|
||||
"{{ {} }}",
|
||||
@ -1527,6 +1777,14 @@ 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) => {
|
||||
@ -1562,10 +1820,11 @@ impl MemberExpression {
|
||||
let value = memory.get(&identifier.name, identifier.into())?;
|
||||
value.clone()
|
||||
}
|
||||
}
|
||||
.get_json_value()?;
|
||||
};
|
||||
|
||||
if let serde_json::Value::Array(array) = array {
|
||||
let array_json = array.get_json_value()?;
|
||||
|
||||
if let serde_json::Value::Array(array) = array_json {
|
||||
if let Some(value) = array.get(index) {
|
||||
Ok(MemoryItem::UserVal(UserVal {
|
||||
value: value.clone(),
|
||||
@ -1613,10 +1872,11 @@ impl MemberExpression {
|
||||
let value = memory.get(&identifier.name, identifier.into())?;
|
||||
value.clone()
|
||||
}
|
||||
}
|
||||
.get_json_value()?;
|
||||
};
|
||||
|
||||
if let serde_json::Value::Object(map) = object {
|
||||
let object_json = object.get_json_value()?;
|
||||
|
||||
if let serde_json::Value::Object(map) = object_json {
|
||||
if let Some(value) = map.get(&property_name) {
|
||||
Ok(MemoryItem::UserVal(UserVal {
|
||||
value: value.clone(),
|
||||
@ -1676,6 +1936,26 @@ 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()
|
||||
}
|
||||
@ -1691,7 +1971,9 @@ impl BinaryExpression {
|
||||
|
||||
let should_wrap_right = match &self.right {
|
||||
BinaryPart::BinaryExpression(bin_exp) => {
|
||||
self.precedence() > bin_exp.precedence() || self.operator == BinaryOperator::Sub
|
||||
self.precedence() > bin_exp.precedence()
|
||||
|| self.operator == BinaryOperator::Sub
|
||||
|| self.operator == BinaryOperator::Div
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
@ -1874,6 +2156,19 @@ 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))
|
||||
}
|
||||
@ -1943,12 +2238,43 @@ pub struct PipeExpression {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub body: Vec<Value>,
|
||||
pub non_code_meta: NoneCodeMeta,
|
||||
pub non_code_meta: NonCodeMeta,
|
||||
}
|
||||
|
||||
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()
|
||||
@ -1957,7 +2283,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.none_code_nodes.get(&index) {
|
||||
if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
|
||||
s += non_code_meta_value.format(&indentation).trim_end_matches('\n')
|
||||
}
|
||||
|
||||
@ -2067,6 +2393,13 @@ 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();
|
||||
@ -2171,11 +2504,150 @@ 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 super::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
// We have this as a test so we can ensure it never panics with an unwrap in the server.
|
||||
#[test]
|
||||
fn test_variable_kind_to_completion() {
|
||||
@ -2208,9 +2680,8 @@ 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":{"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 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 recasted = some_program.recast(&Default::default(), 0);
|
||||
assert_eq!(
|
||||
@ -2425,7 +2896,7 @@ const things = "things"
|
||||
let some_program_string = r#"let b = {
|
||||
"end": 141,
|
||||
"start": 125,
|
||||
"type": "NoneCodeNode",
|
||||
"type": "NonCodeNode",
|
||||
"value": "
|
||||
// a comment
|
||||
"
|
||||
@ -2605,4 +3076,20 @@ 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);
|
||||
}
|
||||
}
|
@ -486,7 +486,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_serialize_function() {
|
||||
let some_function = crate::abstract_syntax_tree_types::Function::StdLib {
|
||||
let some_function = crate::ast::types::Function::StdLib {
|
||||
func: Box::new(crate::std::sketch::Line),
|
||||
};
|
||||
let serialized = serde_json::to_string(&some_function).unwrap();
|
||||
@ -496,12 +496,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_deserialize_function() {
|
||||
let some_function_string = r#"{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}}"#;
|
||||
let some_function: crate::abstract_syntax_tree_types::Function =
|
||||
serde_json::from_str(some_function_string).unwrap();
|
||||
let some_function: crate::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
some_function,
|
||||
crate::abstract_syntax_tree_types::Function::StdLib {
|
||||
crate::ast::types::Function::StdLib {
|
||||
func: Box::new(crate::std::sketch::Line),
|
||||
}
|
||||
);
|
||||
@ -510,12 +509,11 @@ mod tests {
|
||||
#[test]
|
||||
fn test_deserialize_function_show() {
|
||||
let some_function_string = r#"{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}}"#;
|
||||
let some_function: crate::abstract_syntax_tree_types::Function =
|
||||
serde_json::from_str(some_function_string).unwrap();
|
||||
let some_function: crate::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
some_function,
|
||||
crate::abstract_syntax_tree_types::Function::StdLib {
|
||||
crate::ast::types::Function::StdLib {
|
||||
func: Box::new(crate::std::Show),
|
||||
}
|
||||
);
|
||||
|
@ -4,18 +4,21 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Result;
|
||||
use dashmap::DashMap;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use kittycad::types::{OkWebSocketResponseData, WebSocketRequest, WebSocketResponse};
|
||||
use tokio_tungstenite::tungstenite::Message as WsMsg;
|
||||
|
||||
use crate::errors::{KclError, KclErrorDetails};
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EngineConnection {
|
||||
tcp_write: futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>,
|
||||
tcp_read_handle: tokio::task::JoinHandle<Result<()>>,
|
||||
export_notifier: Arc<tokio::sync::Notify>,
|
||||
snapshot_notifier: Arc<tokio::sync::Notify>,
|
||||
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
|
||||
}
|
||||
|
||||
impl Drop for EngineConnection {
|
||||
@ -31,8 +34,10 @@ pub struct TcpRead {
|
||||
|
||||
impl TcpRead {
|
||||
pub async fn read(&mut self) -> Result<WebSocketResponse> {
|
||||
let msg = self.stream.next().await.unwrap()?;
|
||||
let msg: WebSocketResponse = match msg {
|
||||
let Some(msg) = self.stream.next().await else {
|
||||
anyhow::bail!("Failed to read from websocket");
|
||||
};
|
||||
let msg: WebSocketResponse = match msg? {
|
||||
WsMsg::Text(text) => serde_json::from_str(&text)?,
|
||||
WsMsg::Binary(bin) => bson::from_slice(&bin)?,
|
||||
other => anyhow::bail!("Unexpected websocket message from server: {}", other),
|
||||
@ -42,17 +47,7 @@ impl TcpRead {
|
||||
}
|
||||
|
||||
impl EngineConnection {
|
||||
pub async fn new(ws: reqwest::Upgraded, export_dir: &str, snapshot_file: &str) -> Result<EngineConnection> {
|
||||
// Make sure the export directory exists and that it is a directory.
|
||||
let export_dir = std::path::Path::new(export_dir).to_owned();
|
||||
if !export_dir.exists() {
|
||||
anyhow::bail!("Export directory does not exist: {}", export_dir.display());
|
||||
}
|
||||
// Make sure it is a directory.
|
||||
if !export_dir.is_dir() {
|
||||
anyhow::bail!("Export directory is not a directory: {}", export_dir.display());
|
||||
}
|
||||
|
||||
pub async fn new(ws: reqwest::Upgraded) -> Result<EngineConnection> {
|
||||
let ws_stream = tokio_tungstenite::WebSocketStream::from_raw_socket(
|
||||
ws,
|
||||
tokio_tungstenite::tungstenite::protocol::Role::Client,
|
||||
@ -64,73 +59,21 @@ impl EngineConnection {
|
||||
|
||||
let mut tcp_read = TcpRead { stream: tcp_read };
|
||||
|
||||
let export_notifier = Arc::new(tokio::sync::Notify::new());
|
||||
let export_notifier_clone = export_notifier.clone();
|
||||
|
||||
let snapshot_notifier = Arc::new(tokio::sync::Notify::new());
|
||||
let snapshot_notifier_clone = snapshot_notifier.clone();
|
||||
|
||||
let snapshot_file = snapshot_file.to_owned();
|
||||
let responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>> = Arc::new(DashMap::new());
|
||||
let responses_clone = responses.clone();
|
||||
|
||||
let tcp_read_handle = tokio::spawn(async move {
|
||||
// Get Websocket messages from API server
|
||||
loop {
|
||||
match tcp_read.read().await {
|
||||
Ok(ws_resp) => {
|
||||
if let Some(success) = ws_resp.success {
|
||||
if !success {
|
||||
println!("got ws errors: {:?}", ws_resp.errors);
|
||||
export_notifier.notify_one();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(msg) = ws_resp.resp {
|
||||
match msg {
|
||||
OkWebSocketResponseData::MetricsRequest {} => {
|
||||
// @paultag todo
|
||||
}
|
||||
OkWebSocketResponseData::IceServerInfo { ice_servers } => {
|
||||
println!("got ice server info: {:?}", ice_servers);
|
||||
}
|
||||
OkWebSocketResponseData::SdpAnswer { answer } => {
|
||||
println!("got sdp answer: {:?}", answer);
|
||||
}
|
||||
OkWebSocketResponseData::TrickleIce { candidate } => {
|
||||
println!("got trickle ice: {:?}", candidate);
|
||||
}
|
||||
OkWebSocketResponseData::Modeling { modeling_response } => {
|
||||
if let kittycad::types::OkModelingCmdResponse::TakeSnapshot { data } =
|
||||
modeling_response
|
||||
{
|
||||
if snapshot_file.is_empty() {
|
||||
println!("Got snapshot, but no snapshot file specified.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Save the snapshot locally.
|
||||
std::fs::write(&snapshot_file, data.contents)?;
|
||||
snapshot_notifier.notify_one();
|
||||
}
|
||||
}
|
||||
OkWebSocketResponseData::Export { files } => {
|
||||
// Save the files to our export directory.
|
||||
for file in files {
|
||||
let path = export_dir.join(file.name);
|
||||
std::fs::write(&path, file.contents)?;
|
||||
println!("Wrote file: {}", path.display());
|
||||
}
|
||||
|
||||
// Tell the export notifier that we have new files.
|
||||
export_notifier.notify_one();
|
||||
}
|
||||
}
|
||||
if let Some(id) = ws_resp.request_id {
|
||||
responses_clone.insert(id, ws_resp.clone());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
println!("got ws error: {:?}", e);
|
||||
export_notifier.notify_one();
|
||||
continue;
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,40 +82,60 @@ impl EngineConnection {
|
||||
Ok(EngineConnection {
|
||||
tcp_write,
|
||||
tcp_read_handle,
|
||||
export_notifier: export_notifier_clone,
|
||||
snapshot_notifier: snapshot_notifier_clone,
|
||||
responses,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn wait_for_export(&self) {
|
||||
self.export_notifier.notified().await;
|
||||
}
|
||||
|
||||
pub async fn wait_for_snapshot(&self) {
|
||||
self.snapshot_notifier.notified().await;
|
||||
}
|
||||
|
||||
pub async fn tcp_send(&mut self, msg: WebSocketRequest) -> Result<()> {
|
||||
let msg = serde_json::to_string(&msg)?;
|
||||
self.tcp_write.send(WsMsg::Text(msg)).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_modeling_cmd(
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl EngineManager for EngineConnection {
|
||||
/// Send a modeling command.
|
||||
/// Do not wait for the response message.
|
||||
fn send_modeling_cmd(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<(), KclError> {
|
||||
futures::executor::block_on(self.tcp_send(WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id })).map_err(
|
||||
|e| {
|
||||
futures::executor::block_on(self.send_modeling_cmd_get_response(id, source_range, cmd))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send a modeling command and wait for the response message.
|
||||
async fn send_modeling_cmd_get_response(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<OkWebSocketResponseData, KclError> {
|
||||
self.tcp_send(WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id })
|
||||
.await
|
||||
.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to send modeling command: {}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
},
|
||||
)?;
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// Wait for the response.
|
||||
loop {
|
||||
if let Some(resp) = self.responses.get(&id) {
|
||||
if let Some(data) = &resp.resp {
|
||||
return Ok(data.clone());
|
||||
} else {
|
||||
return Err(KclError::Engine(KclErrorDetails {
|
||||
message: format!("Modeling command failed: {:?}", resp.errors),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
//! engine.
|
||||
|
||||
use anyhow::Result;
|
||||
use kittycad::types::OkWebSocketResponseData;
|
||||
|
||||
use crate::errors::KclError;
|
||||
|
||||
@ -12,8 +13,11 @@ impl EngineConnection {
|
||||
pub async fn new() -> Result<EngineConnection> {
|
||||
Ok(EngineConnection {})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_modeling_cmd(
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl crate::engine::EngineManager for EngineConnection {
|
||||
fn send_modeling_cmd(
|
||||
&mut self,
|
||||
_id: uuid::Uuid,
|
||||
_source_range: crate::executor::SourceRange,
|
||||
@ -21,4 +25,13 @@ impl EngineConnection {
|
||||
) -> Result<(), KclError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_modeling_cmd_get_response(
|
||||
&mut self,
|
||||
_id: uuid::Uuid,
|
||||
_source_range: crate::executor::SourceRange,
|
||||
_cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<OkWebSocketResponseData, KclError> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -30,8 +30,11 @@ impl EngineConnection {
|
||||
pub async fn new(manager: EngineCommandManager) -> Result<EngineConnection, JsValue> {
|
||||
Ok(EngineConnection { manager })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_modeling_cmd(
|
||||
#[async_trait::async_trait(?Send)]
|
||||
impl crate::engine::EngineManager for EngineConnection {
|
||||
fn send_modeling_cmd(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
@ -55,4 +58,53 @@ impl EngineConnection {
|
||||
.sendModelingCommandFromWasm(id.to_string(), source_range_str, cmd_str);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn send_modeling_cmd_get_response(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<kittycad::types::OkWebSocketResponseData, KclError> {
|
||||
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize source range: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
let ws_msg = WebSocketRequest::ModelingCmdReq { cmd, cmd_id: id };
|
||||
let cmd_str = serde_json::to_string(&ws_msg).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to serialize modeling command: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let promise = self
|
||||
.manager
|
||||
.sendModelingCommandFromWasm(id.to_string(), source_range_str, cmd_str);
|
||||
|
||||
let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to wait for promise from engine: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
// Parse the value as a string.
|
||||
let s = value.as_string().ok_or_else(|| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to get string from response from engine: `{:?}`", value),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
let modeling_result: kittycad::types::OkWebSocketResponseData = serde_json::from_str(&s).map_err(|e| {
|
||||
KclError::Engine(KclErrorDetails {
|
||||
message: format!("Failed to deserialize response from engine: {:?}", e),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(modeling_result)
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,5 @@
|
||||
//! Functions for managing engine communications.
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
#[cfg(feature = "engine")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[cfg(not(test))]
|
||||
#[cfg(feature = "engine")]
|
||||
@ -31,37 +26,27 @@ pub use conn_mock::EngineConnection;
|
||||
#[cfg(not(feature = "engine"))]
|
||||
#[cfg(not(test))]
|
||||
pub mod conn_mock;
|
||||
use anyhow::Result;
|
||||
#[cfg(not(feature = "engine"))]
|
||||
#[cfg(not(test))]
|
||||
pub use conn_mock::EngineConnection;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
#[derive(Debug)]
|
||||
#[wasm_bindgen]
|
||||
pub struct EngineManager {
|
||||
connection: EngineConnection,
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(not(test))]
|
||||
#[cfg(feature = "engine")]
|
||||
#[wasm_bindgen]
|
||||
impl EngineManager {
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub async fn new(manager: conn_wasm::EngineCommandManager) -> EngineManager {
|
||||
EngineManager {
|
||||
// This unwrap is safe because the connection is always created.
|
||||
connection: EngineConnection::new(manager).await.unwrap(),
|
||||
}
|
||||
}
|
||||
#[async_trait::async_trait(?Send)]
|
||||
pub trait EngineManager {
|
||||
/// Send a modeling command.
|
||||
/// Do not wait for the response message.
|
||||
fn send_modeling_cmd(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<(), crate::errors::KclError>;
|
||||
|
||||
pub fn send_modeling_cmd(&mut self, id_str: &str, cmd_str: &str) -> Result<(), String> {
|
||||
let id = uuid::Uuid::parse_str(id_str).map_err(|e| e.to_string())?;
|
||||
let cmd = serde_json::from_str(cmd_str).map_err(|e| e.to_string())?;
|
||||
self.connection
|
||||
.send_modeling_cmd(id, crate::executor::SourceRange::default(), cmd)
|
||||
.map_err(String::from)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
/// Send a modeling command and wait for the response message.
|
||||
async fn send_modeling_cmd_get_response(
|
||||
&mut self,
|
||||
id: uuid::Uuid,
|
||||
source_range: crate::executor::SourceRange,
|
||||
cmd: kittycad::types::ModelingCmd,
|
||||
) -> Result<kittycad::types::OkWebSocketResponseData, crate::errors::KclError>;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
||||
|
||||
use crate::{
|
||||
abstract_syntax_tree_types::{BodyItem, Function, FunctionExpression, Value},
|
||||
ast::types::{BodyItem, Function, FunctionExpression, Value},
|
||||
engine::EngineConnection,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
};
|
||||
@ -101,8 +101,8 @@ impl ProgramReturn {
|
||||
#[serde(tag = "type")]
|
||||
pub enum MemoryItem {
|
||||
UserVal(UserVal),
|
||||
SketchGroup(SketchGroup),
|
||||
ExtrudeGroup(ExtrudeGroup),
|
||||
SketchGroup(Box<SketchGroup>),
|
||||
ExtrudeGroup(Box<ExtrudeGroup>),
|
||||
#[ts(skip)]
|
||||
ExtrudeTransform(ExtrudeTransform),
|
||||
#[ts(skip)]
|
||||
@ -159,10 +159,12 @@ impl MemoryItem {
|
||||
if let MemoryItem::UserVal(user_val) = self {
|
||||
Ok(user_val.value.clone())
|
||||
} else {
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Not a user value: {:?}", self),
|
||||
source_ranges: self.clone().into(),
|
||||
}))
|
||||
serde_json::to_value(self).map_err(|err| {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!("Cannot convert memory item to json value: {:?}", err),
|
||||
source_ranges: self.clone().into(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -578,7 +580,7 @@ impl Default for PipeInfo {
|
||||
|
||||
/// Execute a AST's program.
|
||||
pub fn execute(
|
||||
program: crate::abstract_syntax_tree_types::Program,
|
||||
program: crate::ast::types::Program,
|
||||
memory: &mut ProgramMemory,
|
||||
options: BodyType,
|
||||
engine: &mut EngineConnection,
|
||||
@ -599,6 +601,10 @@ pub fn execute(
|
||||
let memory_item = memory.get(&identifier.name, identifier.into())?;
|
||||
args.push(memory_item.clone());
|
||||
}
|
||||
Value::CallExpression(call_expr) => {
|
||||
let result = call_expr.execute(memory, &mut pipe_info, engine)?;
|
||||
args.push(result);
|
||||
}
|
||||
// We do nothing for the rest.
|
||||
_ => (),
|
||||
}
|
||||
@ -1173,6 +1179,16 @@ show(thisBox)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_math_execute_with_pi() {
|
||||
let ast = r#"const myVar = pi() * 2"#;
|
||||
let memory = parse_execute(ast).await.unwrap();
|
||||
assert_eq!(
|
||||
serde_json::json!(std::f64::consts::TAU),
|
||||
memory.root.get("myVar").unwrap().get_json_value().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_math_define_decimal_without_leading_zero() {
|
||||
let ast = r#"let thing = .4 + 7"#;
|
||||
@ -1182,4 +1198,92 @@ show(thisBox)
|
||||
memory.root.get("thing").unwrap().get_json_value().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_zero_param_fn() {
|
||||
let ast = r#"const sigmaAllow = 35000 // psi
|
||||
const leg1 = 5 // inches
|
||||
const leg2 = 8 // inches
|
||||
fn thickness = () => { return 0.56 }
|
||||
|
||||
const bracket = startSketchAt([0,0])
|
||||
|> line([0, leg1], %)
|
||||
|> line([leg2, 0], %)
|
||||
|> line([0, -thickness()], %)
|
||||
|> line([-leg2 + thickness(), 0], %)
|
||||
"#;
|
||||
parse_execute(ast).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_math_negative_variable_in_binary_expression() {
|
||||
let ast = r#"const sigmaAllow = 35000 // psi
|
||||
const width = 1 // inch
|
||||
|
||||
const p = 150 // lbs
|
||||
const distance = 6 // inches
|
||||
const FOS = 2
|
||||
|
||||
const leg1 = 5 // inches
|
||||
const leg2 = 8 // inches
|
||||
|
||||
const thickness_squared = distance * p * FOS * 6 / sigmaAllow
|
||||
const thickness = 0.56 // inches. App does not support square root function yet
|
||||
|
||||
const bracket = startSketchAt([0,0])
|
||||
|> line([0, leg1], %)
|
||||
|> line([leg2, 0], %)
|
||||
|> line([0, -thickness], %)
|
||||
|> line([-leg2 + thickness, 0], %)
|
||||
"#;
|
||||
parse_execute(ast).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_math_doubly_nested_parens() {
|
||||
let ast = r#"const sigmaAllow = 35000 // psi
|
||||
const width = 4 // 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_squared = (distance * p * FOS * 6 / (sigmaAllow - width))
|
||||
const thickness = 0.32 // inches. App does not support square root function yet
|
||||
const bracket = startSketchAt([0,0])
|
||||
|> line([0, leg1], %)
|
||||
|> line([leg2, 0], %)
|
||||
|> line([0, -thickness], %)
|
||||
|> line([-1 * leg2 + thickness, 0], %)
|
||||
|> line([0, -1 * leg1 + thickness], %)
|
||||
|> close(%)
|
||||
|> extrude(width, %)
|
||||
show(bracket)
|
||||
"#;
|
||||
parse_execute(ast).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_math_nested_parens_one_less() {
|
||||
let ast = r#"const sigmaAllow = 35000 // psi
|
||||
const width = 4 // 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_squared = distance * p * FOS * 6 / (sigmaAllow - width)
|
||||
const thickness = 0.32 // inches. App does not support square root function yet
|
||||
const bracket = startSketchAt([0,0])
|
||||
|> line([0, leg1], %)
|
||||
|> line([leg2, 0], %)
|
||||
|> line([0, -thickness], %)
|
||||
|> line([-1 * leg2 + thickness, 0], %)
|
||||
|> line([0, -1 * leg1 + thickness], %)
|
||||
|> close(%)
|
||||
|> extrude(width, %)
|
||||
show(bracket)
|
||||
"#;
|
||||
parse_execute(ast).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
pub mod abstract_syntax_tree_types;
|
||||
#![recursion_limit = "1024"]
|
||||
|
||||
pub mod ast;
|
||||
pub mod docs;
|
||||
pub mod engine;
|
||||
pub mod errors;
|
||||
|
@ -4,8 +4,9 @@ use anyhow::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
abstract_syntax_tree_types::{
|
||||
BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, MemberExpression, ValueMeta,
|
||||
ast::types::{
|
||||
BinaryExpression, BinaryOperator, BinaryPart, CallExpression, Identifier, Literal, MemberExpression,
|
||||
UnaryExpression, ValueMeta,
|
||||
},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::SourceRange,
|
||||
@ -40,7 +41,7 @@ pub struct ParenthesisToken {
|
||||
pub end: usize,
|
||||
}
|
||||
|
||||
crate::abstract_syntax_tree_types::impl_value_meta!(ParenthesisToken);
|
||||
crate::ast::types::impl_value_meta!(ParenthesisToken);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
@ -55,7 +56,7 @@ pub struct ExtendedBinaryExpression {
|
||||
pub end_extended: Option<usize>,
|
||||
}
|
||||
|
||||
crate::abstract_syntax_tree_types::impl_value_meta!(ExtendedBinaryExpression);
|
||||
crate::ast::types::impl_value_meta!(ExtendedBinaryExpression);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
@ -69,7 +70,7 @@ pub struct ExtendedLiteral {
|
||||
pub end_extended: Option<usize>,
|
||||
}
|
||||
|
||||
crate::abstract_syntax_tree_types::impl_value_meta!(ExtendedLiteral);
|
||||
crate::ast::types::impl_value_meta!(ExtendedLiteral);
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
@ -82,6 +83,7 @@ pub enum MathExpression {
|
||||
ExtendedBinaryExpression(Box<ExtendedBinaryExpression>),
|
||||
ParenthesisToken(Box<ParenthesisToken>),
|
||||
MemberExpression(Box<MemberExpression>),
|
||||
UnaryExpression(Box<UnaryExpression>),
|
||||
}
|
||||
|
||||
impl MathExpression {
|
||||
@ -94,6 +96,7 @@ impl MathExpression {
|
||||
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.start(),
|
||||
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.start(),
|
||||
MathExpression::MemberExpression(member_expression) => member_expression.start(),
|
||||
MathExpression::UnaryExpression(unary_expression) => unary_expression.start(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,6 +109,7 @@ impl MathExpression {
|
||||
MathExpression::ExtendedBinaryExpression(extended_binary_expression) => extended_binary_expression.end(),
|
||||
MathExpression::ParenthesisToken(parenthesis_token) => parenthesis_token.end(),
|
||||
MathExpression::MemberExpression(member_expression) => member_expression.end(),
|
||||
MathExpression::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -208,7 +212,8 @@ impl ReversePolishNotation {
|
||||
if prevtoken.token_type == TokenType::Operator {
|
||||
// Get the next token and see if it is a number.
|
||||
if let Ok(nexttoken) = self.parser.get_token(1) {
|
||||
if nexttoken.token_type == TokenType::Number {
|
||||
if nexttoken.token_type == TokenType::Number || nexttoken.token_type == TokenType::Word
|
||||
{
|
||||
// We have a negative number/ word or string.
|
||||
// Change the value of the token to be the negative number/ word or string.
|
||||
let mut new_token = nexttoken.clone();
|
||||
@ -250,7 +255,7 @@ impl ReversePolishNotation {
|
||||
&& current_token.value == "-"
|
||||
{
|
||||
if let Ok(nexttoken) = self.parser.get_token(1) {
|
||||
if nexttoken.token_type == TokenType::Number {
|
||||
if nexttoken.token_type == TokenType::Number || nexttoken.token_type == TokenType::Word {
|
||||
// We have a negative number/ word or string.
|
||||
// Change the value of the token to be the negative number/ word or string.
|
||||
let mut new_token = nexttoken.clone();
|
||||
@ -417,9 +422,8 @@ impl ReversePolishNotation {
|
||||
{
|
||||
let closing_brace = self.parser.find_closing_brace(1, 0, "")?;
|
||||
let mut new_stack = stack;
|
||||
new_stack.push(MathExpression::CallExpression(Box::new(
|
||||
self.parser.make_call_expression(0)?.expression,
|
||||
)));
|
||||
let call_expression = self.parser.make_call_expression(0)?;
|
||||
new_stack.push(MathExpression::CallExpression(Box::new(call_expression.expression)));
|
||||
return self.build_tree(&reverse_polish_notation_tokens[closing_brace + 1..], new_stack);
|
||||
}
|
||||
if reverse_polish_notation_tokens[1].token_type == TokenType::Period
|
||||
@ -435,11 +439,25 @@ impl ReversePolishNotation {
|
||||
);
|
||||
}
|
||||
let mut new_stack = stack;
|
||||
new_stack.push(MathExpression::Identifier(Box::new(Identifier {
|
||||
name: current_token.value.clone(),
|
||||
start: current_token.start,
|
||||
end: current_token.end,
|
||||
})));
|
||||
if current_token.value.starts_with('-') {
|
||||
let expression = UnaryExpression {
|
||||
start: current_token.start,
|
||||
end: current_token.end,
|
||||
operator: crate::ast::types::UnaryOperator::Neg,
|
||||
argument: BinaryPart::Identifier(Box::new(Identifier {
|
||||
name: current_token.value.trim_start_matches('-').to_string(),
|
||||
start: current_token.start + 1,
|
||||
end: current_token.end,
|
||||
})),
|
||||
};
|
||||
new_stack.push(MathExpression::UnaryExpression(Box::new(expression)));
|
||||
} else {
|
||||
new_stack.push(MathExpression::Identifier(Box::new(Identifier {
|
||||
name: current_token.value.clone(),
|
||||
start: current_token.start,
|
||||
end: current_token.end,
|
||||
})));
|
||||
}
|
||||
return self.build_tree(&reverse_polish_notation_tokens[1..], new_stack);
|
||||
}
|
||||
} else if current_token.token_type == TokenType::Brace && current_token.value == "(" {
|
||||
@ -571,6 +589,10 @@ impl ReversePolishNotation {
|
||||
BinaryPart::MemberExpression(member_expression.clone()),
|
||||
member_expression.start,
|
||||
),
|
||||
MathExpression::UnaryExpression(unary_expression) => (
|
||||
BinaryPart::UnaryExpression(unary_expression.clone()),
|
||||
unary_expression.start,
|
||||
),
|
||||
a => {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
@ -605,6 +627,10 @@ impl ReversePolishNotation {
|
||||
BinaryPart::MemberExpression(member_expression.clone()),
|
||||
member_expression.end,
|
||||
),
|
||||
MathExpression::UnaryExpression(unary_expression) => (
|
||||
BinaryPart::UnaryExpression(unary_expression.clone()),
|
||||
unary_expression.end,
|
||||
),
|
||||
a => {
|
||||
return Err(KclError::InvalidExpression(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
@ -1244,4 +1270,22 @@ mod test {
|
||||
let output = parser.build_tree(&input_tokens, vec![]).unwrap();
|
||||
assert_eq!(output, expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_expression_braces_around_lots_of_math() {
|
||||
let code = "(distance * p * FOS * 6 / (sigmaAllow * width))";
|
||||
let tokens = crate::tokeniser::lexer(code);
|
||||
let mut parser = MathParser::new(&tokens);
|
||||
let result = parser.parse();
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_expression_braces_around_internals_lots_of_math() {
|
||||
let code = "distance * p * FOS * 6 / (sigmaAllow * width)";
|
||||
let tokens = crate::tokeniser::lexer(code);
|
||||
let mut parser = MathParser::new(&tokens);
|
||||
let result = parser.parse();
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
abstract_syntax_tree_types::{
|
||||
ast::types::{
|
||||
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ExpressionStatement,
|
||||
FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject, NoneCodeMeta,
|
||||
NoneCodeNode, NoneCodeValue, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression, PipeSubstitution,
|
||||
FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta,
|
||||
NonCodeNode, NonCodeValue, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression, PipeSubstitution,
|
||||
Program, ReturnStatement, UnaryExpression, UnaryOperator, Value, VariableDeclaration, VariableDeclarator,
|
||||
VariableKind,
|
||||
},
|
||||
@ -26,7 +26,7 @@ struct TokenReturn {
|
||||
struct TokenReturnWithNonCode {
|
||||
token: Option<Token>,
|
||||
index: usize,
|
||||
non_code_node: Option<NoneCodeNode>,
|
||||
non_code_node: Option<NonCodeNode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@ -57,7 +57,7 @@ struct ArrayReturn {
|
||||
struct PipeBodyReturn {
|
||||
body: Vec<Value>,
|
||||
last_index: usize,
|
||||
non_code_meta: NoneCodeMeta,
|
||||
non_code_meta: NonCodeMeta,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@ -103,9 +103,9 @@ pub struct ParamsResult {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
struct UnaryExpressionResult {
|
||||
expression: UnaryExpression,
|
||||
last_index: usize,
|
||||
pub struct UnaryExpressionResult {
|
||||
pub expression: UnaryExpression,
|
||||
pub last_index: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@ -136,7 +136,7 @@ struct ReturnStatementResult {
|
||||
struct BodyResult {
|
||||
body: Vec<BodyItem>,
|
||||
last_index: usize,
|
||||
non_code_meta: NoneCodeMeta,
|
||||
non_code_meta: NonCodeMeta,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
@ -183,8 +183,8 @@ impl Parser {
|
||||
let body = self.make_body(
|
||||
0,
|
||||
vec![],
|
||||
NoneCodeMeta {
|
||||
none_code_nodes: HashMap::new(),
|
||||
NonCodeMeta {
|
||||
non_code_nodes: HashMap::new(),
|
||||
start: None,
|
||||
},
|
||||
)?;
|
||||
@ -269,7 +269,7 @@ impl Parser {
|
||||
Ok(index + 1)
|
||||
}
|
||||
|
||||
fn make_none_code_node(&self, index: usize) -> Result<(Option<NoneCodeNode>, usize), KclError> {
|
||||
fn make_non_code_node(&self, index: usize) -> Result<(Option<NonCodeNode>, usize), KclError> {
|
||||
let end_index = self.find_end_of_non_code_node(index)?;
|
||||
let start_index = self.find_start_of_non_code_node(index)?;
|
||||
let non_code_tokens = self.tokens[index..end_index].to_vec();
|
||||
@ -286,10 +286,10 @@ impl Parser {
|
||||
.contains("\n\n")
|
||||
{
|
||||
return Ok((
|
||||
Some(NoneCodeNode {
|
||||
Some(NonCodeNode {
|
||||
start: self.tokens[start_index].start,
|
||||
end: self.tokens[end_index - 1].end,
|
||||
value: NoneCodeValue::NewLine,
|
||||
value: NonCodeValue::NewLine,
|
||||
}),
|
||||
end_index - 1,
|
||||
));
|
||||
@ -330,17 +330,17 @@ impl Parser {
|
||||
let is_new_line_comment =
|
||||
start_end_string.starts_with('\n') || start_end_string.contains('\n') || start_index == 0 || index == 0;
|
||||
|
||||
let node = NoneCodeNode {
|
||||
let node = NonCodeNode {
|
||||
start: self.tokens[start_index].start,
|
||||
end: self.tokens[end_index - 1].end,
|
||||
value: if start_end_string.starts_with("\n\n") && is_new_line_comment {
|
||||
// Preserve if they want a whitespace line before the comment.
|
||||
// But let's just allow one.
|
||||
NoneCodeValue::NewLineBlockComment { value: full_string }
|
||||
NonCodeValue::NewLineBlockComment { value: full_string }
|
||||
} else if is_new_line_comment {
|
||||
NoneCodeValue::BlockComment { value: full_string }
|
||||
NonCodeValue::BlockComment { value: full_string }
|
||||
} else {
|
||||
NoneCodeValue::InlineComment { value: full_string }
|
||||
NonCodeValue::InlineComment { value: full_string }
|
||||
},
|
||||
};
|
||||
Ok((Some(node), end_index - 1))
|
||||
@ -366,7 +366,7 @@ impl Parser {
|
||||
};
|
||||
|
||||
if is_not_code_token(token) {
|
||||
let non_code_node = self.make_none_code_node(new_index)?;
|
||||
let non_code_node = self.make_non_code_node(new_index)?;
|
||||
let new_new_index = non_code_node.1 + 1;
|
||||
let bonus_non_code_node = non_code_node.0;
|
||||
|
||||
@ -731,7 +731,9 @@ impl Parser {
|
||||
|
||||
let maybe_operator = self.next_meaningful_token(index, None)?;
|
||||
if let Some(maybe_operator_token) = maybe_operator.token {
|
||||
if maybe_operator_token.token_type == TokenType::Number {
|
||||
if maybe_operator_token.token_type == TokenType::Number
|
||||
|| maybe_operator_token.token_type == TokenType::Word
|
||||
{
|
||||
return self.find_end_of_binary_expression(maybe_operator.index);
|
||||
} else if maybe_operator_token.token_type != TokenType::Operator
|
||||
|| maybe_operator_token.value == PIPE_OPERATOR
|
||||
@ -775,6 +777,7 @@ impl Parser {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if current_token.token_type == TokenType::Word
|
||||
|| current_token.token_type == TokenType::Number
|
||||
|| current_token.token_type == TokenType::String
|
||||
@ -878,10 +881,12 @@ impl Parser {
|
||||
last_index: function_expression.last_index,
|
||||
})
|
||||
} else {
|
||||
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||
source_ranges: vec![current_token.into()],
|
||||
message: "expression with braces".to_string(),
|
||||
}));
|
||||
// This is likely a binary expression that starts with a parenthesis.
|
||||
let binary_expression = self.make_binary_expression(index)?;
|
||||
Ok(ValueReturn {
|
||||
value: Value::BinaryExpression(Box::new(binary_expression.expression)),
|
||||
last_index: binary_expression.last_index,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return Err(KclError::Unimplemented(KclErrorDetails {
|
||||
@ -1023,13 +1028,13 @@ impl Parser {
|
||||
&self,
|
||||
index: usize,
|
||||
previous_values: Vec<Value>,
|
||||
previous_non_code_meta: Option<NoneCodeMeta>,
|
||||
previous_non_code_meta: Option<NonCodeMeta>,
|
||||
) -> Result<PipeBodyReturn, KclError> {
|
||||
let non_code_meta = match previous_non_code_meta {
|
||||
Some(meta) => meta,
|
||||
None => NoneCodeMeta {
|
||||
None => NonCodeMeta {
|
||||
start: None,
|
||||
none_code_nodes: HashMap::new(),
|
||||
non_code_nodes: HashMap::new(),
|
||||
},
|
||||
};
|
||||
let current_token = self.get_token(index)?;
|
||||
@ -1056,10 +1061,10 @@ impl Parser {
|
||||
non_code_meta,
|
||||
});
|
||||
}
|
||||
let mut _non_code_meta: NoneCodeMeta;
|
||||
let mut _non_code_meta: NonCodeMeta;
|
||||
if let Some(node) = next_pipe.non_code_node {
|
||||
_non_code_meta = non_code_meta;
|
||||
_non_code_meta.none_code_nodes.insert(previous_values.len(), node);
|
||||
_non_code_meta.non_code_nodes.insert(previous_values.len(), node);
|
||||
} else {
|
||||
_non_code_meta = non_code_meta;
|
||||
}
|
||||
@ -1245,11 +1250,19 @@ impl Parser {
|
||||
})
|
||||
})?;
|
||||
let closing_brace_token = self.get_token(closing_brace_index)?;
|
||||
let args = self.make_arguments(brace_token.index, vec![])?;
|
||||
let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) {
|
||||
crate::abstract_syntax_tree_types::Function::StdLib { func: stdlib_fn }
|
||||
// Account for if we have no args.
|
||||
let args = if brace_token.index + 1 == closing_brace_index {
|
||||
ArgumentsReturn {
|
||||
arguments: vec![],
|
||||
last_index: closing_brace_index,
|
||||
}
|
||||
} else {
|
||||
crate::abstract_syntax_tree_types::Function::InMemory
|
||||
self.make_arguments(brace_token.index, vec![])?
|
||||
};
|
||||
let function = if let Some(stdlib_fn) = self.stdlib.get(&callee.name) {
|
||||
crate::ast::types::Function::StdLib { func: stdlib_fn }
|
||||
} else {
|
||||
crate::ast::types::Function::InMemory
|
||||
};
|
||||
Ok(CallExpressionResult {
|
||||
expression: CallExpression {
|
||||
@ -1418,7 +1431,7 @@ impl Parser {
|
||||
self.make_params(next_brace_or_comma_token.index, _previous_params)
|
||||
}
|
||||
|
||||
fn make_unary_expression(&self, index: usize) -> Result<UnaryExpressionResult, KclError> {
|
||||
pub fn make_unary_expression(&self, index: usize) -> Result<UnaryExpressionResult, KclError> {
|
||||
let current_token = self.get_token(index)?;
|
||||
let next_token = self.next_meaningful_token(index, None)?;
|
||||
if next_token.token.is_none() {
|
||||
@ -1583,7 +1596,7 @@ impl Parser {
|
||||
&self,
|
||||
token_index: usize,
|
||||
previous_body: Vec<BodyItem>,
|
||||
previous_non_code_meta: NoneCodeMeta,
|
||||
previous_non_code_meta: NonCodeMeta,
|
||||
) -> Result<BodyResult, KclError> {
|
||||
let mut non_code_meta = previous_non_code_meta;
|
||||
if self.tokens.is_empty() {
|
||||
@ -1616,7 +1629,7 @@ impl Parser {
|
||||
if previous_body.is_empty() {
|
||||
non_code_meta.start = next_token.non_code_node;
|
||||
} else {
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
}
|
||||
return self.make_body(next_token.index, previous_body, non_code_meta);
|
||||
@ -1624,14 +1637,14 @@ impl Parser {
|
||||
|
||||
let next = self.next_meaningful_token(token_index, None)?;
|
||||
if let Some(node) = &next.non_code_node {
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
|
||||
if token.token_type == TokenType::Keyword && VariableKind::from_str(&token.value).is_ok() {
|
||||
let declaration = self.make_variable_declaration(token_index)?;
|
||||
let next_thing = self.next_meaningful_token(declaration.last_index, None)?;
|
||||
if let Some(node) = &next_thing.non_code_node {
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
let mut _previous_body = previous_body;
|
||||
_previous_body.push(BodyItem::VariableDeclaration(VariableDeclaration {
|
||||
@ -1652,7 +1665,7 @@ impl Parser {
|
||||
let statement = self.make_return_statement(token_index)?;
|
||||
let next_thing = self.next_meaningful_token(statement.last_index, None)?;
|
||||
if let Some(node) = &next_thing.non_code_node {
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
let mut _previous_body = previous_body;
|
||||
_previous_body.push(BodyItem::ReturnStatement(ReturnStatement {
|
||||
@ -1676,7 +1689,7 @@ impl Parser {
|
||||
let expression = self.make_expression_statement(token_index)?;
|
||||
let next_thing = self.next_meaningful_token(expression.last_index, None)?;
|
||||
if let Some(node) = &next_thing.non_code_node {
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
let mut _previous_body = previous_body;
|
||||
_previous_body.push(BodyItem::ExpressionStatement(ExpressionStatement {
|
||||
@ -1699,7 +1712,7 @@ impl Parser {
|
||||
&& next_thing_token.token_type == TokenType::Operator
|
||||
{
|
||||
if let Some(node) = &next_thing.non_code_node {
|
||||
non_code_meta.none_code_nodes.insert(previous_body.len(), node.clone());
|
||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
||||
}
|
||||
let expression = self.make_expression_statement(token_index)?;
|
||||
let mut _previous_body = previous_body;
|
||||
@ -1730,8 +1743,8 @@ impl Parser {
|
||||
BodyResult {
|
||||
body: vec![],
|
||||
last_index: next_token_index,
|
||||
non_code_meta: NoneCodeMeta {
|
||||
none_code_nodes: HashMap::new(),
|
||||
non_code_meta: NonCodeMeta {
|
||||
non_code_nodes: HashMap::new(),
|
||||
start: None,
|
||||
},
|
||||
}
|
||||
@ -1739,8 +1752,8 @@ impl Parser {
|
||||
self.make_body(
|
||||
next_token_index,
|
||||
vec![],
|
||||
NoneCodeMeta {
|
||||
none_code_nodes: HashMap::new(),
|
||||
NonCodeMeta {
|
||||
non_code_nodes: HashMap::new(),
|
||||
start: None,
|
||||
},
|
||||
)?
|
||||
@ -1786,7 +1799,7 @@ mod tests {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
use crate::abstract_syntax_tree_types::BinaryOperator;
|
||||
use crate::ast::types::BinaryOperator;
|
||||
|
||||
#[test]
|
||||
fn test_make_identifier() {
|
||||
@ -1866,16 +1879,16 @@ mod tests {
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn test_make_none_code_node() {
|
||||
fn test_make_non_code_node() {
|
||||
let tokens = crate::tokeniser::lexer("log(5, \"hello\", aIdentifier)");
|
||||
let parser = Parser::new(tokens);
|
||||
let index = 4;
|
||||
let expected_output = (None, 4);
|
||||
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output);
|
||||
assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
|
||||
|
||||
let index = 7;
|
||||
let expected_output = (None, 7);
|
||||
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output);
|
||||
assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
|
||||
let tokens = crate::tokeniser::lexer(
|
||||
r#"
|
||||
const yo = { a: { b: { c: '123' } } }
|
||||
@ -1885,28 +1898,28 @@ const key = 'c'"#,
|
||||
let parser = Parser::new(tokens);
|
||||
let index = 0;
|
||||
let expected_output = (None, 0);
|
||||
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output);
|
||||
assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
|
||||
|
||||
let index = 2;
|
||||
let expected_output = (None, 2);
|
||||
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output);
|
||||
assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
|
||||
|
||||
let index = 2;
|
||||
let expected_output = (None, 2);
|
||||
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output);
|
||||
assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
|
||||
|
||||
let index = 29;
|
||||
let expected_output = (
|
||||
Some(NoneCodeNode {
|
||||
Some(NonCodeNode {
|
||||
start: 38,
|
||||
end: 60,
|
||||
value: NoneCodeValue::BlockComment {
|
||||
value: NonCodeValue::BlockComment {
|
||||
value: "this is a comment".to_string(),
|
||||
},
|
||||
}),
|
||||
31,
|
||||
);
|
||||
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output);
|
||||
assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
|
||||
let tokens = crate::tokeniser::lexer(
|
||||
r#"const mySketch = startSketchAt([0,0])
|
||||
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|
||||
@ -1919,16 +1932,16 @@ const key = 'c'"#,
|
||||
let parser = Parser::new(tokens);
|
||||
let index = 57;
|
||||
let expected_output = (
|
||||
Some(NoneCodeNode {
|
||||
Some(NonCodeNode {
|
||||
start: 106,
|
||||
end: 166,
|
||||
value: NoneCodeValue::BlockComment {
|
||||
value: NonCodeValue::BlockComment {
|
||||
value: "this is\n a comment\n spanning a few lines".to_string(),
|
||||
},
|
||||
}),
|
||||
59,
|
||||
);
|
||||
assert_eq!(parser.make_none_code_node(index).unwrap(), expected_output);
|
||||
assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -2741,6 +2754,12 @@ show(mySk1)"#;
|
||||
let tokens = crate::tokeniser::lexer(code);
|
||||
let parser = Parser::new(tokens);
|
||||
let _end = parser.find_end_of_binary_expression(0).unwrap();
|
||||
// with call expression at the end of binary expression
|
||||
let code = "-legX + 2, ";
|
||||
let tokens = crate::tokeniser::lexer(code);
|
||||
let parser = Parser::new(tokens.clone());
|
||||
let end = parser.find_end_of_binary_expression(0).unwrap();
|
||||
assert_eq!(tokens[end].value, "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -2895,8 +2914,8 @@ show(mySk1)"#;
|
||||
.make_body(
|
||||
0,
|
||||
vec![],
|
||||
NoneCodeMeta {
|
||||
none_code_nodes: HashMap::new(),
|
||||
NonCodeMeta {
|
||||
non_code_nodes: HashMap::new(),
|
||||
start: None,
|
||||
},
|
||||
)
|
||||
@ -2933,8 +2952,8 @@ show(mySk1)"#;
|
||||
})),
|
||||
})),
|
||||
})],
|
||||
non_code_meta: NoneCodeMeta {
|
||||
none_code_nodes: Default::default(),
|
||||
non_code_meta: NonCodeMeta {
|
||||
non_code_nodes: Default::default(),
|
||||
start: None,
|
||||
},
|
||||
};
|
||||
@ -3079,6 +3098,20 @@ const secondExtrude = startSketchAt([0,0])
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_negative_in_array_binary_expression() {
|
||||
let tokens = crate::tokeniser::lexer(
|
||||
r#"const leg1 = 5
|
||||
const thickness = 0.56
|
||||
|
||||
const bracket = [-leg2 + thickness, 0]
|
||||
"#,
|
||||
);
|
||||
let parser = Parser::new(tokens);
|
||||
let result = parser.ast();
|
||||
assert!(result.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_nested_open_brackets() {
|
||||
let tokens = crate::tokeniser::lexer(
|
||||
@ -3254,8 +3287,8 @@ e
|
||||
}],
|
||||
kind: VariableKind::Const,
|
||||
})],
|
||||
non_code_meta: NoneCodeMeta {
|
||||
none_code_nodes: Default::default(),
|
||||
non_code_meta: NonCodeMeta {
|
||||
non_code_nodes: Default::default(),
|
||||
start: None,
|
||||
},
|
||||
};
|
||||
@ -3332,6 +3365,19 @@ e
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_param_function() {
|
||||
let program = r#"
|
||||
fn firstPrimeNumber = () => {
|
||||
return 2
|
||||
}
|
||||
firstPrimeNumber()
|
||||
"#;
|
||||
let tokens = crate::tokeniser::lexer(program);
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let _ast = parser.ast().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_keyword_ok_in_fn_args_return() {
|
||||
let some_program_string = r#"fn thing = (param) => {
|
||||
@ -3384,4 +3430,47 @@ thing(false)
|
||||
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 2])], message: "Expected a `let` variable kind, found: `fn`" }"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_member_expression_sketch_group() {
|
||||
let some_program_string = r#"fn cube = (pos, scale) => {
|
||||
const sg = startSketchAt(pos)
|
||||
|> line([0, scale], %)
|
||||
|> line([scale, 0], %)
|
||||
|> line([0, -scale], %)
|
||||
|
||||
return sg
|
||||
}
|
||||
|
||||
const b1 = cube([0,0], 10)
|
||||
const b2 = cube([3,3], 4)
|
||||
|
||||
const pt1 = b1[0]
|
||||
const pt2 = b2[0]
|
||||
|
||||
show(b1)
|
||||
show(b2)"#;
|
||||
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
parser.ast().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_math_with_stdlib() {
|
||||
let some_program_string = r#"const d2r = pi() / 2
|
||||
let other_thing = 2 * cos(3)"#;
|
||||
let tokens = crate::tokeniser::lexer(some_program_string);
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
parser.ast().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // ignore until more stack fixes
|
||||
fn test_parse_pipes_on_pipes() {
|
||||
let code = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
|
||||
|
||||
let tokens = crate::tokeniser::lexer(code);
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
parser.ast().unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -3,21 +3,23 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
#[cfg(feature = "cli")]
|
||||
use clap::Parser;
|
||||
use dashmap::DashMap;
|
||||
use tower_lsp::{jsonrpc::Result as RpcResult, lsp_types::*, Client, LanguageServer};
|
||||
|
||||
use crate::{abstract_syntax_tree_types::VariableKind, executor::SourceRange, parser::PIPE_OPERATOR};
|
||||
use crate::{ast::types::VariableKind, executor::SourceRange, parser::PIPE_OPERATOR};
|
||||
|
||||
/// A subcommand for running the server.
|
||||
#[derive(Parser, Clone, Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "cli", derive(Parser))]
|
||||
pub struct Server {
|
||||
/// Port that the server should listen
|
||||
#[clap(long, default_value = "8080")]
|
||||
#[cfg_attr(feature = "cli", clap(long, default_value = "8080"))]
|
||||
pub socket: i32,
|
||||
|
||||
/// Listen over stdin and stdout instead of a tcp socket.
|
||||
#[clap(short, long, default_value = "false")]
|
||||
#[cfg_attr(feature = "cli", clap(short, long, default_value = "false"))]
|
||||
pub stdio: bool,
|
||||
}
|
||||
|
||||
@ -34,7 +36,7 @@ pub struct Backend {
|
||||
/// Token maps.
|
||||
pub token_map: DashMap<String, Vec<crate::tokeniser::Token>>,
|
||||
/// AST maps.
|
||||
pub ast_map: DashMap<String, crate::abstract_syntax_tree_types::Program>,
|
||||
pub ast_map: DashMap<String, crate::ast::types::Program>,
|
||||
/// Current code.
|
||||
pub current_code_map: DashMap<String, String>,
|
||||
/// Diagnostics.
|
||||
@ -171,19 +173,19 @@ impl Backend {
|
||||
|
||||
for item in &ast.body {
|
||||
match item {
|
||||
crate::abstract_syntax_tree_types::BodyItem::ExpressionStatement(_) => continue,
|
||||
crate::abstract_syntax_tree_types::BodyItem::ReturnStatement(_) => continue,
|
||||
crate::abstract_syntax_tree_types::BodyItem::VariableDeclaration(variable) => {
|
||||
crate::ast::types::BodyItem::ExpressionStatement(_) => continue,
|
||||
crate::ast::types::BodyItem::ReturnStatement(_) => continue,
|
||||
crate::ast::types::BodyItem::VariableDeclaration(variable) => {
|
||||
// We only want to complete variables.
|
||||
for declaration in &variable.declarations {
|
||||
completions.push(CompletionItem {
|
||||
label: declaration.id.name.to_string(),
|
||||
label_details: None,
|
||||
kind: Some(match variable.kind {
|
||||
crate::abstract_syntax_tree_types::VariableKind::Let => CompletionItemKind::VARIABLE,
|
||||
crate::abstract_syntax_tree_types::VariableKind::Const => CompletionItemKind::CONSTANT,
|
||||
crate::abstract_syntax_tree_types::VariableKind::Var => CompletionItemKind::VARIABLE,
|
||||
crate::abstract_syntax_tree_types::VariableKind::Fn => CompletionItemKind::FUNCTION,
|
||||
crate::ast::types::VariableKind::Let => CompletionItemKind::VARIABLE,
|
||||
crate::ast::types::VariableKind::Const => CompletionItemKind::CONSTANT,
|
||||
crate::ast::types::VariableKind::Var => CompletionItemKind::VARIABLE,
|
||||
crate::ast::types::VariableKind::Fn => CompletionItemKind::FUNCTION,
|
||||
}),
|
||||
detail: Some(variable.kind.to_string()),
|
||||
documentation: None,
|
||||
@ -368,7 +370,7 @@ impl LanguageServer for Backend {
|
||||
};
|
||||
|
||||
match hover {
|
||||
crate::abstract_syntax_tree_types::Hover::Function { name, range } => {
|
||||
crate::ast::types::Hover::Function { name, range } => {
|
||||
// Get the docs for this function.
|
||||
let Some(completion) = self.stdlib_completions.get(&name) else {
|
||||
return Ok(None);
|
||||
@ -399,7 +401,7 @@ impl LanguageServer for Backend {
|
||||
range: Some(range),
|
||||
}))
|
||||
}
|
||||
crate::abstract_syntax_tree_types::Hover::Signature { .. } => Ok(None),
|
||||
crate::ast::types::Hover::Signature { .. } => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
@ -482,7 +484,7 @@ impl LanguageServer for Backend {
|
||||
};
|
||||
|
||||
match hover {
|
||||
crate::abstract_syntax_tree_types::Hover::Function { name, range: _ } => {
|
||||
crate::ast::types::Hover::Function { name, range: _ } => {
|
||||
// Get the docs for this function.
|
||||
let Some(signature) = self.stdlib_signatures.get(&name) else {
|
||||
return Ok(None);
|
||||
@ -490,7 +492,7 @@ impl LanguageServer for Backend {
|
||||
|
||||
Ok(Some(signature.clone()))
|
||||
}
|
||||
crate::abstract_syntax_tree_types::Hover::Signature {
|
||||
crate::ast::types::Hover::Signature {
|
||||
name,
|
||||
parameter_index,
|
||||
range: _,
|
||||
@ -554,7 +556,7 @@ impl LanguageServer for Backend {
|
||||
};
|
||||
// Now recast it.
|
||||
let recast = ast.recast(
|
||||
&crate::abstract_syntax_tree_types::FormatOptions {
|
||||
&crate::ast::types::FormatOptions {
|
||||
tab_size: params.options.tab_size as usize,
|
||||
insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
|
||||
use_tabs: !params.options.insert_spaces,
|
||||
|
@ -23,7 +23,7 @@ pub fn extrude(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "extrude"
|
||||
}]
|
||||
fn inner_extrude(length: f64, sketch_group: SketchGroup, args: &mut Args) -> Result<ExtrudeGroup, KclError> {
|
||||
fn inner_extrude(length: f64, sketch_group: Box<SketchGroup>, args: &mut Args) -> Result<Box<ExtrudeGroup>, KclError> {
|
||||
let id = uuid::Uuid::new_v4();
|
||||
|
||||
let cmd = kittycad::types::ModelingCmd::Extrude {
|
||||
@ -33,7 +33,7 @@ fn inner_extrude(length: f64, sketch_group: SketchGroup, args: &mut Args) -> Res
|
||||
};
|
||||
args.send_modeling_cmd(id, cmd)?;
|
||||
|
||||
Ok(ExtrudeGroup {
|
||||
Ok(Box::new(ExtrudeGroup {
|
||||
id,
|
||||
// TODO, this is just an empty array now, should be deleted. This
|
||||
// comment was originally in the JS code.
|
||||
@ -42,13 +42,13 @@ fn inner_extrude(length: f64, sketch_group: SketchGroup, args: &mut Args) -> Res
|
||||
position: sketch_group.position,
|
||||
rotation: sketch_group.rotation,
|
||||
meta: sketch_group.meta,
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
/// Returns the extrude wall transform.
|
||||
pub fn get_extrude_wall_transform(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let (surface_name, extrude_group) = args.get_path_name_extrude_group()?;
|
||||
let result = inner_get_extrude_wall_transform(&surface_name, extrude_group, args)?;
|
||||
let result = inner_get_extrude_wall_transform(&surface_name, *extrude_group, args)?;
|
||||
Ok(MemoryItem::ExtrudeTransform(result))
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,11 @@ use anyhow::Result;
|
||||
use derive_docs::stdlib;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use crate::{errors::KclError, executor::MemoryItem, std::Args};
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::MemoryItem,
|
||||
std::Args,
|
||||
};
|
||||
|
||||
/// Computes the cosine of a number (in radians).
|
||||
pub fn cos(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
@ -54,17 +58,320 @@ fn inner_tan(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.tan())
|
||||
}
|
||||
|
||||
/// Return the value of `pi`.
|
||||
/// Return the value of `pi`. Archimedes’ constant (π).
|
||||
pub fn pi(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let result = inner_pi()?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Return the value of `pi`.
|
||||
/// Return the value of `pi`. Archimedes’ constant (π).
|
||||
#[stdlib {
|
||||
name = "pi",
|
||||
}]
|
||||
fn inner_pi() -> Result<f64, KclError> {
|
||||
Ok(std::f64::consts::PI)
|
||||
}
|
||||
|
||||
/// Computes the square root of a number.
|
||||
pub fn sqrt(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let num = args.get_number()?;
|
||||
let result = inner_sqrt(num)?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the square root of a number.
|
||||
#[stdlib {
|
||||
name = "sqrt",
|
||||
}]
|
||||
fn inner_sqrt(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.sqrt())
|
||||
}
|
||||
|
||||
/// Computes the absolute value of a number.
|
||||
pub fn abs(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let num = args.get_number()?;
|
||||
let result = inner_abs(num)?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the absolute value of a number.
|
||||
#[stdlib {
|
||||
name = "abs",
|
||||
}]
|
||||
fn inner_abs(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.abs())
|
||||
}
|
||||
|
||||
/// Computes the largest integer less than or equal to a number.
|
||||
pub fn floor(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let num = args.get_number()?;
|
||||
let result = inner_floor(num)?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the largest integer less than or equal to a number.
|
||||
#[stdlib {
|
||||
name = "floor",
|
||||
}]
|
||||
fn inner_floor(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.floor())
|
||||
}
|
||||
|
||||
/// Computes the smallest integer greater than or equal to a number.
|
||||
pub fn ceil(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let num = args.get_number()?;
|
||||
let result = inner_ceil(num)?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the smallest integer greater than or equal to a number.
|
||||
#[stdlib {
|
||||
name = "ceil",
|
||||
}]
|
||||
fn inner_ceil(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.ceil())
|
||||
}
|
||||
|
||||
/// Computes the minimum of the given arguments.
|
||||
pub fn min(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let nums = args.get_number_array()?;
|
||||
let result = inner_min(nums);
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the minimum of the given arguments.
|
||||
#[stdlib {
|
||||
name = "min",
|
||||
}]
|
||||
fn inner_min(args: Vec<f64>) -> f64 {
|
||||
let mut min = std::f64::MAX;
|
||||
for arg in args.iter() {
|
||||
if *arg < min {
|
||||
min = *arg;
|
||||
}
|
||||
}
|
||||
|
||||
min
|
||||
}
|
||||
|
||||
/// Computes the maximum of the given arguments.
|
||||
pub fn max(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let nums = args.get_number_array()?;
|
||||
let result = inner_max(nums);
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the maximum of the given arguments.
|
||||
#[stdlib {
|
||||
name = "max",
|
||||
}]
|
||||
fn inner_max(args: Vec<f64>) -> f64 {
|
||||
let mut max = std::f64::MAX;
|
||||
for arg in args.iter() {
|
||||
if *arg > max {
|
||||
max = *arg;
|
||||
}
|
||||
}
|
||||
|
||||
max
|
||||
}
|
||||
|
||||
/// Computes the number to a power.
|
||||
pub fn pow(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let nums = args.get_number_array()?;
|
||||
if nums.len() > 2 {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!("expected 2 arguments, got {}", nums.len()),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
if nums.len() <= 1 {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!("expected 2 arguments, got {}", nums.len()),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
let result = inner_pow(nums[0], nums[1])?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the number to a power.
|
||||
#[stdlib {
|
||||
name = "pow",
|
||||
}]
|
||||
fn inner_pow(num: f64, pow: f64) -> Result<f64, KclError> {
|
||||
Ok(num.powf(pow))
|
||||
}
|
||||
|
||||
/// Computes the arccosine of a number (in radians).
|
||||
pub fn acos(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let num = args.get_number()?;
|
||||
let result = inner_acos(num)?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the arccosine of a number (in radians).
|
||||
#[stdlib {
|
||||
name = "acos",
|
||||
}]
|
||||
fn inner_acos(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.acos())
|
||||
}
|
||||
|
||||
/// Computes the arcsine of a number (in radians).
|
||||
pub fn asin(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let num = args.get_number()?;
|
||||
let result = inner_asin(num)?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the arcsine of a number (in radians).
|
||||
#[stdlib {
|
||||
name = "asin",
|
||||
}]
|
||||
fn inner_asin(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.asin())
|
||||
}
|
||||
|
||||
/// Computes the arctangent of a number (in radians).
|
||||
pub fn atan(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let num = args.get_number()?;
|
||||
let result = inner_atan(num)?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the arctangent of a number (in radians).
|
||||
#[stdlib {
|
||||
name = "atan",
|
||||
}]
|
||||
fn inner_atan(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.atan())
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub fn log(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let nums = args.get_number_array()?;
|
||||
if nums.len() > 2 {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!("expected 2 arguments, got {}", nums.len()),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
|
||||
if nums.len() <= 1 {
|
||||
return Err(KclError::Type(KclErrorDetails {
|
||||
message: format!("expected 2 arguments, got {}", nums.len()),
|
||||
source_ranges: vec![args.source_range],
|
||||
}));
|
||||
}
|
||||
let result = inner_log(nums[0], nums[1])?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// 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.
|
||||
#[stdlib {
|
||||
name = "log",
|
||||
}]
|
||||
fn inner_log(num: f64, base: f64) -> Result<f64, KclError> {
|
||||
Ok(num.log(base))
|
||||
}
|
||||
|
||||
/// Computes the base 2 logarithm of the number.
|
||||
pub fn log2(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let num = args.get_number()?;
|
||||
let result = inner_log2(num)?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the base 2 logarithm of the number.
|
||||
#[stdlib {
|
||||
name = "log2",
|
||||
}]
|
||||
fn inner_log2(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.log2())
|
||||
}
|
||||
|
||||
/// Computes the base 10 logarithm of the number.
|
||||
pub fn log10(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let num = args.get_number()?;
|
||||
let result = inner_log10(num)?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the base 10 logarithm of the number.
|
||||
#[stdlib {
|
||||
name = "log10",
|
||||
}]
|
||||
fn inner_log10(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.log10())
|
||||
}
|
||||
|
||||
/// Computes the natural logarithm of the number.
|
||||
pub fn ln(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let num = args.get_number()?;
|
||||
let result = inner_ln(num)?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Computes the natural logarithm of the number.
|
||||
#[stdlib {
|
||||
name = "ln",
|
||||
}]
|
||||
fn inner_ln(num: f64) -> Result<f64, KclError> {
|
||||
Ok(num.ln())
|
||||
}
|
||||
|
||||
/// Return the value of Euler’s number `e`.
|
||||
pub fn e(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let result = inner_e()?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Return the value of Euler’s number `e`.
|
||||
#[stdlib {
|
||||
name = "e",
|
||||
}]
|
||||
fn inner_e() -> Result<f64, KclError> {
|
||||
Ok(std::f64::consts::E)
|
||||
}
|
||||
|
||||
/// Return the value of `tau`. The full circle constant (τ). Equal to 2π.
|
||||
pub fn tau(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let result = inner_tau()?;
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Return the value of `tau`. The full circle constant (τ). Equal to 2π.
|
||||
#[stdlib {
|
||||
name = "tau",
|
||||
}]
|
||||
fn inner_tau() -> Result<f64, KclError> {
|
||||
Ok(std::f64::consts::TAU)
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
abstract_syntax_tree_types::parse_json_number_as_f64,
|
||||
engine::EngineConnection,
|
||||
ast::types::parse_json_number_as_f64,
|
||||
engine::{EngineConnection, EngineManager},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{ExtrudeGroup, MemoryItem, Metadata, SketchGroup, SourceRange},
|
||||
};
|
||||
@ -32,7 +32,6 @@ impl StdLib {
|
||||
pub fn new() -> Self {
|
||||
let internal_fns: Vec<Box<(dyn crate::docs::StdLibFn)>> = vec![
|
||||
Box::new(Show),
|
||||
Box::new(Min),
|
||||
Box::new(LegLen),
|
||||
Box::new(LegAngX),
|
||||
Box::new(LegAngY),
|
||||
@ -65,7 +64,23 @@ impl StdLib {
|
||||
Box::new(crate::std::math::Cos),
|
||||
Box::new(crate::std::math::Sin),
|
||||
Box::new(crate::std::math::Tan),
|
||||
Box::new(crate::std::math::Acos),
|
||||
Box::new(crate::std::math::Asin),
|
||||
Box::new(crate::std::math::Atan),
|
||||
Box::new(crate::std::math::Pi),
|
||||
Box::new(crate::std::math::E),
|
||||
Box::new(crate::std::math::Tau),
|
||||
Box::new(crate::std::math::Sqrt),
|
||||
Box::new(crate::std::math::Abs),
|
||||
Box::new(crate::std::math::Floor),
|
||||
Box::new(crate::std::math::Ceil),
|
||||
Box::new(crate::std::math::Min),
|
||||
Box::new(crate::std::math::Max),
|
||||
Box::new(crate::std::math::Pow),
|
||||
Box::new(crate::std::math::Log),
|
||||
Box::new(crate::std::math::Log2),
|
||||
Box::new(crate::std::math::Log10),
|
||||
Box::new(crate::std::math::Ln),
|
||||
];
|
||||
|
||||
let mut fns = HashMap::new();
|
||||
@ -164,7 +179,7 @@ impl<'a> Args<'a> {
|
||||
Ok((numbers[0], numbers[1]))
|
||||
}
|
||||
|
||||
fn get_segment_name_sketch_group(&self) -> Result<(String, SketchGroup), KclError> {
|
||||
fn get_segment_name_sketch_group(&self) -> Result<(String, Box<SketchGroup>), KclError> {
|
||||
// Iterate over our args, the first argument should be a UserVal with a string value.
|
||||
// The second argument should be a SketchGroup.
|
||||
let first_value = self
|
||||
@ -206,7 +221,7 @@ impl<'a> Args<'a> {
|
||||
Ok((segment_name, sketch_group))
|
||||
}
|
||||
|
||||
fn get_sketch_group(&self) -> Result<SketchGroup, KclError> {
|
||||
fn get_sketch_group(&self) -> Result<Box<SketchGroup>, KclError> {
|
||||
let first_value = self.args.first().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails {
|
||||
message: format!("Expected a SketchGroup as the first argument, found `{:?}`", self.args),
|
||||
@ -248,7 +263,7 @@ impl<'a> Args<'a> {
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn get_data_and_sketch_group<T: serde::de::DeserializeOwned>(&self) -> Result<(T, SketchGroup), KclError> {
|
||||
fn get_data_and_sketch_group<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Box<SketchGroup>), KclError> {
|
||||
let first_value = self
|
||||
.args
|
||||
.first()
|
||||
@ -286,7 +301,7 @@ impl<'a> Args<'a> {
|
||||
Ok((data, sketch_group))
|
||||
}
|
||||
|
||||
fn get_segment_name_to_number_sketch_group(&self) -> Result<(String, f64, SketchGroup), KclError> {
|
||||
fn get_segment_name_to_number_sketch_group(&self) -> Result<(String, f64, Box<SketchGroup>), KclError> {
|
||||
// Iterate over our args, the first argument should be a UserVal with a string value.
|
||||
// The second argument should be a number.
|
||||
// The third argument should be a SketchGroup.
|
||||
@ -342,7 +357,7 @@ impl<'a> Args<'a> {
|
||||
Ok((segment_name, to_number, sketch_group))
|
||||
}
|
||||
|
||||
fn get_number_sketch_group(&self) -> Result<(f64, SketchGroup), KclError> {
|
||||
fn get_number_sketch_group(&self) -> Result<(f64, Box<SketchGroup>), KclError> {
|
||||
// Iterate over our args, the first argument should be a number.
|
||||
// The second argument should be a SketchGroup.
|
||||
let first_value = self
|
||||
@ -377,7 +392,7 @@ impl<'a> Args<'a> {
|
||||
Ok((number, sketch_group))
|
||||
}
|
||||
|
||||
fn get_path_name_extrude_group(&self) -> Result<(String, ExtrudeGroup), KclError> {
|
||||
fn get_path_name_extrude_group(&self) -> Result<(String, Box<ExtrudeGroup>), KclError> {
|
||||
// Iterate over our args, the first argument should be a UserVal with a string value.
|
||||
// The second argument should be a ExtrudeGroup.
|
||||
let first_value = self
|
||||
@ -426,29 +441,6 @@ impl<'a> Args<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the minimum of the given arguments.
|
||||
pub fn min(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
let nums = args.get_number_array()?;
|
||||
let result = inner_min(nums);
|
||||
|
||||
args.make_user_val_from_f64(result)
|
||||
}
|
||||
|
||||
/// Returns the minimum of the given arguments.
|
||||
#[stdlib {
|
||||
name = "min",
|
||||
}]
|
||||
fn inner_min(args: Vec<f64>) -> f64 {
|
||||
let mut min = std::f64::MAX;
|
||||
for arg in args.iter() {
|
||||
if *arg < min {
|
||||
min = *arg;
|
||||
}
|
||||
}
|
||||
|
||||
min
|
||||
}
|
||||
|
||||
/// Render a model.
|
||||
// This never actually gets called so this is fine.
|
||||
pub fn show(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
@ -462,7 +454,7 @@ pub fn show(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
#[stdlib {
|
||||
name = "show",
|
||||
}]
|
||||
fn inner_show(_sketch: SketchGroup) {}
|
||||
fn inner_show(_sketch: Box<SketchGroup>) {}
|
||||
|
||||
/// Returns the length of the given leg.
|
||||
pub fn leg_length(args: &mut Args) -> Result<MemoryItem, KclError> {
|
||||
@ -526,9 +518,10 @@ pub enum Primitive {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::std::StdLib;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::std::StdLib;
|
||||
|
||||
#[test]
|
||||
fn test_generate_stdlib_markdown_docs() {
|
||||
let stdlib = StdLib::new();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user