Merge branch 'main' into pierremtb/issue1349
| @ -1,3 +1,3 @@ | ||||
| [codespell] | ||||
| ignore-words-list: crate,everytime,inout,co-ordinate,ot | ||||
| ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo | ||||
| skip: **/target,node_modules,build,**/Cargo.lock,./src-tauri/gen/schemas | ||||
|  | ||||
							
								
								
									
										29
									
								
								.github/workflows/announce_release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,29 +0,0 @@ | ||||
| name: Announce Release | ||||
|  | ||||
| on: | ||||
|   release: | ||||
|     types: [published] | ||||
|  | ||||
| jobs: | ||||
|   announce_release: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|     - name: Check out code | ||||
|       uses: actions/checkout@v4 | ||||
|  | ||||
|     - name: Set up Python | ||||
|       uses: actions/setup-python@v5 | ||||
|       with: | ||||
|         python-version: '3.x' | ||||
|  | ||||
|     - name: Install dependencies | ||||
|       run: | | ||||
|         python -m pip install --upgrade pip | ||||
|         pip install requests | ||||
|  | ||||
|     - name: Announce Release | ||||
|       env: | ||||
|         DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} | ||||
|         RELEASE_VERSION: ${{ github.event.release.tag_name }} | ||||
|         RELEASE_BODY: ${{ github.event.release.body}} | ||||
|       run: python public/announce_release.py | ||||
							
								
								
									
										40
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -86,8 +86,6 @@ jobs: | ||||
|  | ||||
|       - run: yarn test:nowatch | ||||
|  | ||||
|       - run: yarn test:cov | ||||
|  | ||||
|  | ||||
|   prepare-json-files: | ||||
|     runs-on: ubuntu-latest  # seperate job on Ubuntu for easy string manipulations (compared to Windows) | ||||
| @ -126,7 +124,7 @@ jobs: | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         os: [macos-latest, ubuntu-latest, windows-latest] | ||||
|         os: [macos-14, ubuntu-latest, windows-latest] | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|  | ||||
| @ -188,10 +186,10 @@ jobs: | ||||
|       - name: Fix format | ||||
|         run: yarn fmt | ||||
|  | ||||
|       - name: Install Universal target (MacOS only) | ||||
|         if: matrix.os == 'macos-latest' | ||||
|       - name: Install x86 target for Universal builds (MacOS only) | ||||
|         if: matrix.os == 'macos-14' | ||||
|         run: | | ||||
|           rustup target add aarch64-apple-darwin | ||||
|           rustup target add x86_64-apple-darwin | ||||
|  | ||||
|       - name: Prepare certificate and variables (Windows only) | ||||
|         if: ${{ matrix.os == 'windows-latest' && env.BUILD_RELEASE == 'true' }} | ||||
| @ -225,7 +223,7 @@ jobs: | ||||
|         with: | ||||
|           includeRelease: false | ||||
|           includeDebug: true | ||||
|           args: ${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }} | ||||
|           args: ${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }} | ||||
|  | ||||
|       - name: Build the app (release) and sign | ||||
|         uses: tauri-apps/tauri-action@v0 | ||||
| @ -241,12 +239,12 @@ jobs: | ||||
|           APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | ||||
|           TAURI_CONF_ARGS: "--config ${{ matrix.os == 'windows-latest' && 'src-tauri\\tauri.release.conf.json' || 'src-tauri/tauri.release.conf.json' }}" | ||||
|         with: | ||||
|           args: "${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }} ${{ env.TAURI_CONF_ARGS }}" | ||||
|           args: "${{ matrix.os == 'macos-14' && '--target universal-apple-darwin' || '' }} ${{ env.TAURI_CONF_ARGS }}" | ||||
|  | ||||
|       - uses: actions/upload-artifact@v3 | ||||
|         if: matrix.os != 'ubuntu-latest' | ||||
|         env: | ||||
|           PREFIX: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin' || 'src-tauri/target' }} | ||||
|           PREFIX: ${{ matrix.os == 'macos-14' && 'src-tauri/target/universal-apple-darwin' || 'src-tauri/target' }} | ||||
|           MODE: ${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }} | ||||
|         with: | ||||
|           path: "${{ env.PREFIX }}/${{ env.MODE }}/bundle/*/*" | ||||
| @ -371,3 +369,27 @@ jobs: | ||||
|         uses: softprops/action-gh-release@v1 | ||||
|         with: | ||||
|           files: 'artifact/*/Zoo*' | ||||
|            | ||||
|   announce_release: | ||||
|     needs: [publish-apps-release] | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Check out code | ||||
|         uses: actions/checkout@v4 | ||||
|            | ||||
|       - name: Set up Python | ||||
|         uses: actions/setup-python@v5 | ||||
|         with: | ||||
|           python-version: '3.x' | ||||
|    | ||||
|       - name: Install dependencies | ||||
|         run: | | ||||
|           python -m pip install --upgrade pip | ||||
|           pip install requests | ||||
|    | ||||
|       - name: Announce Release | ||||
|         env: | ||||
|           DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} | ||||
|           RELEASE_VERSION: ${{ github.event.release.tag_name }} | ||||
|           RELEASE_BODY: ${{ github.event.release.body}} | ||||
|         run: python public/announce_release.py | ||||
							
								
								
									
										2
									
								
								.github/workflows/playwright.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -79,7 +79,7 @@ jobs: | ||||
|  | ||||
|   playwright-macos: | ||||
|     timeout-minutes: 60 | ||||
|     runs-on: macos-latest | ||||
|     runs-on: macos-14 | ||||
|     needs: playwright-ubuntu | ||||
|     steps: | ||||
|     - uses: actions/checkout@v4 | ||||
|  | ||||
| @ -136,6 +136,11 @@ Before you submit a contribution PR to this repo, please ensure that: | ||||
| VERSION=x.y.z yarn run bump-jsons | ||||
| ``` | ||||
|  | ||||
| Alternatively you can try the experimental `make-release.sh` bash script that will create the branch with the updated json files for you. | ||||
| run `./make-release.sh` for a patch update | ||||
| run `./make-release.sh "minor"` for minor | ||||
| run `./make-release.sh "major"` for major | ||||
|  | ||||
| The PR may serve as a place to discuss the human-readable changelog and extra QA. A quick way of getting PR's merged since the last bump is to [use this PR filter](https://github.com/KittyCAD/modeling-app/pulls?q=is%3Apr+sort%3Aupdated-desc+is%3Amerged+), open up the browser console and past in the following | ||||
|  | ||||
| ```typescript | ||||
|  | ||||
							
								
								
									
										15696
									
								
								docs/kcl/std.json
									
									
									
									
									
								
							
							
						
						
							
								
								
									
										2915
									
								
								docs/kcl/std.md
									
									
									
									
									
								
							
							
						
						| Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 193 KiB | 
| Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 193 KiB | 
| Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 259 KiB | 
| Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 220 KiB | 
| Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 220 KiB | 
| Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 220 KiB | 
| Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 193 KiB | 
| @ -19,59 +19,59 @@ DATA; | ||||
| ); | ||||
| #4 = CARTESIAN_POINT('NONE', (0, 0, -0)); | ||||
| #5 = VERTEX_POINT('NONE', #4); | ||||
| #6 = CARTESIAN_POINT('NONE', (0, -0.0254, -0)); | ||||
| #6 = CARTESIAN_POINT('NONE', (0, -0.64516, -0)); | ||||
| #7 = VERTEX_POINT('NONE', #6); | ||||
| #8 = CARTESIAN_POINT('NONE', (0, -0.0254, 0.1016)); | ||||
| #8 = CARTESIAN_POINT('NONE', (0, -0.64516, 2.58064)); | ||||
| #9 = VERTEX_POINT('NONE', #8); | ||||
| #10 = CARTESIAN_POINT('NONE', (0, 0, 0.1016)); | ||||
| #10 = CARTESIAN_POINT('NONE', (0, 0, 2.58064)); | ||||
| #11 = VERTEX_POINT('NONE', #10); | ||||
| #12 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0)); | ||||
| #12 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0)); | ||||
| #13 = VERTEX_POINT('NONE', #12); | ||||
| #14 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, 0.1016)); | ||||
| #14 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, 2.58064)); | ||||
| #15 = VERTEX_POINT('NONE', #14); | ||||
| #16 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0)); | ||||
| #16 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0)); | ||||
| #17 = VERTEX_POINT('NONE', #16); | ||||
| #18 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, 0.1016)); | ||||
| #18 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, 2.58064)); | ||||
| #19 = VERTEX_POINT('NONE', #18); | ||||
| #20 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0)); | ||||
| #20 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0)); | ||||
| #21 = VERTEX_POINT('NONE', #20); | ||||
| #22 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, 0.1016)); | ||||
| #22 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, 2.58064)); | ||||
| #23 = VERTEX_POINT('NONE', #22); | ||||
| #24 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0)); | ||||
| #24 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0)); | ||||
| #25 = VERTEX_POINT('NONE', #24); | ||||
| #26 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, 0.1016)); | ||||
| #26 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, 2.58064)); | ||||
| #27 = VERTEX_POINT('NONE', #26); | ||||
| #28 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0)); | ||||
| #28 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0)); | ||||
| #29 = VERTEX_POINT('NONE', #28); | ||||
| #30 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, 0.1016)); | ||||
| #30 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, 2.58064)); | ||||
| #31 = VERTEX_POINT('NONE', #30); | ||||
| #32 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0)); | ||||
| #32 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0)); | ||||
| #33 = VERTEX_POINT('NONE', #32); | ||||
| #34 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, 0.1016)); | ||||
| #34 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, 2.58064)); | ||||
| #35 = VERTEX_POINT('NONE', #34); | ||||
| #36 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0)); | ||||
| #36 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0)); | ||||
| #37 = VERTEX_POINT('NONE', #36); | ||||
| #38 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, 0.1016)); | ||||
| #38 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, 2.58064)); | ||||
| #39 = VERTEX_POINT('NONE', #38); | ||||
| #40 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0)); | ||||
| #40 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0)); | ||||
| #41 = VERTEX_POINT('NONE', #40); | ||||
| #42 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, 0.1016)); | ||||
| #42 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, 2.58064)); | ||||
| #43 = VERTEX_POINT('NONE', #42); | ||||
| #44 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0)); | ||||
| #44 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0)); | ||||
| #45 = VERTEX_POINT('NONE', #44); | ||||
| #46 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, 0.1016)); | ||||
| #46 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, 2.58064)); | ||||
| #47 = VERTEX_POINT('NONE', #46); | ||||
| #48 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0)); | ||||
| #48 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0)); | ||||
| #49 = VERTEX_POINT('NONE', #48); | ||||
| #50 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, 0.1016)); | ||||
| #50 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, 2.58064)); | ||||
| #51 = VERTEX_POINT('NONE', #50); | ||||
| #52 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0)); | ||||
| #52 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0)); | ||||
| #53 = VERTEX_POINT('NONE', #52); | ||||
| #54 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, 0.1016)); | ||||
| #54 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, 2.58064)); | ||||
| #55 = VERTEX_POINT('NONE', #54); | ||||
| #56 = CARTESIAN_POINT('NONE', (0, 0.0254, -0)); | ||||
| #56 = CARTESIAN_POINT('NONE', (0, 0.64516, -0)); | ||||
| #57 = VERTEX_POINT('NONE', #56); | ||||
| #58 = CARTESIAN_POINT('NONE', (0, 0.0254, 0.1016)); | ||||
| #58 = CARTESIAN_POINT('NONE', (0, 0.64516, 2.58064)); | ||||
| #59 = VERTEX_POINT('NONE', #58); | ||||
| #60 = DIRECTION('NONE', (0, -1, 0)); | ||||
| #61 = VECTOR('NONE', #60, 1); | ||||
| @ -79,11 +79,11 @@ DATA; | ||||
| #63 = LINE('NONE', #62, #61); | ||||
| #64 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #65 = VECTOR('NONE', #64, 1); | ||||
| #66 = CARTESIAN_POINT('NONE', (0, -0.0254, -0)); | ||||
| #66 = CARTESIAN_POINT('NONE', (0, -0.64516, -0)); | ||||
| #67 = LINE('NONE', #66, #65); | ||||
| #68 = DIRECTION('NONE', (0, -1, 0)); | ||||
| #69 = VECTOR('NONE', #68, 1); | ||||
| #70 = CARTESIAN_POINT('NONE', (0, 0, 0.1016)); | ||||
| #70 = CARTESIAN_POINT('NONE', (0, 0, 2.58064)); | ||||
| #71 = LINE('NONE', #70, #69); | ||||
| #72 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #73 = VECTOR('NONE', #72, 1); | ||||
| @ -91,155 +91,155 @@ DATA; | ||||
| #75 = LINE('NONE', #74, #73); | ||||
| #76 = DIRECTION('NONE', (1, 0, 0)); | ||||
| #77 = VECTOR('NONE', #76, 1); | ||||
| #78 = CARTESIAN_POINT('NONE', (0, -0.0254, -0)); | ||||
| #78 = CARTESIAN_POINT('NONE', (0, -0.64516, -0)); | ||||
| #79 = LINE('NONE', #78, #77); | ||||
| #80 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #81 = VECTOR('NONE', #80, 1); | ||||
| #82 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0)); | ||||
| #82 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0)); | ||||
| #83 = LINE('NONE', #82, #81); | ||||
| #84 = DIRECTION('NONE', (1, 0, 0)); | ||||
| #85 = VECTOR('NONE', #84, 1); | ||||
| #86 = CARTESIAN_POINT('NONE', (0, -0.0254, 0.1016)); | ||||
| #86 = CARTESIAN_POINT('NONE', (0, -0.64516, 2.58064)); | ||||
| #87 = LINE('NONE', #86, #85); | ||||
| #88 = DIRECTION('NONE', (0.8191520442889919, -0.5735764363510459, 0)); | ||||
| #88 = DIRECTION('NONE', (0.819152044288992, -0.5735764363510459, 0)); | ||||
| #89 = VECTOR('NONE', #88, 1); | ||||
| #90 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0)); | ||||
| #90 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, -0)); | ||||
| #91 = LINE('NONE', #90, #89); | ||||
| #92 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #93 = VECTOR('NONE', #92, 1); | ||||
| #94 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0)); | ||||
| #94 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0)); | ||||
| #95 = LINE('NONE', #94, #93); | ||||
| #96 = DIRECTION('NONE', (0.8191520442889919, -0.5735764363510459, 0)); | ||||
| #96 = DIRECTION('NONE', (0.819152044288992, -0.5735764363510459, 0)); | ||||
| #97 = VECTOR('NONE', #96, 1); | ||||
| #98 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, 0.1016)); | ||||
| #98 = CARTESIAN_POINT('NONE', (1.996782122555674, -0.64516, 2.58064)); | ||||
| #99 = LINE('NONE', #98, #97); | ||||
| #100 = DIRECTION('NONE', (1, -0.0000000000000003079278779307945, 0)); | ||||
| #100 = DIRECTION('NONE', (1, -0.00000000000000038794063361359933, 0)); | ||||
| #101 = VECTOR('NONE', #100, 1); | ||||
| #102 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0)); | ||||
| #102 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, -0)); | ||||
| #103 = LINE('NONE', #102, #101); | ||||
| #104 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #105 = VECTOR('NONE', #104, 1); | ||||
| #106 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0)); | ||||
| #106 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0)); | ||||
| #107 = LINE('NONE', #106, #105); | ||||
| #108 = DIRECTION('NONE', (1, -0.0000000000000003079278779307945, 0)); | ||||
| #108 = DIRECTION('NONE', (1, -0.00000000000000038794063361359933, 0)); | ||||
| #109 = VECTOR('NONE', #108, 1); | ||||
| #110 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, 0.1016)); | ||||
| #110 = CARTESIAN_POINT('NONE', (3.839550058615159, -1.9354799999999992, 2.58064)); | ||||
| #111 = LINE('NONE', #110, #109); | ||||
| #112 = DIRECTION('NONE', (0, 1, 0)); | ||||
| #113 = VECTOR('NONE', #112, 1); | ||||
| #114 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0)); | ||||
| #114 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, -0)); | ||||
| #115 = LINE('NONE', #114, #113); | ||||
| #116 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #117 = VECTOR('NONE', #116, 1); | ||||
| #118 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0)); | ||||
| #118 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0)); | ||||
| #119 = LINE('NONE', #118, #117); | ||||
| #120 = DIRECTION('NONE', (0, 1, 0)); | ||||
| #121 = VECTOR('NONE', #120, 1); | ||||
| #122 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, 0.1016)); | ||||
| #122 = CARTESIAN_POINT('NONE', (6.12902, -1.93548, 2.58064)); | ||||
| #123 = LINE('NONE', #122, #121); | ||||
| #124 = DIRECTION('NONE', (-1, 0, 0)); | ||||
| #125 = VECTOR('NONE', #124, 1); | ||||
| #126 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0)); | ||||
| #126 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, -0)); | ||||
| #127 = LINE('NONE', #126, #125); | ||||
| #128 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #129 = VECTOR('NONE', #128, 1); | ||||
| #130 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0)); | ||||
| #130 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0)); | ||||
| #131 = LINE('NONE', #130, #129); | ||||
| #132 = DIRECTION('NONE', (-1, 0, 0)); | ||||
| #133 = VECTOR('NONE', #132, 1); | ||||
| #134 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, 0.1016)); | ||||
| #134 = CARTESIAN_POINT('NONE', (6.12902, -1.6129, 2.58064)); | ||||
| #135 = LINE('NONE', #134, #133); | ||||
| #136 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0)); | ||||
| #137 = VECTOR('NONE', #136, 1); | ||||
| #138 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0)); | ||||
| #138 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, -0)); | ||||
| #139 = LINE('NONE', #138, #137); | ||||
| #140 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #141 = VECTOR('NONE', #140, 1); | ||||
| #142 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0)); | ||||
| #142 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0)); | ||||
| #143 = LINE('NONE', #142, #141); | ||||
| #144 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0)); | ||||
| #145 = VECTOR('NONE', #144, 1); | ||||
| #146 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, 0.1016)); | ||||
| #146 = CARTESIAN_POINT('NONE', (3.9412591419317424, -1.6129, 2.58064)); | ||||
| #147 = LINE('NONE', #146, #145); | ||||
| #148 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406993, 0)); | ||||
| #148 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406992, 0)); | ||||
| #149 = VECTOR('NONE', #148, 1); | ||||
| #150 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0)); | ||||
| #150 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, -0)); | ||||
| #151 = LINE('NONE', #150, #149); | ||||
| #152 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #153 = VECTOR('NONE', #152, 1); | ||||
| #154 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0)); | ||||
| #154 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0)); | ||||
| #155 = LINE('NONE', #154, #153); | ||||
| #156 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406993, 0)); | ||||
| #156 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406992, 0)); | ||||
| #157 = VECTOR('NONE', #156, 1); | ||||
| #158 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, 0.1016)); | ||||
| #158 = CARTESIAN_POINT('NONE', (1.6377992218573856, 0, 2.58064)); | ||||
| #159 = LINE('NONE', #158, #157); | ||||
| #160 = DIRECTION('NONE', (1, -0.00000000000000007295344279228718, 0)); | ||||
| #160 = DIRECTION('NONE', (1, -0.0000000000000001378647737807002, 0)); | ||||
| #161 = VECTOR('NONE', #160, 1); | ||||
| #162 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0)); | ||||
| #162 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, -0)); | ||||
| #163 = LINE('NONE', #162, #161); | ||||
| #164 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #165 = VECTOR('NONE', #164, 1); | ||||
| #166 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0)); | ||||
| #166 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0)); | ||||
| #167 = LINE('NONE', #166, #165); | ||||
| #168 = DIRECTION('NONE', (1, -0.00000000000000007295344279228718, 0)); | ||||
| #168 = DIRECTION('NONE', (1, -0.0000000000000001378647737807002, 0)); | ||||
| #169 = VECTOR('NONE', #168, 1); | ||||
| #170 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, 0.1016)); | ||||
| #170 = CARTESIAN_POINT('NONE', (3.7131243491113075, 0.9677400000000002, 2.58064)); | ||||
| #171 = LINE('NONE', #170, #169); | ||||
| #172 = DIRECTION('NONE', (0, 1, 0)); | ||||
| #173 = VECTOR('NONE', #172, 1); | ||||
| #174 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0)); | ||||
| #174 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, -0)); | ||||
| #175 = LINE('NONE', #174, #173); | ||||
| #176 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #177 = VECTOR('NONE', #176, 1); | ||||
| #178 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0)); | ||||
| #178 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0)); | ||||
| #179 = LINE('NONE', #178, #177); | ||||
| #180 = DIRECTION('NONE', (0, 1, 0)); | ||||
| #181 = VECTOR('NONE', #180, 1); | ||||
| #182 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, 0.1016)); | ||||
| #182 = CARTESIAN_POINT('NONE', (6.12902, 0.9677399999999998, 2.58064)); | ||||
| #183 = LINE('NONE', #182, #181); | ||||
| #184 = DIRECTION('NONE', (-1, 0, 0)); | ||||
| #185 = VECTOR('NONE', #184, 1); | ||||
| #186 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0)); | ||||
| #186 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, -0)); | ||||
| #187 = LINE('NONE', #186, #185); | ||||
| #188 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #189 = VECTOR('NONE', #188, 1); | ||||
| #190 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0)); | ||||
| #190 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0)); | ||||
| #191 = LINE('NONE', #190, #189); | ||||
| #192 = DIRECTION('NONE', (-1, 0, 0)); | ||||
| #193 = VECTOR('NONE', #192, 1); | ||||
| #194 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, 0.1016)); | ||||
| #194 = CARTESIAN_POINT('NONE', (6.12902, 1.29032, 2.58064)); | ||||
| #195 = LINE('NONE', #194, #193); | ||||
| #196 = DIRECTION('NONE', (-0.90630778703665, -0.42261826174069944, 0)); | ||||
| #196 = DIRECTION('NONE', (-0.90630778703665, -0.4226182617406995, 0)); | ||||
| #197 = VECTOR('NONE', #196, 1); | ||||
| #198 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0)); | ||||
| #198 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, -0)); | ||||
| #199 = LINE('NONE', #198, #197); | ||||
| #200 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #201 = VECTOR('NONE', #200, 1); | ||||
| #202 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0)); | ||||
| #202 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0)); | ||||
| #203 = LINE('NONE', #202, #201); | ||||
| #204 = DIRECTION('NONE', (-0.90630778703665, -0.42261826174069944, 0)); | ||||
| #204 = DIRECTION('NONE', (-0.90630778703665, -0.4226182617406995, 0)); | ||||
| #205 = VECTOR('NONE', #204, 1); | ||||
| #206 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, 0.1016)); | ||||
| #206 = CARTESIAN_POINT('NONE', (3.6416100848359463, 1.29032, 2.58064)); | ||||
| #207 = LINE('NONE', #206, #205); | ||||
| #208 = DIRECTION('NONE', (-1, 0, 0)); | ||||
| #209 = VECTOR('NONE', #208, 1); | ||||
| #210 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0)); | ||||
| #210 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, -0)); | ||||
| #211 = LINE('NONE', #210, #209); | ||||
| #212 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #213 = VECTOR('NONE', #212, 1); | ||||
| #214 = CARTESIAN_POINT('NONE', (0, 0.0254, -0)); | ||||
| #214 = CARTESIAN_POINT('NONE', (0, 0.64516, -0)); | ||||
| #215 = LINE('NONE', #214, #213); | ||||
| #216 = DIRECTION('NONE', (-1, 0, 0)); | ||||
| #217 = VECTOR('NONE', #216, 1); | ||||
| #218 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, 0.1016)); | ||||
| #218 = CARTESIAN_POINT('NONE', (2.2580599999999995, 0.64516, 2.58064)); | ||||
| #219 = LINE('NONE', #218, #217); | ||||
| #220 = DIRECTION('NONE', (0, -1, 0)); | ||||
| #221 = VECTOR('NONE', #220, 1); | ||||
| #222 = CARTESIAN_POINT('NONE', (0, 0.0254, -0)); | ||||
| #222 = CARTESIAN_POINT('NONE', (0, 0.64516, -0)); | ||||
| #223 = LINE('NONE', #222, #221); | ||||
| #224 = DIRECTION('NONE', (0, -1, 0)); | ||||
| #225 = VECTOR('NONE', #224, 1); | ||||
| #226 = CARTESIAN_POINT('NONE', (0, 0.0254, 0.1016)); | ||||
| #226 = CARTESIAN_POINT('NONE', (0, 0.64516, 2.58064)); | ||||
| #227 = LINE('NONE', #226, #225); | ||||
| #228 = EDGE_CURVE('NONE', #5, #7, #63, .T.); | ||||
| #229 = EDGE_CURVE('NONE', #7, #9, #67, .T.); | ||||
| @ -383,59 +383,59 @@ DATA; | ||||
| #367 = ORIENTED_EDGE('NONE', *, *, #267, .T.); | ||||
| #368 = ORIENTED_EDGE('NONE', *, *, #269, .T.); | ||||
| #369 = EDGE_LOOP('NONE', (#355, #356, #357, #358, #359, #360, #361, #362, #363, #364, #365, #366, #367, #368)); | ||||
| #370 = CARTESIAN_POINT('NONE', (0, -0.0127, 0.0508)); | ||||
| #370 = CARTESIAN_POINT('NONE', (0, -0.3225799999999985, 1.2903199999999995)); | ||||
| #371 = DIRECTION('NONE', (-1, -0, 0)); | ||||
| #372 = AXIS2_PLACEMENT_3D('NONE', #370, #371, $); | ||||
| #373 = PLANE('NONE', #372); | ||||
| #374 = CARTESIAN_POINT('NONE', (0.039306734695977924, -0.025399999999999995, 0.0508)); | ||||
| #374 = CARTESIAN_POINT('NONE', (0.9983910612778368, -0.6451599999999998, 1.2903199999999997)); | ||||
| #375 = DIRECTION('NONE', (0, -1, 0)); | ||||
| #376 = AXIS2_PLACEMENT_3D('NONE', #374, #375, $); | ||||
| #377 = PLANE('NONE', #376); | ||||
| #378 = CARTESIAN_POINT('NONE', (0.11488842876320533, -0.05079999999999996, 0.05079999999999999)); | ||||
| #378 = CARTESIAN_POINT('NONE', (2.918166090585415, -1.2903199999999988, 1.2903199999999997)); | ||||
| #379 = DIRECTION('NONE', (-0.5735764363510459, -0.8191520442889919, 0)); | ||||
| #380 = AXIS2_PLACEMENT_3D('NONE', #378, #379, $); | ||||
| #381 = PLANE('NONE', #380); | ||||
| #382 = CARTESIAN_POINT('NONE', (0.19623169406722757, -0.07619999999999999, 0.0508)); | ||||
| #382 = CARTESIAN_POINT('NONE', (4.984285029307579, -1.9354799999999992, 1.2903199999999997)); | ||||
| #383 = DIRECTION('NONE', (0, -1, 0)); | ||||
| #384 = AXIS2_PLACEMENT_3D('NONE', #382, #383, $); | ||||
| #385 = PLANE('NONE', #384); | ||||
| #386 = CARTESIAN_POINT('NONE', (0.2413, -0.06985, 0.0508)); | ||||
| #386 = CARTESIAN_POINT('NONE', (6.129019999999999, -1.7741899999999997, 1.2903199999999997)); | ||||
| #387 = DIRECTION('NONE', (1, -0, 0)); | ||||
| #388 = AXIS2_PLACEMENT_3D('NONE', #386, #387, $); | ||||
| #389 = PLANE('NONE', #388); | ||||
| #390 = CARTESIAN_POINT('NONE', (0.19823384137660915, -0.0635, 0.0508)); | ||||
| #390 = CARTESIAN_POINT('NONE', (5.035139570965871, -1.6128999999999998, 1.2903199999999997)); | ||||
| #391 = DIRECTION('NONE', (0, 1, -0)); | ||||
| #392 = AXIS2_PLACEMENT_3D('NONE', #390, #391, $); | ||||
| #393 = PLANE('NONE', #392); | ||||
| #394 = CARTESIAN_POINT('NONE', (0.10982398353915601, -0.03174999999999997, 0.0508)); | ||||
| #395 = DIRECTION('NONE', (0.5735764363510459, 0.8191520442889917, -0)); | ||||
| #394 = CARTESIAN_POINT('NONE', (2.7895291818945633, -0.8064499999999998, 1.2903199999999995)); | ||||
| #395 = DIRECTION('NONE', (0.5735764363510459, 0.8191520442889918, -0)); | ||||
| #396 = AXIS2_PLACEMENT_3D('NONE', #394, #395, $); | ||||
| #397 = PLANE('NONE', #396); | ||||
| #398 = CARTESIAN_POINT('NONE', (0.105333141160801, 0.019049999999999987, 0.0508)); | ||||
| #399 = DIRECTION('NONE', (0.4226182617406993, -0.90630778703665, 0)); | ||||
| #398 = CARTESIAN_POINT('NONE', (2.6754617854843468, 0.4838700000000003, 1.2903199999999997)); | ||||
| #399 = DIRECTION('NONE', (0.4226182617406992, -0.90630778703665, 0)); | ||||
| #400 = AXIS2_PLACEMENT_3D('NONE', #398, #399, $); | ||||
| #401 = PLANE('NONE', #400); | ||||
| #402 = CARTESIAN_POINT('NONE', (0.19374299899825406, 0.0381, 0.0508)); | ||||
| #402 = CARTESIAN_POINT('NONE', (4.921072174555653, 0.9677399999999998, 1.2903199999999995)); | ||||
| #403 = DIRECTION('NONE', (0, -1, 0)); | ||||
| #404 = AXIS2_PLACEMENT_3D('NONE', #402, #403, $); | ||||
| #405 = PLANE('NONE', #404); | ||||
| #406 = CARTESIAN_POINT('NONE', (0.2413, 0.044449999999999996, 0.0508)); | ||||
| #406 = CARTESIAN_POINT('NONE', (6.129019999999998, 1.1290299999999989, 1.2903199999999995)); | ||||
| #407 = DIRECTION('NONE', (1, -0, 0)); | ||||
| #408 = AXIS2_PLACEMENT_3D('NONE', #406, #407, $); | ||||
| #409 = PLANE('NONE', #408); | ||||
| #410 = CARTESIAN_POINT('NONE', (0.19233523789047138, 0.0508, 0.0508)); | ||||
| #410 = CARTESIAN_POINT('NONE', (4.8853150424179725, 1.2903199999999997, 1.2903199999999997)); | ||||
| #411 = DIRECTION('NONE', (0, 1, -0)); | ||||
| #412 = AXIS2_PLACEMENT_3D('NONE', #410, #411, $); | ||||
| #413 = PLANE('NONE', #412); | ||||
| #414 = CARTESIAN_POINT('NONE', (0.11613523789047137, 0.0381, 0.05079999999999999)); | ||||
| #415 = DIRECTION('NONE', (-0.42261826174069966, 0.90630778703665, -0)); | ||||
| #414 = CARTESIAN_POINT('NONE', (2.9498350424179733, 0.9677399999999998, 1.2903199999999997)); | ||||
| #415 = DIRECTION('NONE', (-0.42261826174069933, 0.9063077870366499, -0)); | ||||
| #416 = AXIS2_PLACEMENT_3D('NONE', #414, #415, $); | ||||
| #417 = PLANE('NONE', #416); | ||||
| #418 = CARTESIAN_POINT('NONE', (0.044449999999999996, 0.0254, 0.0508)); | ||||
| #418 = CARTESIAN_POINT('NONE', (1.1290299999999998, 0.6451599999999998, 1.29032)); | ||||
| #419 = DIRECTION('NONE', (0, 1, -0)); | ||||
| #420 = AXIS2_PLACEMENT_3D('NONE', #418, #419, $); | ||||
| #421 = PLANE('NONE', #420); | ||||
| #422 = CARTESIAN_POINT('NONE', (0, 0.0127, 0.0508)); | ||||
| #422 = CARTESIAN_POINT('NONE', (0, 0.32257999999999987, 1.2903199999999995)); | ||||
| #423 = DIRECTION('NONE', (-1, -0, 0)); | ||||
| #424 = AXIS2_PLACEMENT_3D('NONE', #422, #423, $); | ||||
| #425 = PLANE('NONE', #424); | ||||
| @ -443,7 +443,7 @@ DATA; | ||||
| #427 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #428 = AXIS2_PLACEMENT_3D('NONE', #426, #427, $); | ||||
| #429 = PLANE('NONE', #428); | ||||
| #430 = CARTESIAN_POINT('NONE', (0, 0, 0.1016)); | ||||
| #430 = CARTESIAN_POINT('NONE', (0, 0, 2.58064)); | ||||
| #431 = DIRECTION('NONE', (0, 0, 1)); | ||||
| #432 = AXIS2_PLACEMENT_3D('NONE', #430, #431, $); | ||||
| #433 = PLANE('NONE', #432); | ||||
|  | ||||
| Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 221 KiB | 
| @ -1,478 +1,478 @@ | ||||
| solid unnamed | ||||
| facet normal -1 0 0 | ||||
|     outer loop | ||||
|         vertex 0 -4 0 | ||||
|         vertex 0 -101.600006 0 | ||||
|         vertex 0 -0 0 | ||||
|         vertex 0 -4 -1 | ||||
|         vertex 0 -101.600006 -25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -1 0 0 | ||||
|     outer loop | ||||
|         vertex 0 -4 -1 | ||||
|         vertex 0 -101.600006 -25.400002 | ||||
|         vertex 0 -0 0 | ||||
|         vertex 0 -0 -1 | ||||
|         vertex 0 -0 -25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 -1 | ||||
|     outer loop | ||||
|         vertex 0 -4 -1 | ||||
|         vertex 0 -0 -1 | ||||
|         vertex 3.0950184 -4 -1 | ||||
|         vertex 0 -101.600006 -25.400002 | ||||
|         vertex 0 -0 -25.400002 | ||||
|         vertex 78.613464 -101.600006 -25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 -1 | ||||
|     outer loop | ||||
|         vertex 3.0950184 -4 -1 | ||||
|         vertex 0 -0 -1 | ||||
|         vertex 3.0950184 -0 -1 | ||||
|         vertex 78.613464 -101.600006 -25.400002 | ||||
|         vertex 0 -0 -25.400002 | ||||
|         vertex 78.613464 -0 -25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -0.57357645 0 -0.81915206 | ||||
| facet normal -0.5735764 0 -0.8191522 | ||||
|     outer loop | ||||
|         vertex 3.0950184 -4 -1 | ||||
|         vertex 3.0950184 -0 -1 | ||||
|         vertex 5.9513144 -4 -3 | ||||
|         vertex 78.613464 -101.600006 -25.400002 | ||||
|         vertex 78.613464 -0 -25.400002 | ||||
|         vertex 151.16339 -101.600006 -76.2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -0.57357645 0 -0.81915206 | ||||
| facet normal -0.5735764 0 -0.8191522 | ||||
|     outer loop | ||||
|         vertex 5.9513144 -4 -3 | ||||
|         vertex 3.0950184 -0 -1 | ||||
|         vertex 5.9513144 -0 -3 | ||||
|         vertex 151.16339 -101.600006 -76.2 | ||||
|         vertex 78.613464 -0 -25.400002 | ||||
|         vertex 151.16339 -0 -76.2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 -1 | ||||
|     outer loop | ||||
|         vertex 5.9513144 -4 -3 | ||||
|         vertex 5.9513144 -0 -3 | ||||
|         vertex 9.5 -4 -3 | ||||
|         vertex 151.16339 -101.600006 -76.2 | ||||
|         vertex 151.16339 -0 -76.2 | ||||
|         vertex 241.3 -101.600006 -76.2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 -1 | ||||
|     outer loop | ||||
|         vertex 9.5 -4 -3 | ||||
|         vertex 5.9513144 -0 -3 | ||||
|         vertex 9.5 -0 -3 | ||||
|         vertex 241.3 -101.600006 -76.2 | ||||
|         vertex 151.16339 -0 -76.2 | ||||
|         vertex 241.3 -0 -76.2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 1 0 0 | ||||
|     outer loop | ||||
|         vertex 9.5 -4 -3 | ||||
|         vertex 9.5 -0 -3 | ||||
|         vertex 9.5 -4 -2.5 | ||||
|         vertex 241.3 -101.600006 -76.2 | ||||
|         vertex 241.3 -0 -76.2 | ||||
|         vertex 241.3 -101.600006 -63.5 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 1 -0 0 | ||||
|     outer loop | ||||
|         vertex 9.5 -4 -2.5 | ||||
|         vertex 9.5 -0 -3 | ||||
|         vertex 9.5 -0 -2.5 | ||||
|         vertex 241.3 -101.600006 -63.5 | ||||
|         vertex 241.3 -0 -76.2 | ||||
|         vertex 241.3 -0 -63.5 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -0 1 | ||||
|     outer loop | ||||
|         vertex 241.3 -101.600006 -63.5 | ||||
|         vertex 241.3 -0 -63.5 | ||||
|         vertex 155.16768 -101.600006 -63.5 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 1 | ||||
|     outer loop | ||||
|         vertex 155.16768 -101.600006 -63.5 | ||||
|         vertex 241.3 -0 -63.5 | ||||
|         vertex 155.16768 -0 -63.5 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.5735765 0 0.81915194 | ||||
|     outer loop | ||||
|         vertex 87.15214 -101.600006 -15.875 | ||||
|         vertex 109.82398 -101.600006 -31.75 | ||||
|         vertex 109.82398 -0 -31.75 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.57357645 0 0.819152 | ||||
|     outer loop | ||||
|         vertex 109.82398 -101.600006 -31.75 | ||||
|         vertex 155.16768 -101.600006 -63.5 | ||||
|         vertex 155.16768 -0 -63.5 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.57357645 0 0.81915206 | ||||
|     outer loop | ||||
|         vertex 87.15214 -0 -15.875 | ||||
|         vertex 64.480286 -101.600006 0 | ||||
|         vertex 87.15214 -101.600006 -15.875 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.5735765 0 0.81915194 | ||||
|     outer loop | ||||
|         vertex 109.82398 -0 -31.75 | ||||
|         vertex 87.15214 -0 -15.875 | ||||
|         vertex 87.15214 -101.600006 -15.875 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.57357645 -0 0.819152 | ||||
|     outer loop | ||||
|         vertex 109.82398 -101.600006 -31.75 | ||||
|         vertex 155.16768 -0 -63.5 | ||||
|         vertex 109.82398 -0 -31.75 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.57357645 -0 0.81915206 | ||||
|     outer loop | ||||
|         vertex 64.480286 -101.600006 0 | ||||
|         vertex 87.15214 -0 -15.875 | ||||
|         vertex 64.480286 -0 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.4226182 0 -0.9063078 | ||||
|     outer loop | ||||
|         vertex 84.906715 -101.600006 9.525 | ||||
|         vertex 64.480286 -101.600006 0 | ||||
|         vertex 64.480286 -0 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.42261833 0 -0.90630776 | ||||
|     outer loop | ||||
|         vertex 105.33314 -101.600006 19.05 | ||||
|         vertex 84.906715 -101.600006 9.525 | ||||
|         vertex 84.906715 -0 9.525 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.4226182 0 -0.9063078 | ||||
|     outer loop | ||||
|         vertex 84.906715 -0 9.525 | ||||
|         vertex 84.906715 -101.600006 9.525 | ||||
|         vertex 64.480286 -0 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.4226183 0 -0.9063078 | ||||
|     outer loop | ||||
|         vertex 105.33314 -0 19.05 | ||||
|         vertex 146.18599 -101.600006 38.1 | ||||
|         vertex 105.33314 -101.600006 19.05 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.42261833 0 -0.90630776 | ||||
|     outer loop | ||||
|         vertex 105.33314 -101.600006 19.05 | ||||
|         vertex 84.906715 -0 9.525 | ||||
|         vertex 105.33314 -0 19.05 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.4226183 0 -0.9063078 | ||||
|     outer loop | ||||
|         vertex 146.18599 -101.600006 38.1 | ||||
|         vertex 105.33314 -0 19.05 | ||||
|         vertex 146.18599 -0 38.1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 -1 | ||||
|     outer loop | ||||
|         vertex 146.18599 -101.600006 38.1 | ||||
|         vertex 146.18599 -0 38.1 | ||||
|         vertex 241.3 -101.600006 38.1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 -1 | ||||
|     outer loop | ||||
|         vertex 241.3 -101.600006 38.1 | ||||
|         vertex 146.18599 -0 38.1 | ||||
|         vertex 241.3 -0 38.1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 1 0 0 | ||||
|     outer loop | ||||
|         vertex 241.3 -101.600006 38.1 | ||||
|         vertex 241.3 -0 38.1 | ||||
|         vertex 241.3 -101.600006 50.800003 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 1 -0 0 | ||||
|     outer loop | ||||
|         vertex 241.3 -101.600006 50.800003 | ||||
|         vertex 241.3 -0 38.1 | ||||
|         vertex 241.3 -0 50.800003 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -0 0.99999994 | ||||
|     outer loop | ||||
|         vertex 9.5 -4 -2.5 | ||||
|         vertex 9.5 -0 -2.5 | ||||
|         vertex 6.108964 -4 -2.5 | ||||
|         vertex 241.3 -101.600006 50.800003 | ||||
|         vertex 241.3 -0 50.800003 | ||||
|         vertex 143.37048 -101.600006 50.800003 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 0.99999994 | ||||
|     outer loop | ||||
|         vertex 6.108964 -4 -2.5 | ||||
|         vertex 9.5 -0 -2.5 | ||||
|         vertex 6.108964 -0 -2.5 | ||||
|         vertex 143.37048 -101.600006 50.800003 | ||||
|         vertex 241.3 -0 50.800003 | ||||
|         vertex 143.37048 -0 50.800003 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.5735763 0 0.8191522 | ||||
| facet normal -0.42261827 0 0.9063078 | ||||
|     outer loop | ||||
|         vertex 3.4311862 -4 -0.625 | ||||
|         vertex 4.323779 -4 -1.25 | ||||
|         vertex 4.323779 -0 -1.25 | ||||
|         vertex 143.37048 -101.600006 50.800003 | ||||
|         vertex 143.37048 -0 50.800003 | ||||
|         vertex 88.9 -101.600006 25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.57357645 0 0.819152 | ||||
| facet normal -0.42261827 0 0.9063078 | ||||
|     outer loop | ||||
|         vertex 4.323779 -4 -1.25 | ||||
|         vertex 6.108964 -4 -2.5 | ||||
|         vertex 6.108964 -0 -2.5 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.57357645 0 0.819152 | ||||
|     outer loop | ||||
|         vertex 3.4311862 -0 -0.625 | ||||
|         vertex 2.5385938 -4 0 | ||||
|         vertex 3.4311862 -4 -0.625 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.5735763 0 0.8191522 | ||||
|     outer loop | ||||
|         vertex 4.323779 -0 -1.25 | ||||
|         vertex 3.4311862 -0 -0.625 | ||||
|         vertex 3.4311862 -4 -0.625 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.57357645 -0 0.819152 | ||||
|     outer loop | ||||
|         vertex 4.323779 -4 -1.25 | ||||
|         vertex 6.108964 -0 -2.5 | ||||
|         vertex 4.323779 -0 -1.25 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.57357645 -0 0.819152 | ||||
|     outer loop | ||||
|         vertex 2.5385938 -4 0 | ||||
|         vertex 3.4311862 -0 -0.625 | ||||
|         vertex 2.5385938 -0 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.42261824 0 -0.9063078 | ||||
|     outer loop | ||||
|         vertex 3.342784 -4 0.375 | ||||
|         vertex 2.5385938 -4 0 | ||||
|         vertex 2.5385938 -0 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.42261824 0 -0.9063078 | ||||
|     outer loop | ||||
|         vertex 4.146974 -4 0.75 | ||||
|         vertex 3.342784 -4 0.375 | ||||
|         vertex 3.342784 -0 0.375 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.42261824 0 -0.9063078 | ||||
|     outer loop | ||||
|         vertex 3.342784 -0 0.375 | ||||
|         vertex 3.342784 -4 0.375 | ||||
|         vertex 2.5385938 -0 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.42261833 0 -0.90630776 | ||||
|     outer loop | ||||
|         vertex 4.146974 -0 0.75 | ||||
|         vertex 5.755354 -4 1.5 | ||||
|         vertex 4.146974 -4 0.75 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.42261824 0 -0.9063078 | ||||
|     outer loop | ||||
|         vertex 4.146974 -4 0.75 | ||||
|         vertex 3.342784 -0 0.375 | ||||
|         vertex 4.146974 -0 0.75 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0.42261833 0 -0.90630776 | ||||
|     outer loop | ||||
|         vertex 5.755354 -4 1.5 | ||||
|         vertex 4.146974 -0 0.75 | ||||
|         vertex 5.755354 -0 1.5 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 -1 | ||||
|     outer loop | ||||
|         vertex 5.755354 -4 1.5 | ||||
|         vertex 5.755354 -0 1.5 | ||||
|         vertex 9.5 -4 1.5 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 -1 | ||||
|     outer loop | ||||
|         vertex 9.5 -4 1.5 | ||||
|         vertex 5.755354 -0 1.5 | ||||
|         vertex 9.5 -0 1.5 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 1 0 0 | ||||
|     outer loop | ||||
|         vertex 9.5 -4 1.5 | ||||
|         vertex 9.5 -0 1.5 | ||||
|         vertex 9.5 -4 2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 1 -0 0 | ||||
|     outer loop | ||||
|         vertex 9.5 -4 2 | ||||
|         vertex 9.5 -0 1.5 | ||||
|         vertex 9.5 -0 2 | ||||
|         vertex 88.9 -101.600006 25.400002 | ||||
|         vertex 143.37048 -0 50.800003 | ||||
|         vertex 88.9 -0 25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -0 1 | ||||
|     outer loop | ||||
|         vertex 9.5 -4 2 | ||||
|         vertex 9.5 -0 2 | ||||
|         vertex 5.644507 -4 2 | ||||
|         vertex 88.9 -101.600006 25.400002 | ||||
|         vertex 88.9 -0 25.400002 | ||||
|         vertex 0 -101.600006 25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 1 | ||||
|     outer loop | ||||
|         vertex 5.644507 -4 2 | ||||
|         vertex 9.5 -0 2 | ||||
|         vertex 5.644507 -0 2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -0.42261824 0 0.90630776 | ||||
|     outer loop | ||||
|         vertex 5.644507 -4 2 | ||||
|         vertex 5.644507 -0 2 | ||||
|         vertex 3.5 -4 1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -0.42261824 0 0.90630776 | ||||
|     outer loop | ||||
|         vertex 3.5 -4 1 | ||||
|         vertex 5.644507 -0 2 | ||||
|         vertex 3.5 -0 1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -0 1 | ||||
|     outer loop | ||||
|         vertex 3.5 -4 1 | ||||
|         vertex 3.5 -0 1 | ||||
|         vertex 0 -4 1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0 1 | ||||
|     outer loop | ||||
|         vertex 0 -4 1 | ||||
|         vertex 3.5 -0 1 | ||||
|         vertex 0 -0 1 | ||||
|         vertex 0 -101.600006 25.400002 | ||||
|         vertex 88.9 -0 25.400002 | ||||
|         vertex 0 -0 25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -1 0 0 | ||||
|     outer loop | ||||
|         vertex 0 -4 1 | ||||
|         vertex 0 -0 1 | ||||
|         vertex 0 -4 0 | ||||
|         vertex 0 -101.600006 25.400002 | ||||
|         vertex 0 -0 25.400002 | ||||
|         vertex 0 -101.600006 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -1 0 0 | ||||
|     outer loop | ||||
|         vertex 0 -4 0 | ||||
|         vertex 0 -0 1 | ||||
|         vertex 0 -101.600006 0 | ||||
|         vertex 0 -0 25.400002 | ||||
|         vertex 0 -0 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 -0 | ||||
|     outer loop | ||||
|         vertex 3.342784 -0 0.375 | ||||
|         vertex 2.5385938 -0 0 | ||||
|         vertex 3.5 -0 1 | ||||
|         vertex 84.906715 -0 9.525 | ||||
|         vertex 64.480286 -0 0 | ||||
|         vertex 88.9 -0 25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 0 | ||||
|     outer loop | ||||
|         vertex 4.146974 -0 0.75 | ||||
|         vertex 3.342784 -0 0.375 | ||||
|         vertex 3.5 -0 1 | ||||
|         vertex 105.33314 -0 19.05 | ||||
|         vertex 84.906715 -0 9.525 | ||||
|         vertex 88.9 -0 25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 0 | ||||
|     outer loop | ||||
|         vertex 3.4311862 -0 -0.625 | ||||
|         vertex 4.323779 -0 -1.25 | ||||
|         vertex 3.0950184 -0 -1 | ||||
|         vertex 87.15214 -0 -15.875 | ||||
|         vertex 109.82398 -0 -31.75 | ||||
|         vertex 78.613464 -0 -25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0.99999994 0 | ||||
| facet normal 0 1 0 | ||||
|     outer loop | ||||
|         vertex 4.146974 -0 0.75 | ||||
|         vertex 5.644507 -0 2 | ||||
|         vertex 5.755354 -0 1.5 | ||||
|         vertex 105.33314 -0 19.05 | ||||
|         vertex 143.37048 -0 50.800003 | ||||
|         vertex 146.18599 -0 38.1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -0 1 0 | ||||
|     outer loop | ||||
|         vertex 0 -0 1 | ||||
|         vertex 3.5 -0 1 | ||||
|         vertex 2.5385938 -0 0 | ||||
|         vertex 0 -0 25.400002 | ||||
|         vertex 88.9 -0 25.400002 | ||||
|         vertex 64.480286 -0 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 0 | ||||
|     outer loop | ||||
|         vertex 0 -0 1 | ||||
|         vertex 2.5385938 -0 0 | ||||
|         vertex 0 -0 25.400002 | ||||
|         vertex 64.480286 -0 0 | ||||
|         vertex 0 -0 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -0 1 0 | ||||
|     outer loop | ||||
|         vertex 5.644507 -0 2 | ||||
|         vertex 9.5 -0 2 | ||||
|         vertex 5.755354 -0 1.5 | ||||
|         vertex 143.37048 -0 50.800003 | ||||
|         vertex 241.3 -0 50.800003 | ||||
|         vertex 146.18599 -0 38.1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 0 | ||||
|     outer loop | ||||
|         vertex 9.5 -0 2 | ||||
|         vertex 9.5 -0 1.5 | ||||
|         vertex 5.755354 -0 1.5 | ||||
|         vertex 241.3 -0 50.800003 | ||||
|         vertex 241.3 -0 38.1 | ||||
|         vertex 146.18599 -0 38.1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 -0 | ||||
|     outer loop | ||||
|         vertex 4.146974 -0 0.75 | ||||
|         vertex 3.5 -0 1 | ||||
|         vertex 5.644507 -0 2 | ||||
|         vertex 105.33314 -0 19.05 | ||||
|         vertex 88.9 -0 25.400002 | ||||
|         vertex 143.37048 -0 50.800003 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0.99999994 0 | ||||
|     outer loop | ||||
|         vertex 2.5385938 -0 0 | ||||
|         vertex 3.4311862 -0 -0.625 | ||||
|         vertex 3.0950184 -0 -1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0.99999994 0 | ||||
|     outer loop | ||||
|         vertex 4.323779 -0 -1.25 | ||||
|         vertex 5.9513144 -0 -3 | ||||
|         vertex 3.0950184 -0 -1 | ||||
|         vertex 64.480286 -0 0 | ||||
|         vertex 87.15214 -0 -15.875 | ||||
|         vertex 78.613464 -0 -25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 0 | ||||
|     outer loop | ||||
|         vertex 6.108964 -0 -2.5 | ||||
|         vertex 5.9513144 -0 -3 | ||||
|         vertex 4.323779 -0 -1.25 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 0.99999994 0 | ||||
|     outer loop | ||||
|         vertex 9.5 -0 -2.5 | ||||
|         vertex 9.5 -0 -3 | ||||
|         vertex 6.108964 -0 -2.5 | ||||
|         vertex 109.82398 -0 -31.75 | ||||
|         vertex 151.16339 -0 -76.2 | ||||
|         vertex 78.613464 -0 -25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 0 | ||||
|     outer loop | ||||
|         vertex 6.108964 -0 -2.5 | ||||
|         vertex 9.5 -0 -3 | ||||
|         vertex 5.9513144 -0 -3 | ||||
|         vertex 155.16768 -0 -63.5 | ||||
|         vertex 151.16339 -0 -76.2 | ||||
|         vertex 109.82398 -0 -31.75 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 0 | ||||
|     outer loop | ||||
|         vertex 2.5385938 -0 0 | ||||
|         vertex 3.0950184 -0 -1 | ||||
|         vertex 0 -0 -1 | ||||
|         vertex 241.3 -0 -63.5 | ||||
|         vertex 241.3 -0 -76.2 | ||||
|         vertex 155.16768 -0 -63.5 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 0 | ||||
|     outer loop | ||||
|         vertex 0 -0 -1 | ||||
|         vertex 155.16768 -0 -63.5 | ||||
|         vertex 241.3 -0 -76.2 | ||||
|         vertex 151.16339 -0 -76.2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 0 | ||||
|     outer loop | ||||
|         vertex 64.480286 -0 0 | ||||
|         vertex 78.613464 -0 -25.400002 | ||||
|         vertex 0 -0 -25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 1 0 | ||||
|     outer loop | ||||
|         vertex 0 -0 -25.400002 | ||||
|         vertex 0 -0 0 | ||||
|         vertex 2.5385938 -0 0 | ||||
|         vertex 64.480286 -0 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -0 -1 0 | ||||
|     outer loop | ||||
|         vertex 3.342784 -4 0.375 | ||||
|         vertex 3.5 -4 1 | ||||
|         vertex 2.5385938 -4 0 | ||||
|         vertex 84.906715 -101.600006 9.525 | ||||
|         vertex 88.9 -101.600006 25.400002 | ||||
|         vertex 64.480286 -101.600006 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -0 -1 0 | ||||
|     outer loop | ||||
|         vertex 4.146974 -4 0.75 | ||||
|         vertex 3.5 -4 1 | ||||
|         vertex 3.342784 -4 0.375 | ||||
|         vertex 105.33314 -101.600006 19.05 | ||||
|         vertex 88.9 -101.600006 25.400002 | ||||
|         vertex 84.906715 -101.600006 9.525 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 -0 | ||||
|     outer loop | ||||
|         vertex 3.4311862 -4 -0.625 | ||||
|         vertex 3.0950184 -4 -1 | ||||
|         vertex 4.323779 -4 -1.25 | ||||
|         vertex 87.15214 -101.600006 -15.875 | ||||
|         vertex 78.613464 -101.600006 -25.400002 | ||||
|         vertex 109.82398 -101.600006 -31.75 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 0 | ||||
|     outer loop | ||||
|         vertex 105.33314 -101.600006 19.05 | ||||
|         vertex 146.18599 -101.600006 38.1 | ||||
|         vertex 143.37048 -101.600006 50.800003 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 0 | ||||
|     outer loop | ||||
|         vertex 0 -101.600006 25.400002 | ||||
|         vertex 64.480286 -101.600006 0 | ||||
|         vertex 88.9 -101.600006 25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 0 | ||||
|     outer loop | ||||
|         vertex 0 -101.600006 25.400002 | ||||
|         vertex 0 -101.600006 0 | ||||
|         vertex 64.480286 -101.600006 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 0 | ||||
|     outer loop | ||||
|         vertex 143.37048 -101.600006 50.800003 | ||||
|         vertex 146.18599 -101.600006 38.1 | ||||
|         vertex 241.3 -101.600006 50.800003 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 -0 | ||||
|     outer loop | ||||
|         vertex 241.3 -101.600006 50.800003 | ||||
|         vertex 146.18599 -101.600006 38.1 | ||||
|         vertex 241.3 -101.600006 38.1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 0 | ||||
|     outer loop | ||||
|         vertex 105.33314 -101.600006 19.05 | ||||
|         vertex 143.37048 -101.600006 50.800003 | ||||
|         vertex 88.9 -101.600006 25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -0.99999994 0 | ||||
|     outer loop | ||||
|         vertex 4.146974 -4 0.75 | ||||
|         vertex 5.755354 -4 1.5 | ||||
|         vertex 5.644507 -4 2 | ||||
|         vertex 64.480286 -101.600006 0 | ||||
|         vertex 78.613464 -101.600006 -25.400002 | ||||
|         vertex 87.15214 -101.600006 -15.875 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 0 | ||||
| facet normal -0 -1 -0 | ||||
|     outer loop | ||||
|         vertex 0 -4 1 | ||||
|         vertex 2.5385938 -4 0 | ||||
|         vertex 3.5 -4 1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 0 | ||||
|     outer loop | ||||
|         vertex 0 -4 1 | ||||
|         vertex 0 -4 0 | ||||
|         vertex 2.5385938 -4 0 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 0 | ||||
|     outer loop | ||||
|         vertex 5.644507 -4 2 | ||||
|         vertex 5.755354 -4 1.5 | ||||
|         vertex 9.5 -4 2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 -0 | ||||
|     outer loop | ||||
|         vertex 9.5 -4 2 | ||||
|         vertex 5.755354 -4 1.5 | ||||
|         vertex 9.5 -4 1.5 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 0 | ||||
|     outer loop | ||||
|         vertex 4.146974 -4 0.75 | ||||
|         vertex 5.644507 -4 2 | ||||
|         vertex 3.5 -4 1 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -0.99999994 0 | ||||
|     outer loop | ||||
|         vertex 2.5385938 -4 0 | ||||
|         vertex 3.0950184 -4 -1 | ||||
|         vertex 3.4311862 -4 -0.625 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -0 -0.99999994 -0 | ||||
|     outer loop | ||||
|         vertex 4.323779 -4 -1.25 | ||||
|         vertex 3.0950184 -4 -1 | ||||
|         vertex 5.9513144 -4 -3 | ||||
|         vertex 109.82398 -101.600006 -31.75 | ||||
|         vertex 78.613464 -101.600006 -25.400002 | ||||
|         vertex 151.16339 -101.600006 -76.2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -0 -1 0 | ||||
|     outer loop | ||||
|         vertex 6.108964 -4 -2.5 | ||||
|         vertex 4.323779 -4 -1.25 | ||||
|         vertex 5.9513144 -4 -3 | ||||
|         vertex 155.16768 -101.600006 -63.5 | ||||
|         vertex 109.82398 -101.600006 -31.75 | ||||
|         vertex 151.16339 -101.600006 -76.2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal -0 -0.99999994 -0 | ||||
| facet normal -0 -1 -0 | ||||
|     outer loop | ||||
|         vertex 9.5 -4 -2.5 | ||||
|         vertex 6.108964 -4 -2.5 | ||||
|         vertex 9.5 -4 -3 | ||||
|         vertex 241.3 -101.600006 -63.5 | ||||
|         vertex 155.16768 -101.600006 -63.5 | ||||
|         vertex 241.3 -101.600006 -76.2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 -0 | ||||
|     outer loop | ||||
|         vertex 6.108964 -4 -2.5 | ||||
|         vertex 5.9513144 -4 -3 | ||||
|         vertex 9.5 -4 -3 | ||||
|         vertex 155.16768 -101.600006 -63.5 | ||||
|         vertex 151.16339 -101.600006 -76.2 | ||||
|         vertex 241.3 -101.600006 -76.2 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 -0 | ||||
|     outer loop | ||||
|         vertex 2.5385938 -4 0 | ||||
|         vertex 0 -4 -1 | ||||
|         vertex 3.0950184 -4 -1 | ||||
|         vertex 64.480286 -101.600006 0 | ||||
|         vertex 0 -101.600006 -25.400002 | ||||
|         vertex 78.613464 -101.600006 -25.400002 | ||||
|     endloop | ||||
| endfacet | ||||
| facet normal 0 -1 0 | ||||
|     outer loop | ||||
|         vertex 0 -4 -1 | ||||
|         vertex 2.5385938 -4 0 | ||||
|         vertex 0 -4 0 | ||||
|         vertex 0 -101.600006 -25.400002 | ||||
|         vertex 64.480286 -101.600006 0 | ||||
|         vertex 0 -101.600006 0 | ||||
|     endloop | ||||
| endfacet | ||||
| endsolid unnamed | ||||
|  | ||||
| Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 221 KiB | 
| @ -68,11 +68,11 @@ test('Basic sketch', async ({ page }) => { | ||||
|     `const part001 = startSketchOn('-XZ')` | ||||
|   ) | ||||
|  | ||||
|   await page.waitForTimeout(500) // TODO detect animation ending, or disable animation | ||||
|   await page.waitForTimeout(300) // TODO detect animation ending, or disable animation | ||||
|  | ||||
|   const startXPx = 600 | ||||
|   await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) | ||||
|   const startAt = '[23.89, -32.23]' | ||||
|   const startAt = '[23.74, -32.03]' | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %)`) | ||||
| @ -82,7 +82,7 @@ test('Basic sketch', async ({ page }) => { | ||||
|   await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) | ||||
|   await page.waitForTimeout(100) | ||||
|  | ||||
|   const num = 24.11 | ||||
|   const num = 23.97 | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %) | ||||
| @ -93,14 +93,14 @@ test('Basic sketch', async ({ page }) => { | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %) | ||||
|   |> line([${num}, 0], %) | ||||
|   |> line([0, ${num + 0.01}], %)`) | ||||
|   |> line([0, ${num}], %)`) | ||||
|   await page.mouse.click(startXPx, 500 - PUR * 20) | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %) | ||||
|   |> line([${num}, 0], %) | ||||
|   |> line([0, ${num + 0.01}], %) | ||||
|   |> line([-48, 0], %)`) | ||||
|   |> line([0, ${num}], %) | ||||
|   |> line([-47.71, 0], %)`) | ||||
|  | ||||
|   // deselect line tool | ||||
|   await page.getByRole('button', { name: 'Line' }).click() | ||||
| @ -124,7 +124,7 @@ test('Basic sketch', async ({ page }) => { | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %) | ||||
|   |> line({ to: [${num}, 0], tag: 'seg01' }, %) | ||||
|   |> line([0, ${num + 0.01}], %) | ||||
|   |> line([0, ${num}], %) | ||||
|   |> angledLine([180, segLen('seg01', %)], %)`) | ||||
| }) | ||||
|  | ||||
| @ -197,7 +197,7 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => { | ||||
|  | ||||
| test('executes on load', async ({ page, context }) => { | ||||
|   const u = getUtils(page) | ||||
|   await context.addInitScript(async (token) => { | ||||
|   await context.addInitScript(async () => { | ||||
|     localStorage.setItem( | ||||
|       'persistCode', | ||||
|       `const part001 = startSketchOn('-XZ') | ||||
| @ -306,9 +306,10 @@ test('Can create sketches on all planes and their back sides', async ({ | ||||
|  | ||||
|   const codeTemplate = ( | ||||
|     plane = 'XY', | ||||
|     rounded = false | ||||
|     rounded = false, | ||||
|     otherThing = '1' | ||||
|   ) => `const part001 = startSketchOn('${plane}') | ||||
|   |> startProfileAt([28.91, -39${rounded ? '' : '.01'}], %)` | ||||
|   |> startProfileAt([28.9${otherThing}, -39${rounded ? '' : '.01'}], %)` | ||||
|   await TestSinglePlane({ | ||||
|     viewCmd: camPos, | ||||
|     expectedCode: codeTemplate('XY'), | ||||
| @ -328,7 +329,7 @@ test('Can create sketches on all planes and their back sides', async ({ | ||||
|   const camCmdBackSide: [number, number, number] = [-100, -100, -100] | ||||
|   await TestSinglePlane({ | ||||
|     viewCmd: camCmdBackSide, | ||||
|     expectedCode: codeTemplate('-XY', true), | ||||
|     expectedCode: codeTemplate('-XY', false, '3'), | ||||
|     clickCoords: { x: 601, y: 118 }, // back of red plane | ||||
|   }) | ||||
|   await TestSinglePlane({ | ||||
| @ -366,6 +367,7 @@ test('Auto complete works', async ({ page }) => { | ||||
|   await page.keyboard.type('  |> startProfi') | ||||
|   // expect there be a single auto complete option that we can just hit enter on | ||||
|   await expect(page.locator('.cm-completionLabel')).toBeVisible() | ||||
|   await page.waitForTimeout(100) | ||||
|   await page.keyboard.press('Enter') // accepting the auto complete, not a new line | ||||
|  | ||||
|   await page.keyboard.type('([0,0], %)') | ||||
| @ -373,6 +375,7 @@ test('Auto complete works', async ({ page }) => { | ||||
|   await page.keyboard.type('  |> lin') | ||||
|  | ||||
|   await expect(page.locator('.cm-tooltip-autocomplete')).toBeVisible() | ||||
|   await page.waitForTimeout(100) | ||||
|   // press arrow down twice then enter to accept xLine | ||||
|   await page.keyboard.press('ArrowDown') | ||||
|   await page.keyboard.press('ArrowDown') | ||||
| @ -458,7 +461,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => { | ||||
|  | ||||
|   const startXPx = 600 | ||||
|   await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) | ||||
|   const startAt = '[23.89, -32.23]' | ||||
|   const startAt = '[23.74, -32.03]' | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %)`) | ||||
| @ -467,8 +470,8 @@ test('Selections work on fresh and edited sketch', async ({ page }) => { | ||||
|  | ||||
|   await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) | ||||
|  | ||||
|   const num = 24.11 | ||||
|   const num2 = '48' | ||||
|   const num = 23.97 | ||||
|   const num2 = '47.71' | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %) | ||||
| @ -479,13 +482,13 @@ test('Selections work on fresh and edited sketch', async ({ page }) => { | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %) | ||||
|   |> line([${num}, 0], %) | ||||
|   |> line([0, ${num + 0.01}], %)`) | ||||
|   |> line([0, ${num}], %)`) | ||||
|   await page.mouse.click(startXPx, 500 - PUR * 20) | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %) | ||||
|   |> line([${num}, 0], %) | ||||
|   |> line([0, ${num + 0.01}], %) | ||||
|   |> line([0, ${num}], %) | ||||
|   |> line([-${num2}, 0], %)`) | ||||
|  | ||||
|   // deselect line tool | ||||
| @ -571,12 +574,13 @@ test('Selections work on fresh and edited sketch', async ({ page }) => { | ||||
|   await u.closeDebugPanel() | ||||
|  | ||||
|   // select a line | ||||
|   await topHorzSegmentClick() | ||||
|   await page.waitForTimeout(200) | ||||
|   // await topHorzSegmentClick() | ||||
|   await page.getByText(startAt).click() // TODO remove this and reinstate // await topHorzSegmentClick() | ||||
|   await page.waitForTimeout(100) | ||||
|  | ||||
|   // enter sketch again | ||||
|   await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|   await page.waitForTimeout(700) // wait for animation | ||||
|   await page.getByRole('button', { name: 'Edit Sketch' }).click() | ||||
|   await page.waitForTimeout(300) // wait for animation | ||||
|  | ||||
|   // hover again and check it works | ||||
|   await selectionSequence() | ||||
| @ -712,7 +716,7 @@ test('Can add multiple sketches', async ({ page }) => { | ||||
|  | ||||
|   const startXPx = 600 | ||||
|   await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) | ||||
|   const startAt = '[23.89, -32.23]' | ||||
|   const startAt = '[23.74, -32.03]' | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %)`) | ||||
| @ -722,7 +726,7 @@ test('Can add multiple sketches', async ({ page }) => { | ||||
|   await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) | ||||
|   await page.waitForTimeout(100) | ||||
|  | ||||
|   const num = 24.11 | ||||
|   const num = 23.97 | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %) | ||||
| @ -733,13 +737,13 @@ test('Can add multiple sketches', async ({ page }) => { | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %) | ||||
|   |> line([${num}, 0], %) | ||||
|   |> line([0, ${num + 0.01}], %)`) | ||||
|   |> line([0, ${num}], %)`) | ||||
|   await page.mouse.click(startXPx, 500 - PUR * 20) | ||||
|   const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %) | ||||
|   |> line([${num}, 0], %) | ||||
|   |> line([0, ${num + 0.01}], %) | ||||
|   |> line([-48, 0], %)` | ||||
|   |> line([0, ${num}], %) | ||||
|   |> line([-47.71, 0], %)` | ||||
|   await expect(page.locator('.cm-content')).toHaveText(finalCodeFirstSketch) | ||||
|  | ||||
|   // exit the sketch | ||||
| @ -810,7 +814,7 @@ const part002 = startSketchOn('XY') | ||||
|  | ||||
| test('ProgramMemory can be serialised', async ({ page, context }) => { | ||||
|   const u = getUtils(page) | ||||
|   await context.addInitScript(async (token) => { | ||||
|   await context.addInitScript(async () => { | ||||
|     localStorage.setItem( | ||||
|       'persistCode', | ||||
|       `const part = startSketchOn('XY') | ||||
| @ -846,3 +850,167 @@ test('ProgramMemory can be serialised', async ({ page, context }) => { | ||||
|     }) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| test("Various pipe expressions should and shouldn't allow edit and or extrude", async ({ | ||||
|   page, | ||||
|   context, | ||||
| }) => { | ||||
|   const u = getUtils(page) | ||||
|   const selectionsSnippets = { | ||||
|     extrudeAndEditBlocked: '|> startProfileAt([10.81, 32.99], %)', | ||||
|     extrudeAndEditBlockedInFunction: '|> startProfileAt(pos, %)', | ||||
|     extrudeAndEditAllowed: '|> startProfileAt([15.72, 4.7], %)', | ||||
|     editOnly: '|> startProfileAt([15.79, -14.6], %)', | ||||
|   } | ||||
|   await context.addInitScript( | ||||
|     async ({ | ||||
|       extrudeAndEditBlocked, | ||||
|       extrudeAndEditBlockedInFunction, | ||||
|       extrudeAndEditAllowed, | ||||
|       editOnly, | ||||
|     }: any) => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `const part001 = startSketchOn('-XZ') | ||||
|   ${extrudeAndEditBlocked} | ||||
|   |> line([25.96, 2.93], %) | ||||
|   |> line([5.25, -5.72], %) | ||||
|   |> line([-2.01, -10.35], %) | ||||
|   |> line([-27.65, -2.78], %) | ||||
|   |> close(%) | ||||
|   |> extrude(5, %) | ||||
| const part002 = startSketchOn('-XZ') | ||||
|   ${extrudeAndEditAllowed} | ||||
|   |> line([10.32, 6.47], %) | ||||
|   |> line([9.71, -6.16], %) | ||||
|   |> line([-3.08, -9.86], %) | ||||
|   |> line([-12.02, -1.54], %) | ||||
|   |> close(%) | ||||
| const part003 = startSketchOn('-XZ') | ||||
|   ${editOnly} | ||||
|   |> line([27.55, -1.65], %) | ||||
|   |> line([4.95, -8], %) | ||||
|   |> line([-20.38, -10.12], %) | ||||
|   |> line([-15.79, 17.08], %) | ||||
|  | ||||
| fn yohey = (pos) => { | ||||
|   const part004 = startSketchOn('-XZ') | ||||
|   ${extrudeAndEditBlockedInFunction} | ||||
|   |> line([27.55, -1.65], %) | ||||
|   |> line([4.95, -10.53], %) | ||||
|   |> line([-20.38, -8], %) | ||||
|   |> line([-15.79, 17.08], %) | ||||
|   return '' | ||||
| } | ||||
|      | ||||
|     yohey([15.79, -34.6]) | ||||
| ` | ||||
|       ) | ||||
|     }, | ||||
|     selectionsSnippets | ||||
|   ) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await page.goto('/') | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|   // wait for execution done | ||||
|   await u.openDebugPanel() | ||||
|   await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|   await u.closeDebugPanel() | ||||
|  | ||||
|   await page.getByText(selectionsSnippets.extrudeAndEditBlocked).click() | ||||
|   await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled() | ||||
|   await expect( | ||||
|     page.getByRole('button', { name: 'Edit Sketch' }) | ||||
|   ).not.toBeVisible() | ||||
|  | ||||
|   await page.getByText(selectionsSnippets.extrudeAndEditAllowed).click() | ||||
|   await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled() | ||||
|   await expect( | ||||
|     page.getByRole('button', { name: 'Edit Sketch' }) | ||||
|   ).not.toBeDisabled() | ||||
|  | ||||
|   await page.getByText(selectionsSnippets.editOnly).click() | ||||
|   await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled() | ||||
|   await expect( | ||||
|     page.getByRole('button', { name: 'Edit Sketch' }) | ||||
|   ).not.toBeDisabled() | ||||
|  | ||||
|   await page | ||||
|     .getByText(selectionsSnippets.extrudeAndEditBlockedInFunction) | ||||
|     .click() | ||||
|   await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled() | ||||
|   await expect( | ||||
|     page.getByRole('button', { name: 'Edit Sketch' }) | ||||
|   ).not.toBeVisible() | ||||
|  | ||||
|   // selecting an editable sketch but clicking "start sktech" should start a new sketch and not edit the existing one | ||||
|   await page.getByText(selectionsSnippets.extrudeAndEditAllowed).click() | ||||
|   await page.getByRole('button', { name: 'Start Sketch' }).click() | ||||
|   await page.mouse.click(700, 200) | ||||
|   // expect main content to contain `part005` i.e. started a new sketch | ||||
|   await expect(page.locator('.cm-content')).toHaveText( | ||||
|     /part005 = startSketchOn\('-XZ'\)/ | ||||
|   ) | ||||
| }) | ||||
|  | ||||
| test('Deselecting line tool should mean nothing happens on click', async ({ | ||||
|   page, | ||||
| }) => { | ||||
|   const u = getUtils(page) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|   await page.goto('/') | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|   await u.openDebugPanel() | ||||
|  | ||||
|   await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible() | ||||
|  | ||||
|   // click on "Start Sketch" button | ||||
|   await u.clearCommandLogs() | ||||
|   await u.doAndWaitForImageDiff( | ||||
|     () => page.getByRole('button', { name: 'Start Sketch' }).click(), | ||||
|     200 | ||||
|   ) | ||||
|  | ||||
|   await page.mouse.click(700, 200) | ||||
|  | ||||
|   await expect(page.locator('.cm-content')).toHaveText( | ||||
|     `const part001 = startSketchOn('-XZ')` | ||||
|   ) | ||||
|  | ||||
|   await page.waitForTimeout(300) | ||||
|  | ||||
|   let previousCodeContent = await page.locator('.cm-content').innerText() | ||||
|  | ||||
|   // deselect the line tool by clicking it | ||||
|   await page.getByRole('button', { name: 'Line' }).click() | ||||
|  | ||||
|   await page.mouse.click(700, 200) | ||||
|   await page.waitForTimeout(100) | ||||
|   await page.mouse.click(700, 250) | ||||
|   await page.waitForTimeout(100) | ||||
|   await page.mouse.click(750, 200) | ||||
|   await page.waitForTimeout(100) | ||||
|  | ||||
|   // expect no change | ||||
|   await expect(page.locator('.cm-content')).toHaveText(previousCodeContent) | ||||
|  | ||||
|   // select line tool again | ||||
|   await page.getByRole('button', { name: 'Line' }).click() | ||||
|  | ||||
|   await u.closeDebugPanel() | ||||
|  | ||||
|   // line tool should work as expected again | ||||
|   await page.mouse.click(700, 200) | ||||
|   await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) | ||||
|   previousCodeContent = await page.locator('.cm-content').innerText() | ||||
|  | ||||
|   await page.mouse.click(700, 300) | ||||
|   await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) | ||||
|   previousCodeContent = await page.locator('.cm-content').innerText() | ||||
|  | ||||
|   await page.mouse.click(750, 300) | ||||
|   await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent) | ||||
|   previousCodeContent = await page.locator('.cm-content').innerText() | ||||
| }) | ||||
|  | ||||
| @ -1,7 +1,5 @@ | ||||
| import { test, expect } from '@playwright/test' | ||||
| import { secrets } from './secrets' | ||||
| import { EngineCommand } from '../../src/lang/std/engineConnection' | ||||
| import { v4 as uuidv4 } from 'uuid' | ||||
| import { getUtils } from './test-utils' | ||||
| import { Models } from '@kittycad/lib' | ||||
| import fsp from 'fs/promises' | ||||
| @ -364,3 +362,118 @@ const part001 = startSketchOn('-XZ') | ||||
|     }) | ||||
|   } | ||||
| }) | ||||
|  | ||||
| test('extrude on each default plane should be stable', async ({ | ||||
|   page, | ||||
|   context, | ||||
| }) => { | ||||
|   const u = getUtils(page) | ||||
|   const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}') | ||||
|   |> startProfileAt([0.70, 0.44], %) | ||||
|   |> line([0.66, -0.02], %) | ||||
|   |> line([0.28, 0.50], %) | ||||
|   |> line([-0.56, 0.44], %) | ||||
|   |> line([-0.54, -0.38], %) | ||||
|   |> close(%) | ||||
|   |> extrude(1.00, %) | ||||
| ` | ||||
|   await context.addInitScript(async (code) => { | ||||
|     localStorage.setItem('persistCode', code) | ||||
|   }, makeCode('XY')) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   await page.goto('/') | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|  | ||||
|   // wait for execution done | ||||
|   await u.openDebugPanel() | ||||
|   await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|   await u.clearAndCloseDebugPanel() | ||||
|  | ||||
|   await page.getByText('Code').click() | ||||
|   await expect(page).toHaveScreenshot({ | ||||
|     maxDiffPixels: 100, | ||||
|   }) | ||||
|   await page.getByText('Code').click() | ||||
|  | ||||
|   const runSnapshotsForOtherPlanes = async (plane = 'XY') => { | ||||
|     // clear code | ||||
|     await u.removeCurrentCode() | ||||
|     // add makeCode('XZ') | ||||
|     await page.locator('.cm-content').fill(makeCode(plane)) | ||||
|     // wait for execution done | ||||
|     await u.openDebugPanel() | ||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') | ||||
|     await u.clearAndCloseDebugPanel() | ||||
|  | ||||
|     await page.getByText('Code').click() | ||||
|     await expect(page).toHaveScreenshot({ | ||||
|       maxDiffPixels: 100, | ||||
|     }) | ||||
|     await page.getByText('Code').click() | ||||
|   } | ||||
|   await runSnapshotsForOtherPlanes('-XY') | ||||
|  | ||||
|   await runSnapshotsForOtherPlanes('XZ') | ||||
|   await runSnapshotsForOtherPlanes('-XZ') | ||||
|  | ||||
|   await runSnapshotsForOtherPlanes('YZ') | ||||
|   await runSnapshotsForOtherPlanes('-YZ') | ||||
| }) | ||||
|  | ||||
| test('Draft segments should look right', async ({ page }) => { | ||||
|   const u = getUtils(page) | ||||
|   await page.setViewportSize({ width: 1200, height: 500 }) | ||||
|   const PUR = 400 / 37.5 //pixeltoUnitRatio | ||||
|   await page.goto('/') | ||||
|   await u.waitForAuthSkipAppStart() | ||||
|   await u.openDebugPanel() | ||||
|  | ||||
|   await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible() | ||||
|  | ||||
|   // click on "Start Sketch" button | ||||
|   await u.clearCommandLogs() | ||||
|   await u.doAndWaitForImageDiff( | ||||
|     () => page.getByRole('button', { name: 'Start Sketch' }).click(), | ||||
|     200 | ||||
|   ) | ||||
|  | ||||
|   // select a plane | ||||
|   await page.mouse.click(700, 200) | ||||
|  | ||||
|   await expect(page.locator('.cm-content')).toHaveText( | ||||
|     `const part001 = startSketchOn('-XZ')` | ||||
|   ) | ||||
|  | ||||
|   await page.waitForTimeout(300) // TODO detect animation ending, or disable animation | ||||
|  | ||||
|   const startXPx = 600 | ||||
|   await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10) | ||||
|   const startAt = '[23.74, -32.03]' | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %)`) | ||||
|   await page.waitForTimeout(100) | ||||
|  | ||||
|   await u.closeDebugPanel() | ||||
|   await page.mouse.move(startXPx + PUR * 20, 500 - PUR * 10) | ||||
|   await expect(page).toHaveScreenshot({ | ||||
|     maxDiffPixels: 100, | ||||
|   }) | ||||
|  | ||||
|   await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10) | ||||
|   await page.waitForTimeout(100) | ||||
|  | ||||
|   const num = 23.97 | ||||
|   await expect(page.locator('.cm-content')) | ||||
|     .toHaveText(`const part001 = startSketchOn('-XZ') | ||||
|   |> startProfileAt(${startAt}, %) | ||||
|   |> line([${num}, 0], %)`) | ||||
|  | ||||
|   await page.getByRole('button', { name: 'Tangential Arc' }).click() | ||||
|  | ||||
|   await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 }) | ||||
|  | ||||
|   await expect(page).toHaveScreenshot({ | ||||
|     maxDiffPixels: 100, | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| After Width: | Height: | Size: 41 KiB | 
| After Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 95 KiB | 
| Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 79 KiB | 
| Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 111 KiB | 
| After Width: | Height: | Size: 53 KiB | 
| After Width: | Height: | Size: 52 KiB | 
| After Width: | Height: | Size: 53 KiB | 
| After Width: | Height: | Size: 56 KiB | 
| After Width: | Height: | Size: 50 KiB | 
| After Width: | Height: | Size: 52 KiB | 
							
								
								
									
										69
									
								
								make-release.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						| @ -0,0 +1,69 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| if ! git diff-index --quiet HEAD --; then | ||||
|   echo "Please stash uncommitted changes before running release script" | ||||
|   exit 1 | ||||
| fi | ||||
|  | ||||
| git checkout main | ||||
| git pull | ||||
| git fetch --all | ||||
|  | ||||
| # Get the latest semver tag from git | ||||
| latest_tag=$(jq -r '.version' package.json) | ||||
| latest_tag="v$latest_tag" | ||||
|  | ||||
| # Print the latest semver tag | ||||
| echo "Latest semver tag: $latest_tag" | ||||
|  | ||||
| # Function to bump version numbers | ||||
| bump_version() { | ||||
|   local version=$1 | ||||
|   local bump_type=$2 | ||||
|   local major=$(echo $version | cut -d '.' -f 1 | sed 's/v//') | ||||
|   local minor=$(echo $version | cut -d '.' -f 2) | ||||
|   local patch=$(echo $version | cut -d '.' -f 3) | ||||
|  | ||||
|   case "$bump_type" in | ||||
|     major) | ||||
|       major=$((major + 1)) | ||||
|       minor=0 | ||||
|       patch=0 | ||||
|       ;; | ||||
|     minor) | ||||
|       minor=$((minor + 1)) | ||||
|       patch=0 | ||||
|       ;; | ||||
|     *) | ||||
|       patch=$((patch + 1)) | ||||
|       ;; | ||||
|   esac | ||||
|  | ||||
|   echo "v${major}.${minor}.${patch}" | ||||
| } | ||||
|  | ||||
| # Determine the type of bump based on the argument | ||||
| bump_type=${1:-patch} | ||||
|  | ||||
| # Bump the version | ||||
| new_version=$(bump_version $latest_tag $bump_type) | ||||
|  | ||||
| # Print the new semver tag | ||||
| echo "New semver tag: $new_version" | ||||
| new_version_number=${new_version:1} | ||||
| echo "New version number without 'v': $new_version_number" | ||||
|  | ||||
|  | ||||
| git checkout -b "cut-release-$new_version" | ||||
|  | ||||
| echo "$(jq --arg v "$new_version_number" '.version=$v' package.json --indent 2)" > package.json | ||||
| echo "$(jq --arg v "$new_version_number" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)" > src-tauri/tauri.conf.json | ||||
|  | ||||
| git add package.json src-tauri/tauri.conf.json | ||||
| git commit -m "Cut release $new_version" | ||||
|  | ||||
| echo "" | ||||
| echo "Versions has been bumped in relevant json files, a branch has been created and committed to." | ||||
| echo "" | ||||
| echo "What's left for you to do is, push the branch and make the release PR." | ||||
| echo "" | ||||
							
								
								
									
										16
									
								
								package.json
									
									
									
									
									
								
							
							
						
						| @ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "untitled-app", | ||||
|   "version": "0.14.0", | ||||
|   "version": "0.15.2", | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "@codemirror/autocomplete": "^6.10.2", | ||||
| @ -10,7 +10,7 @@ | ||||
|     "@fortawesome/react-fontawesome": "^0.2.0", | ||||
|     "@headlessui/react": "^1.7.17", | ||||
|     "@headlessui/tailwindcss": "^0.2.0", | ||||
|     "@kittycad/lib": "^0.0.50", | ||||
|     "@kittycad/lib": "^0.0.53", | ||||
|     "@lezer/javascript": "^1.4.9", | ||||
|     "@open-rpc/client-js": "^1.8.1", | ||||
|     "@react-hook/resize-observer": "^1.2.6", | ||||
| @ -37,6 +37,7 @@ | ||||
|     "fuse.js": "^7.0.0", | ||||
|     "http-server": "^14.1.1", | ||||
|     "json-rpc-2.0": "^1.6.0", | ||||
|     "node-fetch": "^3.3.2", | ||||
|     "re-resizable": "^6.9.11", | ||||
|     "react": "^18.2.0", | ||||
|     "react-dom": "^18.2.0", | ||||
| @ -53,7 +54,7 @@ | ||||
|     "ts-node": "^10.9.1", | ||||
|     "typescript": "^5.2.2", | ||||
|     "uuid": "^9.0.1", | ||||
|     "vitest": "^0.34.6", | ||||
|     "vitest": "^1.3.1", | ||||
|     "vscode-jsonrpc": "^8.1.0", | ||||
|     "vscode-languageserver-protocol": "^3.17.5", | ||||
|     "wasm-pack": "^0.12.1", | ||||
| @ -74,7 +75,6 @@ | ||||
|     "test": "vitest --mode development", | ||||
|     "test:nowatch": "vitest run --mode development", | ||||
|     "test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)", | ||||
|     "test:cov": "vitest run --coverage --mode development", | ||||
|     "test:e2e:tauri": "E2E_TAURI_ENABLED=true TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' wdio run wdio.conf.ts", | ||||
|     "simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &", | ||||
|     "simpleserver": "yarn pretest && http-server ./public --cors -p 3000", | ||||
| @ -115,7 +115,6 @@ | ||||
|     "@tauri-apps/cli": "^2.0.0-beta.1", | ||||
|     "@types/crypto-js": "^4.1.1", | ||||
|     "@types/debounce-promise": "^3.1.8", | ||||
|     "@types/isomorphic-fetch": "^0.0.36", | ||||
|     "@types/pixelmatch": "^5.2.6", | ||||
|     "@types/pngjs": "^6.0.4", | ||||
|     "@types/react-modal": "^3.16.3", | ||||
| @ -124,8 +123,7 @@ | ||||
|     "@types/wait-on": "^5.3.4", | ||||
|     "@types/wicg-file-system-access": "^2020.9.6", | ||||
|     "@types/ws": "^8.5.5", | ||||
|     "@vitejs/plugin-react": "^4.1.1", | ||||
|     "@vitest/coverage-istanbul": "^0.34.6", | ||||
|     "@vitejs/plugin-react": "^4.2.1", | ||||
|     "@wdio/cli": "^8.24.3", | ||||
|     "@wdio/globals": "^8.24.3", | ||||
|     "@wdio/local-runner": "^8.24.3", | ||||
| @ -146,10 +144,10 @@ | ||||
|     "prettier": "^2.8.0", | ||||
|     "setimmediate": "^1.0.5", | ||||
|     "tailwindcss": "^3.3.6", | ||||
|     "vite": "^4.5.2", | ||||
|     "vite": "^5.1.3", | ||||
|     "vite-plugin-eslint": "^1.8.1", | ||||
|     "vite-plugin-package-version": "^1.1.0", | ||||
|     "vite-tsconfig-paths": "^4.2.1", | ||||
|     "vite-tsconfig-paths": "^4.3.1", | ||||
|     "vitest-webgl-canvas-mock": "^1.1.0", | ||||
|     "wait-on": "^7.2.0", | ||||
|     "yarn": "^1.22.19" | ||||
|  | ||||
							
								
								
									
										576
									
								
								src-tauri/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						| @ -16,7 +16,7 @@ tauri-build = { version = "2.0.0-beta", features = [] } | ||||
|  | ||||
| [dependencies] | ||||
| anyhow = "1" | ||||
| kittycad = "0.2.50" | ||||
| kittycad = "0.2.53" | ||||
| oauth2 = "4.4.2" | ||||
| serde = { version = "1.0", features = ["derive"] } | ||||
| serde_json = "1.0" | ||||
|  | ||||
| @ -59,5 +59,5 @@ | ||||
|     } | ||||
|   }, | ||||
|   "productName": "zoo-modeling-app", | ||||
|   "version": "0.14.0" | ||||
|   "version": "0.15.2" | ||||
| } | ||||
|  | ||||
| @ -73,6 +73,9 @@ export function App() { | ||||
|   useHotkeys('shift + e', () => togglePane('kclErrors')) | ||||
|   useHotkeys('shift + d', () => togglePane('debug')) | ||||
|   useHotkeys('esc', () => send('Cancel')) | ||||
|   useHotkeys('backspace', (e) => { | ||||
|     e.preventDefault() | ||||
|   }) | ||||
|   useHotkeys( | ||||
|     isTauri() ? 'mod + ,' : 'shift + mod + ,', | ||||
|     () => navigate(filePath + paths.SETTINGS), | ||||
|  | ||||
| @ -5,6 +5,8 @@ import { useModelingContext } from 'hooks/useModelingContext' | ||||
| import { useCommandsContext } from 'hooks/useCommandsContext' | ||||
| import { ActionButton } from 'components/ActionButton' | ||||
| import usePlatform from 'hooks/usePlatform' | ||||
| import { isSingleCursorInPipe } from 'lang/queryAst' | ||||
| import { kclManager } from 'lang/KclSingleton' | ||||
|  | ||||
| export const Toolbar = () => { | ||||
|   const platform = usePlatform() | ||||
| @ -13,14 +15,15 @@ export const Toolbar = () => { | ||||
|   const toolbarButtonsRef = useRef<HTMLUListElement>(null) | ||||
|   const bgClassName = | ||||
|     'group-enabled:group-hover:bg-energy-10 group-pressed:bg-energy-10 dark:group-enabled:group-hover:bg-chalkboard-80 dark:group-pressed:bg-chalkboard-80' | ||||
|   const pathId = useMemo( | ||||
|     () => | ||||
|       isCursorInSketchCommandRange( | ||||
|         engineCommandManager.artifactMap, | ||||
|         context.selectionRanges | ||||
|       ), | ||||
|     [engineCommandManager.artifactMap, context.selectionRanges] | ||||
|   ) | ||||
|   const pathId = useMemo(() => { | ||||
|     if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) { | ||||
|       return false | ||||
|     } | ||||
|     return isCursorInSketchCommandRange( | ||||
|       engineCommandManager.artifactMap, | ||||
|       context.selectionRanges | ||||
|     ) | ||||
|   }, [engineCommandManager.artifactMap, context.selectionRanges]) | ||||
|  | ||||
|   function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) { | ||||
|     const span = toolbarButtonsRef.current | ||||
| @ -50,7 +53,9 @@ export const Toolbar = () => { | ||||
|           <li className="contents"> | ||||
|             <ActionButton | ||||
|               Element="button" | ||||
|               onClick={() => send({ type: 'Enter sketch' })} | ||||
|               onClick={() => | ||||
|                 send({ type: 'Enter sketch', data: { forceNewSketch: true } }) | ||||
|               } | ||||
|               icon={{ | ||||
|                 icon: 'sketch', | ||||
|                 bgClassName, | ||||
| @ -119,7 +124,7 @@ export const Toolbar = () => { | ||||
|                 aria-pressed={state.matches('Sketch.Tangential arc to')} | ||||
|                 className="pressed:bg-energy-10/20 dark:pressed:bg-energy-80" | ||||
|                 icon={{ | ||||
|                   icon: 'line', | ||||
|                   icon: 'arc', | ||||
|                   bgClassName, | ||||
|                 }} | ||||
|                 disabled={ | ||||
|  | ||||
| @ -26,7 +26,7 @@ function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } { | ||||
|  | ||||
|   if (DEBUG_SHOW_BOTH_SCENES || !isCamMoving) | ||||
|     return { hideClient: false, hideServer: false } | ||||
|   let hideServer = state.matches('Sketch') || state.matches('Sketch no face') | ||||
|   let hideServer = state.matches('Sketch') | ||||
|   if (isTween) { | ||||
|     hideServer = false | ||||
|   } | ||||
|  | ||||
| @ -22,6 +22,7 @@ import { | ||||
|   DEFAULT_PLANES, | ||||
|   DefaultPlane, | ||||
|   defaultPlaneColor, | ||||
|   getSceneScale, | ||||
|   INTERSECTION_PLANE_LAYER, | ||||
|   isQuaternionVertical, | ||||
|   RAYCASTABLE_PLANE, | ||||
| @ -195,11 +196,18 @@ class SceneEntities { | ||||
|     this.axisGroup = new Group() | ||||
|     const gridHelper = createGridHelper({ size: 100, divisions: 10 }) | ||||
|     gridHelper.renderOrder = -3 // is this working? | ||||
|     gridHelper.name = 'gridHelper' | ||||
|     const sceneScale = getSceneScale( | ||||
|       sceneInfra.camera, | ||||
|       sceneInfra.controls.target | ||||
|     ) | ||||
|     gridHelper.scale.set(sceneScale, sceneScale, sceneScale) | ||||
|     this.axisGroup.add(xAxisMesh, yAxisMesh, gridHelper) | ||||
|     this.currentSketchQuaternion && | ||||
|       this.axisGroup.setRotationFromQuaternion(this.currentSketchQuaternion) | ||||
|  | ||||
|     this.axisGroup.userData = { type: AXIS_GROUP } | ||||
|     this.axisGroup.name = AXIS_GROUP | ||||
|     this.axisGroup.layers.set(SKETCH_LAYER) | ||||
|     this.axisGroup.traverse((child) => { | ||||
|       child.layers.set(SKETCH_LAYER) | ||||
| @ -230,7 +238,17 @@ class SceneEntities { | ||||
|     ast?: Program | ||||
|     draftSegment?: DraftSegment | ||||
|   }) { | ||||
|     sceneInfra.resetMouseListeners() | ||||
|     this.createIntersectionPlane() | ||||
|     const distance = sceneInfra.controls.target.distanceTo( | ||||
|       sceneInfra.camera.position | ||||
|     ) | ||||
|     // TODO this should probably be distance to the sketch group, more important after sketch on face | ||||
|     // since sketches won't always so close to the origin | ||||
|     // is this the best place to adjust camera far? | ||||
|     if (sceneInfra.camera.far < distance * 1.5) { | ||||
|       sceneInfra.camera.far = distance * 2 | ||||
|     } | ||||
|  | ||||
|     const { truncatedAst, programMemoryOverride, variableDeclarationName } = | ||||
|       this.prepareTruncatedMemoryAndAst( | ||||
| @ -315,6 +333,7 @@ class SceneEntities { | ||||
|     if (!draftSegment) { | ||||
|       sceneInfra.setCallbacks({ | ||||
|         onDrag: (args) => { | ||||
|           if (args.event.which !== 1) return | ||||
|           this.onDragSegment({ | ||||
|             ...args, | ||||
|             sketchPathToNode, | ||||
| @ -322,6 +341,7 @@ class SceneEntities { | ||||
|         }, | ||||
|         onMove: () => {}, | ||||
|         onClick: (args) => { | ||||
|           if (args?.event.which !== 1) return | ||||
|           if (!args || !args.object) { | ||||
|             sceneInfra.modelingSend({ | ||||
|               type: 'Set selection', | ||||
| @ -379,6 +399,7 @@ class SceneEntities { | ||||
|         onDrag: () => {}, | ||||
|         onClick: async (args) => { | ||||
|           if (!args) return | ||||
|           if (args.event.which !== 1) return | ||||
|           const { intersection2d } = args | ||||
|           if (!intersection2d) return | ||||
|  | ||||
| @ -775,8 +796,10 @@ class SceneEntities { | ||||
|       }, | ||||
|       onClick: (args) => { | ||||
|         if (!args || !args.object) return | ||||
|         if (args.event.which !== 1) return | ||||
|         const { object, intersection } = args | ||||
|         const type = object?.userData?.type || '' | ||||
|         console.log('intersection.normal?.z', intersection) | ||||
|         const posNorm = Number(intersection.normal?.z) > 0 | ||||
|         let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY' | ||||
|         let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1] | ||||
|  | ||||
| @ -14,17 +14,15 @@ import { | ||||
|   Vector2, | ||||
|   Group, | ||||
|   PlaneGeometry, | ||||
|   EdgesGeometry, | ||||
|   MeshBasicMaterial, | ||||
|   Mesh, | ||||
|   LineSegments, | ||||
|   DoubleSide, | ||||
|   Intersection, | ||||
|   Object3D, | ||||
|   Object3DEventMap, | ||||
| } from 'three' | ||||
| import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' | ||||
| import { engineCommandManager } from 'lang/std/engineConnection' | ||||
| import { EngineCommand, engineCommandManager } from 'lang/std/engineConnection' | ||||
| import { v4 as uuidv4 } from 'uuid' | ||||
| import { isReducedMotion, roundOff, throttle } from 'lib/utils' | ||||
| import { compareVec2Epsilon2 } from 'lang/std/sketch' | ||||
| @ -34,7 +32,7 @@ import * as TWEEN from '@tweenjs/tween.js' | ||||
| import { MouseGuard, cameraMouseDragGuards } from 'lib/cameraControls' | ||||
| import { SourceRange } from 'lang/wasm' | ||||
| import { Axis } from 'lib/selections' | ||||
| import { createGridHelper } from './helpers' | ||||
| import { BaseUnit, SETTINGS_PERSIST_KEY } from 'machines/settingsMachine' | ||||
|  | ||||
| type SendType = ReturnType<typeof useModelingContext>['send'] | ||||
|  | ||||
| @ -66,11 +64,12 @@ interface ThreeCamValues { | ||||
|   quaternion: Quaternion | ||||
|   zoom: number | ||||
|   isPerspective: boolean | ||||
|   target: Vector3 | ||||
| } | ||||
|  | ||||
| const lastCmdDelay = 50 | ||||
|  | ||||
| let lastCmd: any = null | ||||
| let lastCmd: EngineCommand | null = null | ||||
| let lastCmdTime: number = Date.now() | ||||
| let lastCmdTimeoutId: number | null = null | ||||
|  | ||||
| @ -82,14 +81,14 @@ const sendLastReliableChannel = () => { | ||||
| } | ||||
|  | ||||
| const throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => { | ||||
|   const cmd = { | ||||
|   const cmd: EngineCommand = { | ||||
|     type: 'modeling_cmd_req', | ||||
|     cmd_id: uuidv4(), | ||||
|     cmd: { | ||||
|       type: 'default_camera_look_at', | ||||
|       ...convertThreeCamValuesToEngineCam(threeValues), | ||||
|     }, | ||||
|   } as any | ||||
|   } | ||||
|   engineCommandManager.sendSceneCommand(cmd) | ||||
|   lastCmd = cmd | ||||
|   lastCmdTime = Date.now() | ||||
| @ -103,7 +102,7 @@ const throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => { | ||||
|   ) as any as number | ||||
| }, 1000 / 30) | ||||
|  | ||||
| let lastPerspectiveCmd: any = null | ||||
| let lastPerspectiveCmd: EngineCommand | null = null | ||||
| let lastPerspectiveCmdTime: number = Date.now() | ||||
| let lastPerspectiveCmdTimeoutId: number | null = null | ||||
|  | ||||
| @ -123,8 +122,9 @@ const throttledUpdateEngineFov = throttle( | ||||
|     quaternion: Quaternion | ||||
|     zoom: number | ||||
|     fov: number | ||||
|     target: Vector3 | ||||
|   }) => { | ||||
|     const cmd = { | ||||
|     const cmd: EngineCommand = { | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd_id: uuidv4(), | ||||
|       cmd: { | ||||
| @ -136,7 +136,7 @@ const throttledUpdateEngineFov = throttle( | ||||
|         fov_y: vals.fov, | ||||
|         ...calculateNearFarFromFOV(vals.fov), | ||||
|       }, | ||||
|     } as any | ||||
|     } | ||||
|     engineCommandManager.sendSceneCommand(cmd) | ||||
|     lastPerspectiveCmd = cmd | ||||
|     lastPerspectiveCmdTime = Date.now() | ||||
| @ -225,6 +225,15 @@ class SceneInfra { | ||||
|     this.onMouseLeave = callbacks.onMouseLeave || this.onMouseLeave | ||||
|     this.selected = null // following selections between callbacks being set is too tricky | ||||
|   } | ||||
|   resetMouseListeners = () => { | ||||
|     sceneInfra.setCallbacks({ | ||||
|       onDrag: () => {}, | ||||
|       onMove: () => {}, | ||||
|       onClick: () => {}, | ||||
|       onMouseEnter: () => {}, | ||||
|       onMouseLeave: () => {}, | ||||
|     }) | ||||
|   } | ||||
|   highlightCallback: (a: SourceRange) => void = () => {} | ||||
|   setHighlightCallback(cb: (a: SourceRange) => void) { | ||||
|     this.highlightCallback = cb | ||||
| @ -297,8 +306,17 @@ class SceneInfra { | ||||
|     this.scene.background = null | ||||
|  | ||||
|     // CAMERA | ||||
|     const camHeightDistanceRatio = 0.5 | ||||
|     const baseUnit: BaseUnit = | ||||
|       JSON.parse(localStorage?.getItem(SETTINGS_PERSIST_KEY) || ('{}' as any)) | ||||
|         .baseUnit || 'mm' | ||||
|     const baseRadius = 5.6 | ||||
|     const length = baseUnitTomm(baseUnit) * baseRadius | ||||
|     const ang = Math.atan(camHeightDistanceRatio) | ||||
|     const x = Math.cos(ang) * length | ||||
|     const y = Math.sin(ang) * length | ||||
|     this.camera = this.createPerspectiveCamera() | ||||
|     this.camera.position.set(0, -128, 64) | ||||
|     this.camera.position.set(0, -x, y) | ||||
|     if (DEBUG_SHOW_INTERSECTION_PLANE) | ||||
|       this.camera.layers.enable(INTERSECTION_PLANE_LAYER) | ||||
|  | ||||
| @ -402,12 +420,20 @@ class SceneInfra { | ||||
|   }, 200) | ||||
|  | ||||
|   onCameraChange = () => { | ||||
|     this.camera.position.distanceTo(this.controls.target) | ||||
|     const scale = getSceneScale(this.camera, this.controls.target) | ||||
|     const planesGroup = this.scene.getObjectByName(DEFAULT_PLANES) | ||||
|     const axisGroup = this.scene | ||||
|       .getObjectByName(AXIS_GROUP) | ||||
|       ?.getObjectByName('gridHelper') | ||||
|     planesGroup && planesGroup.scale.set(scale, scale, scale) | ||||
|     axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale) | ||||
|  | ||||
|     throttledUpdateEngineCamera({ | ||||
|       quaternion: this.camera.quaternion, | ||||
|       position: this.camera.position, | ||||
|       zoom: this.camera.zoom, | ||||
|       isPerspective: this.isPerspective, | ||||
|       target: this.controls.target, | ||||
|     }) | ||||
|     this.deferReactUpdate({ | ||||
|       type: | ||||
| @ -453,9 +479,27 @@ class SceneInfra { | ||||
|     if (!this.isFovAnimationInProgress) | ||||
|       this.renderer.render(this.scene, this.camera) | ||||
|   } | ||||
|   tweenCameraToQuaternion( | ||||
|   async tweenCameraToQuaternion( | ||||
|     targetQuaternion: Quaternion, | ||||
|     duration: number = 500 | ||||
|     duration = 500, | ||||
|     toOrthographic = true | ||||
|   ): Promise<void> { | ||||
|     const isVertical = isQuaternionVertical(targetQuaternion) | ||||
|     let _duration = duration | ||||
|     if (isVertical) { | ||||
|       _duration = duration * 0.6 | ||||
|       await this._tweenCameraToQuaternion(new Quaternion(), _duration, false) | ||||
|     } | ||||
|     await this._tweenCameraToQuaternion( | ||||
|       targetQuaternion, | ||||
|       _duration, | ||||
|       toOrthographic | ||||
|     ) | ||||
|   } | ||||
|   _tweenCameraToQuaternion( | ||||
|     targetQuaternion: Quaternion, | ||||
|     duration = 500, | ||||
|     toOrthographic = false | ||||
|   ): Promise<void> { | ||||
|     return new Promise((resolve) => { | ||||
|       const camera = this.camera | ||||
| @ -489,10 +533,10 @@ class SceneInfra { | ||||
|       } | ||||
|  | ||||
|       const onComplete = async () => { | ||||
|         if (isReducedMotion()) { | ||||
|         if (isReducedMotion() && toOrthographic) { | ||||
|           cameraAtTime(0.99) | ||||
|           this.useOrthographicCamera() | ||||
|         } else { | ||||
|         } else if (toOrthographic) { | ||||
|           await this.animateToOrthographic() | ||||
|         } | ||||
|         if (isVertical) cameraAtTime(1) | ||||
| @ -626,10 +670,21 @@ class SceneInfra { | ||||
|     const { x: px, y: py, z: pz } = this.camera.position | ||||
|     const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion | ||||
|     const { x: tx, y: ty, z: tz } = this.controls.target | ||||
|     const zoom = this.camera.zoom | ||||
|     this.camera = this.createPerspectiveCamera() | ||||
|  | ||||
|     this.camera.position.set(px, py, pz) | ||||
|     this.camera.quaternion.set(qx, qy, qz, qw) | ||||
|     const zoomFudgeFactor = 2280 | ||||
|     const distance = zoomFudgeFactor / (zoom * this.fov) | ||||
|     const direction = new Vector3().subVectors( | ||||
|       this.camera.position, | ||||
|       this.controls.target | ||||
|     ) | ||||
|     direction.normalize() | ||||
|     this.camera.position | ||||
|       .copy(this.controls.target) | ||||
|       .addScaledVector(direction, distance) | ||||
|  | ||||
|     this.setupOrbitControls([tx, ty, tz]) | ||||
|  | ||||
| @ -704,6 +759,7 @@ class SceneInfra { | ||||
|       position: newPosition, | ||||
|       quaternion: this.camera.quaternion, | ||||
|       zoom: this.camera.zoom, | ||||
|       target: this.controls.target, | ||||
|     }) | ||||
|   } | ||||
|   getPlaneIntersectPoint = (): { | ||||
| @ -929,21 +985,14 @@ class SceneInfra { | ||||
|       type: DefaultPlane | ||||
|     ): Mesh => { | ||||
|       const planeGeometry = new PlaneGeometry(100, 100) | ||||
|       const planeEdges = new EdgesGeometry(planeGeometry) | ||||
|       const lineMaterial = new LineBasicMaterial({ | ||||
|         color: defaultPlaneColor(type, 0.45, 1), | ||||
|         opacity: 0.9, | ||||
|       }) | ||||
|       const planeMaterial = new MeshBasicMaterial({ | ||||
|         color: defaultPlaneColor(type), | ||||
|         transparent: true, | ||||
|         opacity: 0.35, | ||||
|         opacity: 0.0, | ||||
|         side: DoubleSide, | ||||
|         depthTest: false, // needed to avoid transparency issues | ||||
|       }) | ||||
|       const plane = new Mesh(planeGeometry, planeMaterial) | ||||
|       const edges = new LineSegments(planeEdges, lineMaterial) | ||||
|       plane.add(edges) | ||||
|       plane.rotation.x = rotation.x | ||||
|       plane.rotation.y = rotation.y | ||||
|       plane.rotation.z = rotation.z | ||||
| @ -951,15 +1000,14 @@ class SceneInfra { | ||||
|       plane.name = type | ||||
|       return plane | ||||
|     } | ||||
|     const gridHelper = createGridHelper({ size: 100, divisions: 10 }) | ||||
|     const planes = [ | ||||
|       addPlane({ x: 0, y: Math.PI / 2, z: 0 }, YZ_PLANE), | ||||
|       addPlane({ x: 0, y: 0, z: 0 }, XY_PLANE), | ||||
|       addPlane({ x: -Math.PI / 2, y: 0, z: 0 }, XZ_PLANE), | ||||
|       gridHelper, | ||||
|     ] | ||||
|     const planesGroup = new Group() | ||||
|     planesGroup.userData.type = DEFAULT_PLANES | ||||
|     planesGroup.name = DEFAULT_PLANES | ||||
|     planesGroup.add(...planes) | ||||
|     planesGroup.traverse((child) => { | ||||
|       if (child instanceof Mesh) { | ||||
| @ -967,6 +1015,8 @@ class SceneInfra { | ||||
|       } | ||||
|     }) | ||||
|     planesGroup.layers.enable(SKETCH_LAYER) | ||||
|     const sceneScale = getSceneScale(this.camera, this.controls.target) | ||||
|     planesGroup.scale.set(sceneScale, sceneScale, sceneScale) | ||||
|     this.scene.add(planesGroup) | ||||
|   } | ||||
|   removeDefaultPlanes() { | ||||
| @ -1001,6 +1051,7 @@ class SceneInfra { | ||||
| export const sceneInfra = new SceneInfra() | ||||
|  | ||||
| function convertThreeCamValuesToEngineCam({ | ||||
|   target, | ||||
|   position, | ||||
|   quaternion, | ||||
|   zoom, | ||||
| @ -1023,7 +1074,7 @@ function convertThreeCamValuesToEngineCam({ | ||||
|   const upVector = new Vector3(0, 1, 0).applyEuler(euler).normalize() | ||||
|   if (isPerspective) { | ||||
|     return { | ||||
|       center: lookAtVector, | ||||
|       center: target, | ||||
|       up: upVector, | ||||
|       vantage: position, | ||||
|     } | ||||
| @ -1045,6 +1096,41 @@ function calculateNearFarFromFOV(fov: number) { | ||||
|   return { z_near: 0.1, z_far } | ||||
| } | ||||
|  | ||||
| export function getSceneScale( | ||||
|   camera: PerspectiveCamera | OrthographicCamera, | ||||
|   target: Vector3 | ||||
| ): number { | ||||
|   const distance = | ||||
|     camera instanceof PerspectiveCamera | ||||
|       ? camera.position.distanceTo(target) | ||||
|       : 63.7942123 / camera.zoom | ||||
|  | ||||
|   if (distance <= 20) return 0.1 | ||||
|   else if (distance > 20 && distance <= 200) return 1 | ||||
|   else if (distance > 200 && distance <= 2000) return 10 | ||||
|   else if (distance > 2000 && distance <= 20000) return 100 | ||||
|   else if (distance > 20000) return 1000 | ||||
|  | ||||
|   return 1 | ||||
| } | ||||
|  | ||||
| function baseUnitTomm(baseUnit: BaseUnit) { | ||||
|   switch (baseUnit) { | ||||
|     case 'mm': | ||||
|       return 1 | ||||
|     case 'cm': | ||||
|       return 10 | ||||
|     case 'm': | ||||
|       return 1000 | ||||
|     case 'in': | ||||
|       return 25.4 | ||||
|     case 'ft': | ||||
|       return 304.8 | ||||
|     case 'yd': | ||||
|       return 914.4 | ||||
|   } | ||||
| } | ||||
|  | ||||
| export function isQuaternionVertical(q: Quaternion) { | ||||
|   const v = new Vector3(0, 0, 1).applyQuaternion(q) | ||||
|   // no x or y components means it's vertical | ||||
|  | ||||
| @ -27,6 +27,7 @@ export const CommandBarProvider = ({ | ||||
| }) => { | ||||
|   const { pathname } = useLocation() | ||||
|   const [commandBarState, commandBarSend] = useMachine(commandBarMachine, { | ||||
|     devTools: true, | ||||
|     guards: { | ||||
|       'Arguments are ready': (context, _) => { | ||||
|         return context.selectedCommand?.args | ||||
|  | ||||
| @ -48,6 +48,7 @@ function CommandComboBox({ | ||||
|               (event.metaKey && event.key === 'k') || | ||||
|               (event.key === 'Backspace' && !event.currentTarget.value) | ||||
|             ) { | ||||
|               event.preventDefault() | ||||
|               commandBarSend({ type: 'Close' }) | ||||
|             } | ||||
|           }} | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| export type CustomIconName = | ||||
|   | 'arc' | ||||
|   | 'arrowDown' | ||||
|   | 'arrowLeft' | ||||
|   | 'arrowRight' | ||||
| @ -22,6 +23,7 @@ export type CustomIconName = | ||||
|   | 'networkCrossedOut' | ||||
|   | 'parallel' | ||||
|   | 'search' | ||||
|   | 'settings' | ||||
|   | 'sketch' | ||||
|   | 'vertical' | ||||
|  | ||||
| @ -32,6 +34,22 @@ export const CustomIcon = ({ | ||||
|   name: CustomIconName | ||||
| } & React.SVGProps<SVGSVGElement>) => { | ||||
|   switch (name) { | ||||
|     case 'arc': | ||||
|       return ( | ||||
|         <svg | ||||
|           {...props} | ||||
|           viewBox="0 0 20 20" | ||||
|           fill="none" | ||||
|           xmlns="http://www.w3.org/2000/svg" | ||||
|         > | ||||
|           <path | ||||
|             fillRule="evenodd" | ||||
|             clipRule="evenodd" | ||||
|             d="M10 1.5C8.60217 1.5 7.22591 1.84474 5.99313 2.50367C4.76035 3.1626 3.70911 4.1154 2.93251 5.27765C2.15592 6.43991 1.67794 7.77575 1.54093 9.16685C1.40392 10.558 1.6121 11.9614 2.14703 13.2528C2.68195 14.5442 3.52712 15.6838 4.60766 16.5706C5.6882 17.4574 6.97076 18.064 8.34173 18.3367C9.71271 18.6094 11.1298 18.5398 12.4674 18.134C13.8051 17.7282 15.022 16.9988 16.0104 16.0104C16.3068 15.714 16.5796 15.3974 16.8273 15.0634L16.0241 14.4677C15.8055 14.7624 15.5648 15.0418 15.3033 15.3033C14.4312 16.1754 13.3574 16.819 12.1771 17.1771C10.9969 17.5351 9.74651 17.5965 8.53682 17.3559C7.32714 17.1153 6.19547 16.58 5.24205 15.7976C4.28863 15.0151 3.5429 14.0096 3.07091 12.8701C2.59891 11.7306 2.41522 10.4923 2.53612 9.26487C2.65701 8.03743 3.07875 6.85874 3.76398 5.83322C4.44921 4.8077 5.37678 3.967 6.46453 3.38559C7.55227 2.80418 8.76662 2.5 10 2.5C10.3699 2.5 10.7376 2.52734 11.1005 2.58117L11.2472 1.59199C10.836 1.53099 10.4192 1.5 10 1.5ZM13.2067 3.22008C13.5383 3.37691 13.8593 3.5585 14.1668 3.76398C14.4743 3.96946 14.7649 4.19652 15.0367 4.44286L15.7083 3.70191C15.4002 3.42271 15.0709 3.16538 14.7223 2.93251C14.3738 2.69964 14.0101 2.49384 13.6342 2.31609L13.2067 3.22008ZM16.433 6.14423C16.6216 6.45886 16.7876 6.78818 16.9291 7.12987C17.0706 7.47157 17.1861 7.82181 17.2752 8.17765L18.2453 7.93467C18.1443 7.53138 18.0134 7.13444 17.853 6.74719C17.6926 6.35995 17.5044 5.98672 17.2907 5.63012L16.433 6.14423ZM17.491 10.368C17.473 10.7344 17.428 11.1004 17.3559 11.4632C17.2837 11.8259 17.1852 12.1813 17.0616 12.5267L18.0031 12.8636C18.1432 12.4721 18.2549 12.0694 18.3367 11.6583C18.4184 11.2472 18.4694 10.8323 18.4898 10.4171L17.491 10.368Z" | ||||
|             fill="currentColor" | ||||
|           /> | ||||
|         </svg> | ||||
|       ) | ||||
|     case 'arrowDown': | ||||
|       return ( | ||||
|         <svg | ||||
| @ -121,8 +139,8 @@ export const CustomIcon = ({ | ||||
|           xmlns="http://www.w3.org/2000/svg" | ||||
|         > | ||||
|           <path | ||||
|             fill-rule="evenodd" | ||||
|             clip-rule="evenodd" | ||||
|             fillRule="evenodd" | ||||
|             clipRule="evenodd" | ||||
|             d="M6.5 3H7L13 3L13.5 3V3.5V4.00001L15.5 4.00002L16 4.00002V4.50002V10.0351C15.6905 9.85609 15.3548 9.71733 15 9.62602V5.00002L13.5 5.00001V6.50001V7.00001L13 7.00001L7 7.00001L6.5 7.00001V6.50001V5.00001L5 5.00001V16H10.8773C11.2024 16.4055 11.6047 16.7463 12.062 17H4.5H4V16.5V4.50001V4.00001L4.5 4.00001L6.5 4.00001V3.5V3ZM15.938 17C15.9588 16.9885 15.9794 16.9768 16 16.9649V17H15.938ZM7.5 4V4.50001V6.00001L12.5 6.00001V4.50001V4L7.5 4ZM13 9H7V8H13V9ZM15.6855 11.5L13.2101 14.8005L12.2071 13.7975L11.5 14.5046L12.9107 15.9153L13.6642 15.8617L16.4855 12.1L15.6855 11.5Z" | ||||
|             fill="currentColor" | ||||
|           /> | ||||
| @ -137,8 +155,8 @@ export const CustomIcon = ({ | ||||
|           xmlns="http://www.w3.org/2000/svg" | ||||
|         > | ||||
|           <path | ||||
|             fill-rule="evenodd" | ||||
|             clip-rule="evenodd" | ||||
|             fillRule="evenodd" | ||||
|             clipRule="evenodd" | ||||
|             d="M6.5 3H7L13 3L13.5 3V3.5V4.00001L15.5 4.00002L16 4.00002V4.50002V10.0351C15.6905 9.85609 15.3548 9.71733 15 9.62602V5.00002L13.5 5.00001V6.50001V7.00001L13 7.00001L7 7.00001L6.5 7.00001V6.50001V5.00001L5 5.00001V16H10.8773C11.2024 16.4055 11.6047 16.7463 12.062 17H4.5H4V16.5V4.50001V4.00001L4.5 4.00001L6.5 4.00001V3.5V3ZM15.938 17C15.9588 16.9885 15.9794 16.9768 16 16.9649V17H15.938ZM7.5 4V4.50001V6.00001L12.5 6.00001V4.50001V4L7.5 4ZM13 9H7V8H13V9ZM13.5 11V13H11.5V14H13.5V16H14.5V14H16.5V13H14.5V11H13.5Z" | ||||
|             fill="currentColor" | ||||
|           /> | ||||
| @ -295,8 +313,8 @@ export const CustomIcon = ({ | ||||
|           xmlns="http://www.w3.org/2000/svg" | ||||
|         > | ||||
|           <path | ||||
|             fill-rule="evenodd" | ||||
|             clip-rule="evenodd" | ||||
|             fillRule="evenodd" | ||||
|             clipRule="evenodd" | ||||
|             d="M14 10.5H6V9.5H14V10.5Z" | ||||
|             fill="currentColor" | ||||
|           /> | ||||
| @ -343,8 +361,8 @@ export const CustomIcon = ({ | ||||
|           xmlns="http://www.w3.org/2000/svg" | ||||
|         > | ||||
|           <path | ||||
|             fill-rule="evenodd" | ||||
|             clip-rule="evenodd" | ||||
|             fillRule="evenodd" | ||||
|             clipRule="evenodd" | ||||
|             d="M18 9.64741C17.1925 8.24871 16.0344 7.08457 14.6399 6.26971C13.2455 5.45486 11.6628 5.01742 10.0478 5.00051C8.4328 4.9836 6.84127 5.38779 5.43006 6.17326C4.01884 6.95873 2.83666 8.09837 2 9.47985L2.76881 9.94546C3.52456 8.69756 4.59243 7.66813 5.86718 6.95862C7.14193 6.2491 8.57955 5.88399 10.0384 5.89927C11.4972 5.91455 12.9269 6.30968 14.1865 7.04574C15.4461 7.7818 16.4922 8.83337 17.2216 10.0968L18 9.64741ZM15.2155 11.0953C14.6772 10.1628 13.9051 9.3867 12.9755 8.84347C12.0459 8.30023 10.9907 8.00861 9.91406 7.99733C8.8374 7.98606 7.77638 8.25552 6.83557 8.77917C5.89476 9.30281 5.10664 10.0626 4.54887 10.9836L5.34391 11.4651C5.81802 10.6822 6.48792 10.0364 7.28761 9.59132C8.0873 9.14622 8.98916 8.91718 9.90432 8.92676C10.8195 8.93635 11.7164 9.18423 12.5065 9.64598C13.2967 10.1077 13.953 10.7674 14.4106 11.56L15.2155 11.0953ZM10 14C10.8284 14 11.5 13.3284 11.5 12.5C11.5 11.6716 10.8284 11 10 11C9.17157 11 8.5 11.6716 8.5 12.5C8.5 13.3284 9.17157 14 10 14Z" | ||||
|             fill="currentColor" | ||||
|           /> | ||||
| @ -359,8 +377,8 @@ export const CustomIcon = ({ | ||||
|           xmlns="http://www.w3.org/2000/svg" | ||||
|         > | ||||
|           <path | ||||
|             fill-rule="evenodd" | ||||
|             clip-rule="evenodd" | ||||
|             fillRule="evenodd" | ||||
|             clipRule="evenodd" | ||||
|             d="M4.35352 5.39647L14.253 15.296L14.9601 14.5889L5.06062 4.68936L4.35352 5.39647ZM12.5065 9.64599C11.9609 9.32713 11.3643 9.11025 10.746 9.00341L9.74058 7.99796C9.79835 7.99694 9.85618 7.99674 9.91406 7.99735C10.9907 8.00862 12.0459 8.30025 12.9755 8.84348C13.9051 9.38672 14.6772 10.1628 15.2155 11.0953L14.4106 11.56C13.953 10.7674 13.2967 10.1077 12.5065 9.64599ZM6.48788 8.98789L7.16295 9.66297C6.41824 10.1045 5.79317 10.7233 5.34391 11.4651L4.54887 10.9836C5.03646 10.1785 5.70009 9.49656 6.48788 8.98789ZM10.0384 5.89928C9.3134 5.89169 8.59366 5.97804 7.89655 6.15392L7.16867 5.42605C8.09637 5.13507 9.06776 4.99026 10.0478 5.00052C11.6628 5.01744 13.2455 5.45488 14.6399 6.26973C16.0344 7.08458 17.1925 8.24872 18 9.64742L17.2216 10.0968C16.4922 8.83338 15.4461 7.78181 14.1865 7.04575C12.9269 6.3097 11.4972 5.91456 10.0384 5.89928ZM5.00782 7.50783L4.36522 6.86524C3.42033 7.57557 2.61639 8.46208 2 9.47986L2.76881 9.94547C3.34775 8.98952 4.10986 8.16177 5.00782 7.50783ZM10 14C10.4142 14 10.7892 13.8321 11.0607 13.5607L8.93934 11.4394C8.66789 11.7108 8.5 12.0858 8.5 12.5C8.5 13.3284 9.17157 14 10 14Z" | ||||
|             fill="currentColor" | ||||
|           /> | ||||
| @ -398,6 +416,22 @@ export const CustomIcon = ({ | ||||
|           /> | ||||
|         </svg> | ||||
|       ) | ||||
|     case 'settings': | ||||
|       return ( | ||||
|         <svg | ||||
|           {...props} | ||||
|           viewBox="0 0 20 20" | ||||
|           fill="none" | ||||
|           xmlns="http://www.w3.org/2000/svg" | ||||
|         > | ||||
|           <path | ||||
|             fillRule="evenodd" | ||||
|             clipRule="evenodd" | ||||
|             d="M8 5.5C8 5.77614 7.77614 6 7.5 6C7.22386 6 7 5.77614 7 5.5C7 5.22386 7.22386 5 7.5 5C7.77614 5 8 5.22386 8 5.5ZM6.08535 6C6.29127 6.5826 6.84689 7 7.5 7C8.32843 7 9 6.32843 9 5.5C9 4.67157 8.32843 4 7.5 4C6.84689 4 6.29127 4.4174 6.08535 5H5V6H6.08535ZM15 6H9.94999C9.98278 5.83844 10 5.67123 10 5.5C10 5.32877 9.98278 5.16155 9.94999 5H15V6ZM11 14.5C11 14.7761 10.7761 15 10.5 15C10.2239 15 10 14.7761 10 14.5C10 14.2239 10.2239 14 10.5 14C10.7761 14 11 14.2239 11 14.5ZM9.08535 15C9.29127 15.5826 9.84689 16 10.5 16C11.3284 16 12 15.3284 12 14.5C12 13.6716 11.3284 13 10.5 13C9.84689 13 9.29127 13.4174 9.08535 14H5V15H9.08535ZM15 15H12.95C12.9828 14.8384 13 14.6712 13 14.5C13 14.3288 12.9828 14.1616 12.95 14H15V15ZM11.5 10.5C11.7761 10.5 12 10.2761 12 10C12 9.72386 11.7761 9.5 11.5 9.5C11.2239 9.5 11 9.72386 11 10C11 10.2761 11.2239 10.5 11.5 10.5ZM11.5 8.5C12.1531 8.5 12.7087 8.9174 12.9146 9.5H15V10.5H12.9146C12.7087 11.0826 12.1531 11.5 11.5 11.5C10.6716 11.5 10 10.8284 10 10C10 9.17157 10.6716 8.5 11.5 8.5ZM9.05001 10.5C9.01722 10.3384 9 10.1712 9 10C9 9.82877 9.01722 9.66155 9.05001 9.5H5V10.5H9.05001Z" | ||||
|             fill="currentColor" | ||||
|           /> | ||||
|         </svg> | ||||
|       ) | ||||
|     case 'sketch': | ||||
|       return ( | ||||
|         <svg | ||||
|  | ||||
| @ -30,6 +30,12 @@ type GlobalContext = { | ||||
|   settings: MachineContext<typeof settingsMachine> | ||||
| } | ||||
|  | ||||
| // a little hacky for sure, open to changing it | ||||
| // this implies that we should only even have one instance of this provider mounted at any one time | ||||
| // but I think that's a safe assumption | ||||
| let settingsStateRef: (typeof settingsMachine)['context'] | undefined | ||||
| export const getSettingsState = () => settingsStateRef | ||||
|  | ||||
| export const GlobalStateContext = createContext({} as GlobalContext) | ||||
|  | ||||
| export const GlobalStateProvider = ({ | ||||
| @ -71,6 +77,7 @@ export const GlobalStateProvider = ({ | ||||
|       }, | ||||
|     }, | ||||
|   }) | ||||
|   settingsStateRef = settingsState.context | ||||
|  | ||||
|   useStateMachineCommands({ | ||||
|     machineId: 'settings', | ||||
|  | ||||
| @ -37,6 +37,7 @@ import { sceneInfra } from 'clientSideScene/sceneInfra' | ||||
| import { getSketchQuaternion } from 'clientSideScene/sceneEntities' | ||||
| import { startSketchOnDefault } from 'lang/modifyAst' | ||||
| import { Program } from 'lang/wasm' | ||||
| import { isSingleCursorInPipe } from 'lang/queryAst' | ||||
|  | ||||
| type MachineContext<T extends AnyStateMachine> = { | ||||
|   state: StateFrom<T> | ||||
| @ -182,7 +183,10 @@ export const ModelingMachineProvider = ({ | ||||
|  | ||||
|           return canExtrudeSelection(selectionRanges) | ||||
|         }, | ||||
|         'Selection is one face': ({ selectionRanges }) => { | ||||
|         'Selection is on face': ({ selectionRanges }, { data }) => { | ||||
|           if (data?.forceNewSketch) return false | ||||
|           if (!isSingleCursorInPipe(selectionRanges, kclManager.ast)) | ||||
|             return false | ||||
|           return !!isCursorInSketchCommandRange( | ||||
|             engineCommandManager.artifactMap, | ||||
|             selectionRanges | ||||
| @ -199,6 +203,7 @@ export const ModelingMachineProvider = ({ | ||||
|           await kclManager.executeAstMock(newAst, { updates: 'code' }) | ||||
|           sceneInfra.setCallbacks({ | ||||
|             onClick: () => {}, | ||||
|             onDrag: () => {}, | ||||
|           }) | ||||
|         }, | ||||
|         'animate-to-face': async (_, { data: { plane, normal } }) => { | ||||
|  | ||||
| @ -3,9 +3,9 @@ import ReactCodeMirror, { | ||||
|   ViewUpdate, | ||||
|   keymap, | ||||
| } from '@uiw/react-codemirror' | ||||
| import { FromServer, IntoServer } from 'editor/lsp/codec' | ||||
| import Server from '../editor/lsp/server' | ||||
| import Client from '../editor/lsp/client' | ||||
| import { FromServer, IntoServer } from 'editor/plugins/lsp/codec' | ||||
| import Server from '../editor/plugins/lsp/server' | ||||
| import Client from '../editor/plugins/lsp/client' | ||||
| import { TEST } from 'env' | ||||
| import { useCommandsContext } from 'hooks/useCommandsContext' | ||||
| import { useGlobalStateContext } from 'hooks/useGlobalStateContext' | ||||
| @ -15,8 +15,8 @@ import { useMemo, useRef } from 'react' | ||||
| import { linter, lintGutter } from '@codemirror/lint' | ||||
| import { useStore } from 'useStore' | ||||
| import { processCodeMirrorRanges } from 'lib/selections' | ||||
| import { LanguageServerClient } from 'editor/lsp' | ||||
| import kclLanguage from 'editor/lsp/language' | ||||
| import { LanguageServerClient } from 'editor/plugins/lsp' | ||||
| import kclLanguage from 'editor/plugins/lsp/kcl/language' | ||||
| import { EditorView, lineHighlightField } from 'editor/highlightextension' | ||||
| import { roundOff } from 'lib/utils' | ||||
| import { kclErrToDiagnostic } from 'lang/errors' | ||||
| @ -27,6 +27,9 @@ import { engineCommandManager } from '../lang/std/engineConnection' | ||||
| import { kclManager, useKclContext } from 'lang/KclSingleton' | ||||
| import { ModelingMachineEvent } from 'machines/modelingMachine' | ||||
| import { sceneInfra } from 'clientSideScene/sceneInfra' | ||||
| import { copilotPlugin } from 'editor/plugins/lsp/copilot' | ||||
| import { isTauri } from 'lib/isTauri' | ||||
| import type * as LSP from 'vscode-languageserver-protocol' | ||||
|  | ||||
| export const editorShortcutMeta = { | ||||
|   formatCode: { | ||||
| @ -39,6 +42,15 @@ export const editorShortcutMeta = { | ||||
|   }, | ||||
| } | ||||
|  | ||||
| function getWorkspaceFolders(): LSP.WorkspaceFolder[] { | ||||
|   // We only use workspace folders in Tauri since that is where we use more than | ||||
|   // one file. | ||||
|   if (isTauri()) { | ||||
|     return [{ uri: 'file://', name: 'ProjectRoot' }] | ||||
|   } | ||||
|   return [] | ||||
| } | ||||
|  | ||||
| export const TextEditor = ({ | ||||
|   theme, | ||||
| }: { | ||||
| @ -46,15 +58,19 @@ export const TextEditor = ({ | ||||
| }) => { | ||||
|   const { | ||||
|     editorView, | ||||
|     isLSPServerReady, | ||||
|     isKclLspServerReady, | ||||
|     isCopilotLspServerReady, | ||||
|     setEditorView, | ||||
|     setIsLSPServerReady, | ||||
|     setIsKclLspServerReady, | ||||
|     setIsCopilotLspServerReady, | ||||
|     isShiftDown, | ||||
|   } = useStore((s) => ({ | ||||
|     editorView: s.editorView, | ||||
|     isLSPServerReady: s.isLSPServerReady, | ||||
|     isKclLspServerReady: s.isKclLspServerReady, | ||||
|     isCopilotLspServerReady: s.isCopilotLspServerReady, | ||||
|     setEditorView: s.setEditorView, | ||||
|     setIsLSPServerReady: s.setIsLSPServerReady, | ||||
|     setIsKclLspServerReady: s.setIsKclLspServerReady, | ||||
|     setIsCopilotLspServerReady: s.setIsCopilotLspServerReady, | ||||
|     isShiftDown: s.isShiftDown, | ||||
|   })) | ||||
|   const { code, errors } = useKclContext() | ||||
| @ -66,7 +82,7 @@ export const TextEditor = ({ | ||||
|     state, | ||||
|   } = useModelingContext() | ||||
|  | ||||
|   const { settings: { context: { textWrapping } = {} } = {} } = | ||||
|   const { settings: { context: { textWrapping } = {} } = {}, auth } = | ||||
|     useGlobalStateContext() | ||||
|   const { commandBarSend } = useCommandsContext() | ||||
|   const { enable: convertEnabled, handleClick: convertCallback } = | ||||
| @ -75,20 +91,20 @@ export const TextEditor = ({ | ||||
|   // So this is a bit weird, we need to initialize the lsp server and client. | ||||
|   // But the server happens async so we break this into two parts. | ||||
|   // Below is the client and server promise. | ||||
|   const { lspClient } = useMemo(() => { | ||||
|   const { lspClient: kclLspClient } = useMemo(() => { | ||||
|     const intoServer: IntoServer = new IntoServer() | ||||
|     const fromServer: FromServer = FromServer.create() | ||||
|     const client = new Client(fromServer, intoServer) | ||||
|     if (!TEST) { | ||||
|       Server.initialize(intoServer, fromServer).then((lspServer) => { | ||||
|         lspServer.start() | ||||
|         setIsLSPServerReady(true) | ||||
|         lspServer.start('kcl') | ||||
|         setIsKclLspServerReady(true) | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     const lspClient = new LanguageServerClient({ client }) | ||||
|     const lspClient = new LanguageServerClient({ client, name: 'kcl' }) | ||||
|     return { lspClient } | ||||
|   }, [setIsLSPServerReady]) | ||||
|   }, [setIsKclLspServerReady]) | ||||
|  | ||||
|   // Here we initialize the plugin which will start the client. | ||||
|   // When we have multi-file support the name of the file will be a dep of | ||||
| @ -97,19 +113,57 @@ export const TextEditor = ({ | ||||
|   // We do not want to restart the server, its just wasteful. | ||||
|   const kclLSP = useMemo(() => { | ||||
|     let plugin = null | ||||
|     if (isLSPServerReady && !TEST) { | ||||
|     if (isKclLspServerReady && !TEST) { | ||||
|       // Set up the lsp plugin. | ||||
|       const lsp = kclLanguage({ | ||||
|         // When we have more than one file, we'll need to change this. | ||||
|         documentUri: `file:///we-just-have-one-file-for-now.kcl`, | ||||
|         workspaceFolders: null, | ||||
|         client: lspClient, | ||||
|         workspaceFolders: getWorkspaceFolders(), | ||||
|         client: kclLspClient, | ||||
|       }) | ||||
|  | ||||
|       plugin = lsp | ||||
|     } | ||||
|     return plugin | ||||
|   }, [lspClient, isLSPServerReady]) | ||||
|   }, [kclLspClient, isKclLspServerReady]) | ||||
|  | ||||
|   const { lspClient: copilotLspClient } = useMemo(() => { | ||||
|     const intoServer: IntoServer = new IntoServer() | ||||
|     const fromServer: FromServer = FromServer.create() | ||||
|     const client = new Client(fromServer, intoServer) | ||||
|     if (!TEST) { | ||||
|       Server.initialize(intoServer, fromServer).then((lspServer) => { | ||||
|         const token = auth?.context?.token | ||||
|         lspServer.start('copilot', token) | ||||
|         setIsCopilotLspServerReady(true) | ||||
|       }) | ||||
|     } | ||||
|  | ||||
|     const lspClient = new LanguageServerClient({ client, name: 'copilot' }) | ||||
|     return { lspClient } | ||||
|   }, [setIsCopilotLspServerReady]) | ||||
|  | ||||
|   // Here we initialize the plugin which will start the client. | ||||
|   // When we have multi-file support the name of the file will be a dep of | ||||
|   // this use memo, as well as the directory structure, which I think is | ||||
|   // a good setup because it will restart the client but not the server :) | ||||
|   // We do not want to restart the server, its just wasteful. | ||||
|   const copilotLSP = useMemo(() => { | ||||
|     let plugin = null | ||||
|     if (isCopilotLspServerReady && !TEST) { | ||||
|       // Set up the lsp plugin. | ||||
|       const lsp = copilotPlugin({ | ||||
|         // When we have more than one file, we'll need to change this. | ||||
|         documentUri: `file:///we-just-have-one-file-for-now.kcl`, | ||||
|         workspaceFolders: getWorkspaceFolders(), | ||||
|         client: copilotLspClient, | ||||
|         allowHTMLContent: true, | ||||
|       }) | ||||
|  | ||||
|       plugin = lsp | ||||
|     } | ||||
|     return plugin | ||||
|   }, [copilotLspClient, isCopilotLspServerReady]) | ||||
|  | ||||
|   // const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => { | ||||
|   const onChange = (newCode: string) => { | ||||
| @ -184,6 +238,7 @@ export const TextEditor = ({ | ||||
|     ] as Extension[] | ||||
|  | ||||
|     if (kclLSP) extensions.push(kclLSP) | ||||
|     if (copilotLSP) extensions.push(copilotLSP) | ||||
|  | ||||
|     // These extensions have proven to mess with vitest | ||||
|     if (!TEST) { | ||||
|  | ||||
| @ -117,7 +117,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => { | ||||
|               <div className="p-4 flex flex-col gap-2"> | ||||
|                 <ActionButton | ||||
|                   Element="button" | ||||
|                   icon={{ icon: 'gear' }} | ||||
|                   icon={{ icon: 'settings' }} | ||||
|                   className="border-transparent dark:border-transparent hover:bg-transparent" | ||||
|                   onClick={() => { | ||||
|                     // since /settings is a nested route the sidebar doesn't close | ||||
|  | ||||
| @ -65,6 +65,7 @@ export default class Client extends jsrpc.JSONRPCServerAndClient { | ||||
|   afterInitializedHooks: (() => Promise<void>)[] = [] | ||||
|   #fromServer: FromServer | ||||
|   private serverCapabilities: LSP.ServerCapabilities<any> = {} | ||||
|   private notifyFn: ((message: LSP.NotificationMessage) => void) | null = null | ||||
| 
 | ||||
|   constructor(fromServer: FromServer, intoServer: IntoServer) { | ||||
|     super( | ||||
| @ -167,9 +168,15 @@ export default class Client extends jsrpc.JSONRPCServerAndClient { | ||||
|     return this.serverCapabilities | ||||
|   } | ||||
| 
 | ||||
|   setNotifyFn(fn: (message: LSP.NotificationMessage) => void): void { | ||||
|     this.notifyFn = fn | ||||
|   } | ||||
| 
 | ||||
|   async processNotifications(): Promise<void> { | ||||
|     for await (const notification of this.#fromServer.notifications) { | ||||
|       await this.receiveAndSend(notification) | ||||
|       if (this.notifyFn) { | ||||
|         this.notifyFn(notification) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
							
								
								
									
										496
									
								
								src/editor/plugins/lsp/copilot/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,496 @@ | ||||
| /// Thanks to the Cursor folks for their heavy lifting here. | ||||
| import { indentUnit } from '@codemirror/language' | ||||
| import { | ||||
|   Decoration, | ||||
|   DecorationSet, | ||||
|   EditorView, | ||||
|   ViewPlugin, | ||||
|   ViewUpdate, | ||||
| } from '@codemirror/view' | ||||
| import { | ||||
|   Annotation, | ||||
|   EditorState, | ||||
|   Extension, | ||||
|   Prec, | ||||
|   StateEffect, | ||||
|   StateField, | ||||
|   Transaction, | ||||
| } from '@codemirror/state' | ||||
| import { completionStatus } from '@codemirror/autocomplete' | ||||
| import { offsetToPos, posToOffset } from 'editor/plugins/lsp/util' | ||||
| import { LanguageServerOptions, LanguageServerClient } from 'editor/plugins/lsp' | ||||
| import { | ||||
|   LanguageServerPlugin, | ||||
|   documentUri, | ||||
|   languageId, | ||||
|   workspaceFolders, | ||||
| } from 'editor/plugins/lsp/plugin' | ||||
|  | ||||
| const ghostMark = Decoration.mark({ class: 'cm-ghostText' }) | ||||
|  | ||||
| interface Suggestion { | ||||
|   text: string | ||||
|   displayText: string | ||||
|   cursorPos: number | ||||
|   startPos: number | ||||
|   endPos: number | ||||
|   endReplacement: number | ||||
|   uuid: string | ||||
| } | ||||
|  | ||||
| // Effects to tell StateEffect what to do with GhostText | ||||
| const addSuggestion = StateEffect.define<Suggestion>() | ||||
| const acceptSuggestion = StateEffect.define<null>() | ||||
| const clearSuggestion = StateEffect.define<null>() | ||||
| const typeFirst = StateEffect.define<number>() | ||||
|  | ||||
| interface CompletionState { | ||||
|   ghostText: GhostText | null | ||||
| } | ||||
| interface GhostText { | ||||
|   text: string | ||||
|   displayText: string | ||||
|   displayPos: number | ||||
|   startPos: number | ||||
|   endGhostText: number | ||||
|   endReplacement: number | ||||
|   endPos: number | ||||
|   decorations: DecorationSet | ||||
|   weirdInsert: boolean | ||||
|   uuid: string | ||||
| } | ||||
|  | ||||
| export const completionDecoration = StateField.define<CompletionState>({ | ||||
|   create(_state: EditorState) { | ||||
|     return { ghostText: null } | ||||
|   }, | ||||
|   update(state: CompletionState, transaction: Transaction) { | ||||
|     for (const effect of transaction.effects) { | ||||
|       if (effect.is(addSuggestion)) { | ||||
|         // When adding a suggestion, we set th ghostText | ||||
|         const { | ||||
|           text, | ||||
|           displayText, | ||||
|           endReplacement, | ||||
|           cursorPos, | ||||
|           startPos, | ||||
|           endPos, | ||||
|           uuid, | ||||
|         } = effect.value | ||||
|         const endGhostText = cursorPos + displayText.length | ||||
|         const decorations = Decoration.set([ | ||||
|           ghostMark.range(cursorPos, endGhostText), | ||||
|         ]) | ||||
|         return { | ||||
|           ghostText: { | ||||
|             text, | ||||
|             displayText, | ||||
|             startPos, | ||||
|             endPos, | ||||
|             decorations, | ||||
|             displayPos: cursorPos, | ||||
|             endReplacement, | ||||
|             endGhostText, | ||||
|             weirdInsert: false, | ||||
|             uuid, | ||||
|           }, | ||||
|         } | ||||
|       } else if (effect.is(acceptSuggestion)) { | ||||
|         if (state.ghostText) { | ||||
|           return { ghostText: null } | ||||
|         } | ||||
|       } else if (effect.is(typeFirst)) { | ||||
|         const numChars = effect.value | ||||
|         if (state.ghostText && !state.ghostText.weirdInsert) { | ||||
|           let { | ||||
|             text, | ||||
|             displayText, | ||||
|             displayPos, | ||||
|             startPos, | ||||
|             endPos, | ||||
|             endGhostText, | ||||
|             decorations, | ||||
|             endReplacement, | ||||
|             uuid, | ||||
|           } = state.ghostText | ||||
|  | ||||
|           displayPos += numChars | ||||
|  | ||||
|           displayText = displayText.slice(numChars) | ||||
|  | ||||
|           if (startPos === endGhostText) { | ||||
|             return { ghostText: null } | ||||
|           } else { | ||||
|             decorations = Decoration.set([ | ||||
|               ghostMark.range(displayPos, endGhostText), | ||||
|             ]) | ||||
|             return { | ||||
|               ghostText: { | ||||
|                 text, | ||||
|                 displayText, | ||||
|                 startPos, | ||||
|                 endPos, | ||||
|                 decorations, | ||||
|                 endGhostText, | ||||
|                 endReplacement, | ||||
|                 uuid, | ||||
|                 displayPos, | ||||
|                 weirdInsert: false, | ||||
|               }, | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } else if (effect.is(clearSuggestion)) { | ||||
|         return { ghostText: null } | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // if (transaction.docChanged && state.ghostText) { | ||||
|     //     if (transaction. | ||||
|     //     onsole.log({changes: transaction.changes, transaction}) | ||||
|     //     const newGhostText = state.ghostText.decorations.map(transaction.changes) | ||||
|     //     return {ghostText: {...state.ghostText, decorations: newGhostText}}; | ||||
|     // } | ||||
|  | ||||
|     return state | ||||
|   }, | ||||
|   provide: (field) => | ||||
|     EditorView.decorations.from(field, (value) => | ||||
|       value.ghostText ? value.ghostText.decorations : Decoration.none | ||||
|     ), | ||||
| }) | ||||
|  | ||||
| const copilotEvent = Annotation.define<null>() | ||||
|  | ||||
| /**************************************************************************** | ||||
|  ************************* COMMANDS ****************************************** | ||||
|  *****************************************************************************/ | ||||
|  | ||||
| const acceptSuggestionCommand = ( | ||||
|   copilotClient: LanguageServerClient, | ||||
|   view: EditorView | ||||
| ) => { | ||||
|   // We delete the ghost text and insert the suggestion. | ||||
|   // We also set the cursor to the end of the suggestion. | ||||
|   const ghostText = view.state.field(completionDecoration)!.ghostText | ||||
|   if (!ghostText) { | ||||
|     return false | ||||
|   } | ||||
|  | ||||
|   const ghostTextStart = ghostText.displayPos | ||||
|   const ghostTextEnd = ghostText.endGhostText | ||||
|  | ||||
|   const actualTextStart = ghostText.startPos | ||||
|   const actualTextEnd = ghostText.endPos | ||||
|  | ||||
|   const replacementEnd = ghostText.endReplacement | ||||
|  | ||||
|   const suggestion = ghostText.text | ||||
|  | ||||
|   view.dispatch({ | ||||
|     changes: { | ||||
|       from: ghostTextStart, | ||||
|       to: ghostTextEnd, | ||||
|       insert: '', | ||||
|     }, | ||||
|     // selection: {anchor: actualTextEnd}, | ||||
|     effects: acceptSuggestion.of(null), | ||||
|     annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)], | ||||
|   }) | ||||
|  | ||||
|   const tmpTextEnd = replacementEnd - (ghostTextEnd - ghostTextStart) | ||||
|  | ||||
|   view.dispatch({ | ||||
|     changes: { | ||||
|       from: actualTextStart, | ||||
|       to: tmpTextEnd, | ||||
|       insert: suggestion, | ||||
|     }, | ||||
|     selection: { anchor: actualTextEnd }, | ||||
|     annotations: [copilotEvent.of(null), Transaction.addToHistory.of(true)], | ||||
|   }) | ||||
|  | ||||
|   copilotClient.accept(ghostText.uuid) | ||||
|   return true | ||||
| } | ||||
| export const rejectSuggestionCommand = ( | ||||
|   copilotClient: LanguageServerClient, | ||||
|   view: EditorView | ||||
| ) => { | ||||
|   // We delete the suggestion, then carry through with the original keypress | ||||
|   const ghostText = view.state.field(completionDecoration)!.ghostText | ||||
|   if (!ghostText) { | ||||
|     return false | ||||
|   } | ||||
|  | ||||
|   const ghostTextStart = ghostText.displayPos | ||||
|   const ghostTextEnd = ghostText.endGhostText | ||||
|  | ||||
|   view.dispatch({ | ||||
|     changes: { | ||||
|       from: ghostTextStart, | ||||
|       to: ghostTextEnd, | ||||
|       insert: '', | ||||
|     }, | ||||
|     effects: clearSuggestion.of(null), | ||||
|     annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)], | ||||
|   }) | ||||
|  | ||||
|   copilotClient.reject() | ||||
|   return false | ||||
| } | ||||
|  | ||||
| const sameKeyCommand = ( | ||||
|   copilotClient: LanguageServerClient, | ||||
|   view: EditorView, | ||||
|   key: string | ||||
| ) => { | ||||
|   // When we type a key that is the same as the first letter of the suggestion, we delete the first letter of the suggestion and carry through with the original keypress | ||||
|   const ghostText = view.state.field(completionDecoration)!.ghostText | ||||
|   if (!ghostText) { | ||||
|     return false | ||||
|   } | ||||
|   const ghostTextStart = ghostText.displayPos | ||||
|   const indent = view.state.facet(indentUnit) | ||||
|  | ||||
|   if (key === 'Tab' && ghostText.displayText.startsWith(indent)) { | ||||
|     view.dispatch({ | ||||
|       selection: { anchor: ghostTextStart + indent.length }, | ||||
|       effects: typeFirst.of(indent.length), | ||||
|       annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)], | ||||
|     }) | ||||
|     return true | ||||
|   } else if (key === 'Tab') { | ||||
|     return acceptSuggestionCommand(copilotClient, view) | ||||
|   } else if (ghostText.weirdInsert || key !== ghostText.displayText[0]) { | ||||
|     return rejectSuggestionCommand(copilotClient, view) | ||||
|   } else if (ghostText.displayText.length === 1) { | ||||
|     return acceptSuggestionCommand(copilotClient, view) | ||||
|   } else { | ||||
|     // Use this to delete the first letter of the suggestion | ||||
|     view.dispatch({ | ||||
|       selection: { anchor: ghostTextStart + 1 }, | ||||
|       effects: typeFirst.of(1), | ||||
|       annotations: [copilotEvent.of(null), Transaction.addToHistory.of(false)], | ||||
|     }) | ||||
|  | ||||
|     return true | ||||
|   } | ||||
| } | ||||
|  | ||||
| const completionPlugin = (copilotClient: LanguageServerClient) => | ||||
|   EditorView.domEventHandlers({ | ||||
|     keydown(event, view) { | ||||
|       if ( | ||||
|         event.key !== 'Shift' && | ||||
|         event.key !== 'Control' && | ||||
|         event.key !== 'Alt' && | ||||
|         event.key !== 'Meta' | ||||
|       ) { | ||||
|         return sameKeyCommand(copilotClient, view, event.key) | ||||
|       } else { | ||||
|         return false | ||||
|       } | ||||
|     }, | ||||
|     mousedown(event, view) { | ||||
|       return rejectSuggestionCommand(copilotClient, view) | ||||
|     }, | ||||
|   }) | ||||
|  | ||||
| const viewCompletionPlugin = (copilotClient: LanguageServerClient) => | ||||
|   EditorView.updateListener.of((update) => { | ||||
|     if (update.focusChanged) { | ||||
|       rejectSuggestionCommand(copilotClient, update.view) | ||||
|     } | ||||
|   }) | ||||
| // A view plugin that requests completions from the server after a delay | ||||
| const completionRequester = (client: LanguageServerClient) => { | ||||
|   let timeout: any = null | ||||
|   let lastPos = 0 | ||||
|  | ||||
|   const badUpdate = (update: ViewUpdate) => { | ||||
|     for (const tr of update.transactions) { | ||||
|       if (tr.annotation(copilotEvent) !== undefined) { | ||||
|         return true | ||||
|       } | ||||
|     } | ||||
|     return false | ||||
|   } | ||||
|   const containsGhostText = (update: ViewUpdate) => { | ||||
|     return update.state.field(completionDecoration).ghostText != null | ||||
|   } | ||||
|   const autocompleting = (update: ViewUpdate) => { | ||||
|     return completionStatus(update.state) === 'active' | ||||
|   } | ||||
|   const notFocused = (update: ViewUpdate) => { | ||||
|     return !update.view.hasFocus | ||||
|   } | ||||
|  | ||||
|   return EditorView.updateListener.of((update: ViewUpdate) => { | ||||
|     if ( | ||||
|       update.docChanged && | ||||
|       !update.transactions.some((tr) => | ||||
|         tr.effects.some((e) => e.is(acceptSuggestion) || e.is(clearSuggestion)) | ||||
|       ) | ||||
|     ) { | ||||
|       // Cancel the previous timeout | ||||
|       if (timeout) { | ||||
|         clearTimeout(timeout) | ||||
|       } | ||||
|       if ( | ||||
|         badUpdate(update) || | ||||
|         containsGhostText(update) || | ||||
|         autocompleting(update) || | ||||
|         notFocused(update) | ||||
|       ) { | ||||
|         return | ||||
|       } | ||||
|  | ||||
|       // Get the current position and source | ||||
|       const state = update.state | ||||
|       const pos = state.selection.main.head | ||||
|       const source = state.doc.toString() | ||||
|  | ||||
|       const dUri = state.facet(documentUri) | ||||
|       const path = dUri.split('/').pop()! | ||||
|       const relativePath = dUri.replace('file://', '') | ||||
|  | ||||
|       // Set a new timeout to request completion | ||||
|       timeout = setTimeout(async () => { | ||||
|         // Check if the position has changed | ||||
|         if (pos === lastPos) { | ||||
|           // Request completion from the server | ||||
|           try { | ||||
|             const completionResult = await client.getCompletion({ | ||||
|               doc: { | ||||
|                 source, | ||||
|                 tabSize: state.facet(EditorState.tabSize), | ||||
|                 indentSize: 1, | ||||
|                 insertSpaces: true, | ||||
|                 path, | ||||
|                 uri: dUri, | ||||
|                 relativePath, | ||||
|                 languageId: state.facet(languageId), | ||||
|                 position: offsetToPos(state.doc, pos), | ||||
|               }, | ||||
|             }) | ||||
|  | ||||
|             if (completionResult.completions.length === 0) { | ||||
|               return | ||||
|             } | ||||
|  | ||||
|             let { | ||||
|               text, | ||||
|               displayText, | ||||
|               range: { start }, | ||||
|               position, | ||||
|               uuid, | ||||
|             } = completionResult.completions[0] | ||||
|  | ||||
|             const startPos = posToOffset(state.doc, { | ||||
|               line: start.line, | ||||
|               character: start.character, | ||||
|             })! | ||||
|  | ||||
|             const endGhostPos = | ||||
|               posToOffset(state.doc, { | ||||
|                 line: position.line, | ||||
|                 character: position.character, | ||||
|               })! + displayText.length | ||||
|             // EndPos is the position that marks the complete end | ||||
|             // of what is to be replaced when we accept a completion | ||||
|             // result | ||||
|             const endPos = startPos + text.length | ||||
|  | ||||
|             // Check if the position is still the same | ||||
|             if ( | ||||
|               pos === lastPos && | ||||
|               completionStatus(update.view.state) !== 'active' && | ||||
|               update.view.hasFocus | ||||
|             ) { | ||||
|               // Dispatch an effect to add the suggestion | ||||
|               // If the completion starts before the end of the line, check the end of the line with the end of the completion | ||||
|               const line = update.view.state.doc.lineAt(pos) | ||||
|               if (line.to !== pos) { | ||||
|                 const ending = update.view.state.doc.sliceString(pos, line.to) | ||||
|                 if (displayText.endsWith(ending)) { | ||||
|                   displayText = displayText.slice( | ||||
|                     0, | ||||
|                     displayText.length - ending.length | ||||
|                   ) | ||||
|                 } else if (displayText.includes(ending)) { | ||||
|                   // Remove the ending | ||||
|                   update.view.dispatch({ | ||||
|                     changes: { | ||||
|                       from: pos, | ||||
|                       to: line.to, | ||||
|                       insert: '', | ||||
|                     }, | ||||
|                     selection: { anchor: pos }, | ||||
|                     effects: typeFirst.of(ending.length), | ||||
|                     annotations: [ | ||||
|                       copilotEvent.of(null), | ||||
|                       Transaction.addToHistory.of(false), | ||||
|                     ], | ||||
|                   }) | ||||
|                 } | ||||
|               } | ||||
|               update.view.dispatch({ | ||||
|                 changes: { | ||||
|                   from: pos, | ||||
|                   to: pos, | ||||
|                   insert: displayText, | ||||
|                 }, | ||||
|                 effects: [ | ||||
|                   addSuggestion.of({ | ||||
|                     displayText, | ||||
|                     endReplacement: endGhostPos, | ||||
|                     text, | ||||
|                     cursorPos: pos, | ||||
|                     startPos, | ||||
|                     endPos, | ||||
|                     uuid, | ||||
|                   }), | ||||
|                 ], | ||||
|                 annotations: [ | ||||
|                   copilotEvent.of(null), | ||||
|                   Transaction.addToHistory.of(false), | ||||
|                 ], | ||||
|               }) | ||||
|             } | ||||
|           } catch (error) { | ||||
|             console.warn('copilot completion failed', error) | ||||
|             // Javascript wait for 500ms for some reason is necessary here. | ||||
|             // TODO - FIGURE OUT WHY THIS RESOLVES THE BUG | ||||
|  | ||||
|             await new Promise((resolve) => setTimeout(resolve, 300)) | ||||
|           } | ||||
|         } | ||||
|       }, 150) | ||||
|       // Update the last position | ||||
|       lastPos = pos | ||||
|     } | ||||
|   }) | ||||
| } | ||||
|  | ||||
| export const copilotPlugin = (options: LanguageServerOptions): Extension => { | ||||
|   let plugin: LanguageServerPlugin | null = null | ||||
|  | ||||
|   return [ | ||||
|     documentUri.of(options.documentUri), | ||||
|     languageId.of('kcl'), | ||||
|     workspaceFolders.of(options.workspaceFolders), | ||||
|     ViewPlugin.define( | ||||
|       (view) => | ||||
|         (plugin = new LanguageServerPlugin( | ||||
|           options.client, | ||||
|           view, | ||||
|           options.allowHTMLContent | ||||
|         )) | ||||
|     ), | ||||
|     completionDecoration, | ||||
|     Prec.highest(completionPlugin(options.client)), | ||||
|     Prec.highest(viewCompletionPlugin(options.client)), | ||||
|     completionRequester(options.client), | ||||
|   ] | ||||
| } | ||||
| @ -1,7 +1,68 @@ | ||||
| import type * as LSP from 'vscode-languageserver-protocol' | ||||
| import Client from './client' | ||||
| import { LanguageServerPlugin } from './plugin' | ||||
| import { SemanticToken, deserializeTokens } from './semantic_tokens' | ||||
| import { SemanticToken, deserializeTokens } from './kcl/semantic_tokens' | ||||
| import { LanguageServerPlugin } from 'editor/plugins/lsp/plugin' | ||||
| 
 | ||||
| export interface CopilotGetCompletionsParams { | ||||
|   doc: { | ||||
|     source: string | ||||
|     tabSize: number | ||||
|     indentSize: number | ||||
|     insertSpaces: boolean | ||||
|     path: string | ||||
|     uri: string | ||||
|     relativePath: string | ||||
|     languageId: string | ||||
|     position: { | ||||
|       line: number | ||||
|       character: number | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| interface CopilotGetCompletionsResult { | ||||
|   completions: { | ||||
|     text: string | ||||
|     position: { | ||||
|       line: number | ||||
|       character: number | ||||
|     } | ||||
|     uuid: string | ||||
|     range: { | ||||
|       start: { | ||||
|         line: number | ||||
|         character: number | ||||
|       } | ||||
|       end: { | ||||
|         line: number | ||||
|         character: number | ||||
|       } | ||||
|     } | ||||
|     displayText: string | ||||
|     point: { | ||||
|       line: number | ||||
|       character: number | ||||
|     } | ||||
|     region: { | ||||
|       start: { | ||||
|         line: number | ||||
|         character: number | ||||
|       } | ||||
|       end: { | ||||
|         line: number | ||||
|         character: number | ||||
|       } | ||||
|     } | ||||
|   }[] | ||||
| } | ||||
| 
 | ||||
| interface CopilotAcceptCompletionParams { | ||||
|   uuid: string | ||||
| } | ||||
| 
 | ||||
| interface CopilotRejectCompletionParams { | ||||
|   uuids: string[] | ||||
| } | ||||
| 
 | ||||
| // https://microsoft.github.io/language-server-protocol/specifications/specification-current/
 | ||||
| 
 | ||||
| @ -17,6 +78,9 @@ interface LSPRequestMap { | ||||
|     LSP.SemanticTokensParams, | ||||
|     LSP.SemanticTokens | ||||
|   ] | ||||
|   getCompletions: [CopilotGetCompletionsParams, CopilotGetCompletionsResult] | ||||
|   notifyAccepted: [CopilotAcceptCompletionParams, any] | ||||
|   notifyRejected: [CopilotRejectCompletionParams, any] | ||||
| } | ||||
| 
 | ||||
| // Client to server
 | ||||
| @ -26,26 +90,22 @@ interface LSPNotifyMap { | ||||
|   'textDocument/didOpen': LSP.DidOpenTextDocumentParams | ||||
| } | ||||
| 
 | ||||
| // Server to client
 | ||||
| interface LSPEventMap { | ||||
|   'textDocument/publishDiagnostics': LSP.PublishDiagnosticsParams | ||||
| } | ||||
| 
 | ||||
| export type Notification = { | ||||
|   [key in keyof LSPEventMap]: { | ||||
|     jsonrpc: '2.0' | ||||
|     id?: null | undefined | ||||
|     method: key | ||||
|     params: LSPEventMap[key] | ||||
|   } | ||||
| }[keyof LSPEventMap] | ||||
| 
 | ||||
| export interface LanguageServerClientOptions { | ||||
|   client: Client | ||||
|   name: string | ||||
| } | ||||
| 
 | ||||
| export interface LanguageServerOptions { | ||||
|   // We assume this is the main project directory, we are currently working in.
 | ||||
|   workspaceFolders: LSP.WorkspaceFolder[] | ||||
|   documentUri: string | ||||
|   allowHTMLContent: boolean | ||||
|   client: LanguageServerClient | ||||
| } | ||||
| 
 | ||||
| export class LanguageServerClient { | ||||
|   private client: Client | ||||
|   private name: string | ||||
| 
 | ||||
|   public ready: boolean | ||||
| 
 | ||||
| @ -55,23 +115,31 @@ export class LanguageServerClient { | ||||
| 
 | ||||
|   private isUpdatingSemanticTokens: boolean = false | ||||
|   private semanticTokens: SemanticToken[] = [] | ||||
|   private queuedUids: string[] = [] | ||||
| 
 | ||||
|   constructor(options: LanguageServerClientOptions) { | ||||
|     this.plugins = [] | ||||
|     this.client = options.client | ||||
|     this.name = options.name | ||||
| 
 | ||||
|     this.ready = false | ||||
| 
 | ||||
|     this.queuedUids = [] | ||||
|     this.initializePromise = this.initialize() | ||||
|   } | ||||
| 
 | ||||
|   async initialize() { | ||||
|     // Start the client in the background.
 | ||||
|     this.client.setNotifyFn(this.processNotifications.bind(this)) | ||||
|     this.client.start() | ||||
| 
 | ||||
|     this.ready = true | ||||
|   } | ||||
| 
 | ||||
|   getName(): string { | ||||
|     return this.name | ||||
|   } | ||||
| 
 | ||||
|   getServerCapabilities(): LSP.ServerCapabilities<any> { | ||||
|     return this.client.getServerCapabilities() | ||||
|   } | ||||
| @ -90,6 +158,11 @@ export class LanguageServerClient { | ||||
|   } | ||||
| 
 | ||||
|   async updateSemanticTokens(uri: string) { | ||||
|     const serverCapabilities = this.getServerCapabilities() | ||||
|     if (!serverCapabilities.semanticTokensProvider) { | ||||
|       return | ||||
|     } | ||||
| 
 | ||||
|     // Make sure we can only run, if we aren't already running.
 | ||||
|     if (!this.isUpdatingSemanticTokens) { | ||||
|       this.isUpdatingSemanticTokens = true | ||||
| @ -114,10 +187,18 @@ export class LanguageServerClient { | ||||
|   } | ||||
| 
 | ||||
|   async textDocumentHover(params: LSP.HoverParams) { | ||||
|     const serverCapabilities = this.getServerCapabilities() | ||||
|     if (!serverCapabilities.hoverProvider) { | ||||
|       return | ||||
|     } | ||||
|     return await this.request('textDocument/hover', params) | ||||
|   } | ||||
| 
 | ||||
|   async textDocumentCompletion(params: LSP.CompletionParams) { | ||||
|     const serverCapabilities = this.getServerCapabilities() | ||||
|     if (!serverCapabilities.completionProvider) { | ||||
|       return | ||||
|     } | ||||
|     return await this.request('textDocument/completion', params) | ||||
|   } | ||||
| 
 | ||||
| @ -145,7 +226,35 @@ export class LanguageServerClient { | ||||
|     return this.client.notify(method, params) | ||||
|   } | ||||
| 
 | ||||
|   private processNotification(notification: Notification) { | ||||
|   async getCompletion(params: CopilotGetCompletionsParams) { | ||||
|     const response = await this.request('getCompletions', params) | ||||
|     //
 | ||||
|     this.queuedUids = [...response.completions.map((c) => c.uuid)] | ||||
|     return response | ||||
|   } | ||||
| 
 | ||||
|   async accept(uuid: string) { | ||||
|     const badUids = this.queuedUids.filter((u) => u !== uuid) | ||||
|     this.queuedUids = [] | ||||
|     await this.acceptCompletion({ uuid }) | ||||
|     await this.rejectCompletions({ uuids: badUids }) | ||||
|   } | ||||
| 
 | ||||
|   async reject() { | ||||
|     const badUids = this.queuedUids | ||||
|     this.queuedUids = [] | ||||
|     return await this.rejectCompletions({ uuids: badUids }) | ||||
|   } | ||||
| 
 | ||||
|   async acceptCompletion(params: CopilotAcceptCompletionParams) { | ||||
|     return await this.request('notifyAccepted', params) | ||||
|   } | ||||
| 
 | ||||
|   async rejectCompletions(params: CopilotRejectCompletionParams) { | ||||
|     return await this.request('notifyRejected', params) | ||||
|   } | ||||
| 
 | ||||
|   private processNotifications(notification: LSP.NotificationMessage) { | ||||
|     for (const plugin of this.plugins) plugin.processNotification(notification) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										75
									
								
								src/editor/plugins/lsp/kcl/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,75 @@ | ||||
| import { autocompletion } from '@codemirror/autocomplete' | ||||
| import { Extension } from '@codemirror/state' | ||||
| import { ViewPlugin, hoverTooltip, tooltips } from '@codemirror/view' | ||||
| import { CompletionTriggerKind } from 'vscode-languageserver-protocol' | ||||
| import { offsetToPos } from 'editor/plugins/lsp/util' | ||||
| import { LanguageServerOptions } from 'editor/plugins/lsp' | ||||
| import { | ||||
|   LanguageServerPlugin, | ||||
|   documentUri, | ||||
|   languageId, | ||||
|   workspaceFolders, | ||||
| } from 'editor/plugins/lsp/plugin' | ||||
|  | ||||
| export function kclPlugin(options: LanguageServerOptions): Extension { | ||||
|   let plugin: LanguageServerPlugin | null = null | ||||
|  | ||||
|   return [ | ||||
|     documentUri.of(options.documentUri), | ||||
|     languageId.of('kcl'), | ||||
|     workspaceFolders.of(options.workspaceFolders), | ||||
|     ViewPlugin.define( | ||||
|       (view) => | ||||
|         (plugin = new LanguageServerPlugin( | ||||
|           options.client, | ||||
|           view, | ||||
|           options.allowHTMLContent | ||||
|         )) | ||||
|     ), | ||||
|     hoverTooltip( | ||||
|       (view, pos) => | ||||
|         plugin?.requestHoverTooltip(view, offsetToPos(view.state.doc, pos)) ?? | ||||
|         null | ||||
|     ), | ||||
|     tooltips({ | ||||
|       position: 'absolute', | ||||
|     }), | ||||
|     autocompletion({ | ||||
|       override: [ | ||||
|         async (context) => { | ||||
|           if (plugin == null) return null | ||||
|  | ||||
|           const { state, pos, explicit } = context | ||||
|           const line = state.doc.lineAt(pos) | ||||
|           let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked | ||||
|           let trigChar: string | undefined | ||||
|           if ( | ||||
|             !explicit && | ||||
|             plugin.client | ||||
|               .getServerCapabilities() | ||||
|               .completionProvider?.triggerCharacters?.includes( | ||||
|                 line.text[pos - line.from - 1] | ||||
|               ) | ||||
|           ) { | ||||
|             trigKind = CompletionTriggerKind.TriggerCharacter | ||||
|             trigChar = line.text[pos - line.from - 1] | ||||
|           } | ||||
|           if ( | ||||
|             trigKind === CompletionTriggerKind.Invoked && | ||||
|             !context.matchBefore(/\w+$/) | ||||
|           ) { | ||||
|             return null | ||||
|           } | ||||
|           return await plugin.requestCompletion( | ||||
|             context, | ||||
|             offsetToPos(state.doc, pos), | ||||
|             { | ||||
|               triggerKind: trigKind, | ||||
|               triggerCharacter: trigChar, | ||||
|             } | ||||
|           ) | ||||
|         }, | ||||
|       ], | ||||
|     }), | ||||
|   ] | ||||
| } | ||||
| @ -5,8 +5,8 @@ import { | ||||
|   defineLanguageFacet, | ||||
|   LanguageSupport, | ||||
| } from '@codemirror/language' | ||||
| import { LanguageServerClient } from '.' | ||||
| import { kclPlugin } from './plugin' | ||||
| import { LanguageServerClient } from 'editor/plugins/lsp' | ||||
| import { kclPlugin } from '.' | ||||
| import type * as LSP from 'vscode-languageserver-protocol' | ||||
| import { parser as jsParser } from '@lezer/javascript' | ||||
| import { EditorState } from '@uiw/react-codemirror' | ||||
| @ -14,7 +14,7 @@ import { EditorState } from '@uiw/react-codemirror' | ||||
| const data = defineLanguageFacet({}) | ||||
| 
 | ||||
| export interface LanguageOptions { | ||||
|   workspaceFolders: LSP.WorkspaceFolder[] | null | ||||
|   workspaceFolders: LSP.WorkspaceFolder[] | ||||
|   documentUri: string | ||||
|   client: LanguageServerClient | ||||
| } | ||||
| @ -9,8 +9,8 @@ import { | ||||
|   NodeType, | ||||
|   NodeSet, | ||||
| } from '@lezer/common' | ||||
| import { LanguageServerClient } from '.' | ||||
| import { posToOffset } from './plugin' | ||||
| import { LanguageServerClient } from 'editor/plugins/lsp' | ||||
| import { posToOffset } from 'editor/plugins/lsp/util' | ||||
| import { SemanticToken } from './semantic_tokens' | ||||
| import { DocInput } from '@codemirror/language' | ||||
| import { tags, styleTags } from '@lezer/highlight' | ||||
| @ -1,13 +1,7 @@ | ||||
| import { autocompletion, completeFromList } from '@codemirror/autocomplete' | ||||
| import { completeFromList } from '@codemirror/autocomplete' | ||||
| import { setDiagnostics } from '@codemirror/lint' | ||||
| import { Facet } from '@codemirror/state' | ||||
| import { | ||||
|   EditorView, | ||||
|   ViewPlugin, | ||||
|   Tooltip, | ||||
|   hoverTooltip, | ||||
|   tooltips, | ||||
| } from '@codemirror/view' | ||||
| import { EditorView, Tooltip } from '@codemirror/view' | ||||
| import { | ||||
|   DiagnosticSeverity, | ||||
|   CompletionItemKind, | ||||
| @ -22,10 +16,18 @@ import type { | ||||
| } from '@codemirror/autocomplete' | ||||
| import type { PublishDiagnosticsParams } from 'vscode-languageserver-protocol' | ||||
| import type { ViewUpdate, PluginValue } from '@codemirror/view' | ||||
| import type { Text } from '@codemirror/state' | ||||
| import type * as LSP from 'vscode-languageserver-protocol' | ||||
| import { LanguageServerClient, Notification } from '.' | ||||
| import { LanguageServerClient } from 'editor/plugins/lsp' | ||||
| import { Marked } from '@ts-stack/markdown' | ||||
| import { posToOffset } from 'editor/plugins/lsp/util' | ||||
| 
 | ||||
| const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '') | ||||
| export const documentUri = Facet.define<string, string>({ combine: useLast }) | ||||
| export const languageId = Facet.define<string, string>({ combine: useLast }) | ||||
| export const workspaceFolders = Facet.define< | ||||
|   LSP.WorkspaceFolder[], | ||||
|   LSP.WorkspaceFolder[] | ||||
| >({ combine: useLast }) | ||||
| 
 | ||||
| const changesDelay = 500 | ||||
| 
 | ||||
| @ -33,31 +35,22 @@ const CompletionItemKindMap = Object.fromEntries( | ||||
|   Object.entries(CompletionItemKind).map(([key, value]) => [value, key]) | ||||
| ) as Record<CompletionItemKind, string> | ||||
| 
 | ||||
| const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '') | ||||
| const documentUri = Facet.define<string, string>({ combine: useLast }) | ||||
| const languageId = Facet.define<string, string>({ combine: useLast }) | ||||
| const client = Facet.define<LanguageServerClient, LanguageServerClient>({ | ||||
|   combine: useLast, | ||||
| }) | ||||
| 
 | ||||
| export interface LanguageServerOptions { | ||||
|   workspaceFolders: LSP.WorkspaceFolder[] | null | ||||
|   documentUri: string | ||||
|   allowHTMLContent: boolean | ||||
|   client: LanguageServerClient | ||||
| } | ||||
| 
 | ||||
| export class LanguageServerPlugin implements PluginValue { | ||||
|   public client: LanguageServerClient | ||||
| 
 | ||||
|   private documentUri: string | ||||
|   private languageId: string | ||||
|   private workspaceFolders: LSP.WorkspaceFolder[] | ||||
|   private documentVersion: number | ||||
| 
 | ||||
|   constructor(private view: EditorView, private allowHTMLContent: boolean) { | ||||
|     this.client = this.view.state.facet(client) | ||||
|   constructor( | ||||
|     client: LanguageServerClient, | ||||
|     private view: EditorView, | ||||
|     private allowHTMLContent: boolean | ||||
|   ) { | ||||
|     this.client = client | ||||
|     this.documentUri = this.view.state.facet(documentUri) | ||||
|     this.languageId = this.view.state.facet(languageId) | ||||
|     this.workspaceFolders = this.view.state.facet(workspaceFolders) | ||||
|     this.documentVersion = 0 | ||||
| 
 | ||||
|     this.client.attachPlugin(this) | ||||
| @ -238,11 +231,28 @@ export class LanguageServerPlugin implements PluginValue { | ||||
|     return completeFromList(options)(context) | ||||
|   } | ||||
| 
 | ||||
|   processNotification(notification: Notification) { | ||||
|   processNotification(notification: LSP.NotificationMessage) { | ||||
|     try { | ||||
|       switch (notification.method) { | ||||
|         case 'textDocument/publishDiagnostics': | ||||
|           this.processDiagnostics(notification.params) | ||||
|           this.processDiagnostics( | ||||
|             notification.params as PublishDiagnosticsParams | ||||
|           ) | ||||
|           break | ||||
|         case 'window/logMessage': | ||||
|           console.log( | ||||
|             '[lsp] [window/logMessage]', | ||||
|             this.client.getName(), | ||||
|             notification.params | ||||
|           ) | ||||
|           break | ||||
|         case 'window/showMessage': | ||||
|           console.log( | ||||
|             '[lsp] [window/showMessage]', | ||||
|             this.client.getName(), | ||||
|             notification.params | ||||
|           ) | ||||
|           break | ||||
|       } | ||||
|     } catch (error) { | ||||
|       console.error(error) | ||||
| @ -284,83 +294,6 @@ export class LanguageServerPlugin implements PluginValue { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function kclPlugin(options: LanguageServerOptions) { | ||||
|   let plugin: LanguageServerPlugin | null = null | ||||
| 
 | ||||
|   return [ | ||||
|     client.of(options.client), | ||||
|     documentUri.of(options.documentUri), | ||||
|     languageId.of('kcl'), | ||||
|     ViewPlugin.define( | ||||
|       (view) => | ||||
|         (plugin = new LanguageServerPlugin(view, options.allowHTMLContent)) | ||||
|     ), | ||||
|     hoverTooltip( | ||||
|       (view, pos) => | ||||
|         plugin?.requestHoverTooltip(view, offsetToPos(view.state.doc, pos)) ?? | ||||
|         null | ||||
|     ), | ||||
|     tooltips({ | ||||
|       position: 'absolute', | ||||
|     }), | ||||
|     autocompletion({ | ||||
|       override: [ | ||||
|         async (context) => { | ||||
|           if (plugin == null) return null | ||||
| 
 | ||||
|           const { state, pos, explicit } = context | ||||
|           const line = state.doc.lineAt(pos) | ||||
|           let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked | ||||
|           let trigChar: string | undefined | ||||
|           if ( | ||||
|             !explicit && | ||||
|             plugin.client | ||||
|               .getServerCapabilities() | ||||
|               .completionProvider?.triggerCharacters?.includes( | ||||
|                 line.text[pos - line.from - 1] | ||||
|               ) | ||||
|           ) { | ||||
|             trigKind = CompletionTriggerKind.TriggerCharacter | ||||
|             trigChar = line.text[pos - line.from - 1] | ||||
|           } | ||||
|           if ( | ||||
|             trigKind === CompletionTriggerKind.Invoked && | ||||
|             !context.matchBefore(/\w+$/) | ||||
|           ) { | ||||
|             return null | ||||
|           } | ||||
|           return await plugin.requestCompletion( | ||||
|             context, | ||||
|             offsetToPos(state.doc, pos), | ||||
|             { | ||||
|               triggerKind: trigKind, | ||||
|               triggerCharacter: trigChar, | ||||
|             } | ||||
|           ) | ||||
|         }, | ||||
|       ], | ||||
|     }), | ||||
|   ] | ||||
| } | ||||
| 
 | ||||
| export function posToOffset( | ||||
|   doc: Text, | ||||
|   pos: { line: number; character: number } | ||||
| ): number | undefined { | ||||
|   if (pos.line >= doc.lines) return | ||||
|   const offset = doc.line(pos.line + 1).from + pos.character | ||||
|   if (offset > doc.length) return | ||||
|   return offset | ||||
| } | ||||
| 
 | ||||
| function offsetToPos(doc: Text, offset: number) { | ||||
|   const line = doc.lineAt(offset) | ||||
|   return { | ||||
|     line: line.number - 1, | ||||
|     character: offset - line.from, | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| function formatContents( | ||||
|   contents: LSP.MarkupContent | LSP.MarkedString | LSP.MarkedString[] | ||||
| ): string { | ||||
| @ -34,6 +34,8 @@ const ServerCapabilitiesProviders: IMethodServerCapabilityProviderDictionary = { | ||||
|   'textDocument/foldingRange': 'foldingRangeProvider', | ||||
|   'textDocument/declaration': 'declarationProvider', | ||||
|   'textDocument/executeCommand': 'executeCommandProvider', | ||||
|   'textDocument/semanticTokens/full': 'semanticTokensProvider', | ||||
|   'textDocument/publishDiagnostics': 'diagnosticsProvider', | ||||
| } | ||||
| 
 | ||||
| function registerServerCapability( | ||||
| @ -1,9 +1,11 @@ | ||||
| import init, { | ||||
|   copilot_lsp_run, | ||||
|   InitOutput, | ||||
|   lsp_run, | ||||
|   kcl_lsp_run, | ||||
|   ServerConfig, | ||||
| } from '../../wasm-lib/pkg/wasm_lib' | ||||
| } from 'wasm-lib/pkg/wasm_lib' | ||||
| import { FromServer, IntoServer } from './codec' | ||||
| import { fileSystemManager } from 'lang/std/fileSystemManager' | ||||
| 
 | ||||
| export default class Server { | ||||
|   readonly initOutput: InitOutput | ||||
| @ -29,8 +31,19 @@ export default class Server { | ||||
|     return server | ||||
|   } | ||||
| 
 | ||||
|   async start(): Promise<void> { | ||||
|     const config = new ServerConfig(this.#intoServer, this.#fromServer) | ||||
|     await lsp_run(config) | ||||
|   async start(type_: 'kcl' | 'copilot', token?: string): Promise<void> { | ||||
|     const config = new ServerConfig( | ||||
|       this.#intoServer, | ||||
|       this.#fromServer, | ||||
|       fileSystemManager | ||||
|     ) | ||||
|     if (type_ === 'copilot') { | ||||
|       if (!token) { | ||||
|         throw new Error('auth token is required for copilot') | ||||
|       } | ||||
|       await copilot_lsp_run(config, token) | ||||
|     } else if (type_ === 'kcl') { | ||||
|       await kcl_lsp_run(config) | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/editor/plugins/lsp/util.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,19 @@ | ||||
| import { Text } from '@codemirror/state' | ||||
|  | ||||
| export function posToOffset( | ||||
|   doc: Text, | ||||
|   pos: { line: number; character: number } | ||||
| ): number | undefined { | ||||
|   if (pos.line >= doc.lines) return | ||||
|   const offset = doc.line(pos.line + 1).from + pos.character | ||||
|   if (offset > doc.length) return | ||||
|   return offset | ||||
| } | ||||
|  | ||||
| export function offsetToPos(doc: Text, offset: number) { | ||||
|   const line = doc.lineAt(offset) | ||||
|   return { | ||||
|     line: line.number - 1, | ||||
|     character: offset - line.from, | ||||
|   } | ||||
| } | ||||
| @ -219,3 +219,8 @@ code { | ||||
|   word-break: normal; | ||||
|   word-wrap: break-word; | ||||
| } | ||||
|  | ||||
| .cm-ghostText, | ||||
| .cm-ghostText * { | ||||
|   color: rgb(120, 120, 120, 0.8) !important; | ||||
| } | ||||
|  | ||||
| @ -211,7 +211,7 @@ class KclManager { | ||||
|       console.error('error parsing code', e) | ||||
|       if (e instanceof KCLError) { | ||||
|         this.kclErrors = [e] | ||||
|         if (e.msg === 'file is empty') engineCommandManager.endSession() | ||||
|         if (e.msg === 'file is empty') engineCommandManager?.endSession() | ||||
|       } | ||||
|       return null | ||||
|     } | ||||
| @ -228,7 +228,17 @@ class KclManager { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   async executeAst(ast: Program = this._ast, updateCode = false) { | ||||
|   private _cancelTokens: Map<number, boolean> = new Map() | ||||
|  | ||||
|   async executeAst( | ||||
|     ast: Program = this._ast, | ||||
|     updateCode = false, | ||||
|     executionId?: number | ||||
|   ) { | ||||
|     console.trace('executeAst') | ||||
|     const currentExecutionId = executionId || Date.now() | ||||
|     this._cancelTokens.set(currentExecutionId, false) | ||||
|  | ||||
|     await this.ensureWasmInit() | ||||
|     this.isExecuting = true | ||||
|     const { logs, errors, programMemory } = await executeAst({ | ||||
| @ -236,6 +246,11 @@ class KclManager { | ||||
|       engineCommandManager: this.engineCommandManager, | ||||
|     }) | ||||
|     this.isExecuting = false | ||||
|     // Check the cancellation token for this execution before applying side effects | ||||
|     if (this._cancelTokens.get(currentExecutionId)) { | ||||
|       this._cancelTokens.delete(currentExecutionId) | ||||
|       return | ||||
|     } | ||||
|     this.logs = logs | ||||
|     this.kclErrors = errors | ||||
|     this.programMemory = programMemory | ||||
| @ -248,6 +263,7 @@ class KclManager { | ||||
|       type: 'execution-done', | ||||
|       data: null, | ||||
|     }) | ||||
|     this._cancelTokens.delete(currentExecutionId) | ||||
|   } | ||||
|   async executeAstMock( | ||||
|     ast: Program = this._ast, | ||||
| @ -295,7 +311,13 @@ class KclManager { | ||||
|       } | ||||
|     ) | ||||
|   } | ||||
|   async executeCode(code?: string) { | ||||
|   async executeCode(code?: string, executionId?: number) { | ||||
|     const currentExecutionId = executionId || Date.now() | ||||
|     this._cancelTokens.set(currentExecutionId, false) | ||||
|     if (this._cancelTokens.get(currentExecutionId)) { | ||||
|       this._cancelTokens.delete(currentExecutionId) | ||||
|       return | ||||
|     } | ||||
|     await this.ensureWasmInit() | ||||
|     await this?.engineCommandManager?.waitForReady | ||||
|     const result = await executeCode({ | ||||
| @ -304,6 +326,11 @@ class KclManager { | ||||
|       lastAst: this._ast, | ||||
|       force: false, | ||||
|     }) | ||||
|     // Check the cancellation token for this execution before applying side effects | ||||
|     if (this._cancelTokens.get(currentExecutionId)) { | ||||
|       this._cancelTokens.delete(currentExecutionId) | ||||
|       return | ||||
|     } | ||||
|     if (!result.isChange) return | ||||
|     const { logs, errors, programMemory, ast } = result | ||||
|     this.logs = logs | ||||
| @ -311,6 +338,12 @@ class KclManager { | ||||
|     this.programMemory = programMemory | ||||
|     this.ast = ast | ||||
|     if (code) this.code = code | ||||
|     this._cancelTokens.delete(currentExecutionId) | ||||
|   } | ||||
|   cancelAllExecutions() { | ||||
|     this._cancelTokens.forEach((_, key) => { | ||||
|       this._cancelTokens.set(key, true) | ||||
|     }) | ||||
|   } | ||||
|   setCode(code: string, shouldWriteFile = true) { | ||||
|     if (shouldWriteFile) { | ||||
| @ -391,6 +424,26 @@ class KclManager { | ||||
|     } | ||||
|     return returnVal | ||||
|   } | ||||
|  | ||||
|   get defaultPlanes() { | ||||
|     return this?.engineCommandManager?.defaultPlanes | ||||
|   } | ||||
|  | ||||
|   getPlaneId(axis: 'xy' | 'xz' | 'yz'): string { | ||||
|     return this.defaultPlanes[axis] | ||||
|   } | ||||
|  | ||||
|   showPlanes() { | ||||
|     void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, false) | ||||
|     void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, false) | ||||
|     void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, false) | ||||
|   } | ||||
|  | ||||
|   hidePlanes() { | ||||
|     void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, true) | ||||
|     void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true) | ||||
|     void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true) | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const kclManager = new KclManager(engineCommandManager) | ||||
|  | ||||
| @ -1653,7 +1653,7 @@ describe('parsing errors', () => { | ||||
|  | ||||
|     let _theError | ||||
|     try { | ||||
|       const result = expect(parse(code)) | ||||
|       let _ = expect(parse(code)) | ||||
|     } catch (e) { | ||||
|       _theError = e | ||||
|     } | ||||
|  | ||||
| @ -22,6 +22,7 @@ show(mySketch001)` | ||||
|     expect(shown).toEqual([ | ||||
|       { | ||||
|         type: 'SketchGroup', | ||||
|         on: expect.any(Object), | ||||
|         start: { | ||||
|           to: [0, 0], | ||||
|           from: [0, 0], | ||||
|  | ||||
| @ -143,6 +143,7 @@ show(mySketch) | ||||
|     const { root } = await exe(code) | ||||
|     expect(root.mySk1).toEqual({ | ||||
|       type: 'SketchGroup', | ||||
|       on: expect.any(Object), | ||||
|       start: { | ||||
|         to: [0, 0], | ||||
|         from: [0, 0], | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { ToolTip } from '../useStore' | ||||
| import { Selection } from 'lib/selections' | ||||
| import { Selection, Selections } from 'lib/selections' | ||||
| import { | ||||
|   BinaryExpression, | ||||
|   Program, | ||||
| @ -558,3 +558,24 @@ export function hasExtrudeSketchGroup({ | ||||
|   const varValue = programMemory?.root[varName] | ||||
|   return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup' | ||||
| } | ||||
|  | ||||
| export function isSingleCursorInPipe( | ||||
|   selectionRanges: Selections, | ||||
|   ast: Program | ||||
| ) { | ||||
|   if (selectionRanges.codeBasedSelections.length !== 1) return false | ||||
|   if ( | ||||
|     doesPipeHaveCallExp({ | ||||
|       ast, | ||||
|       selection: selectionRanges.codeBasedSelections[0], | ||||
|       calleeName: 'extrude', | ||||
|     }) | ||||
|   ) | ||||
|     return false | ||||
|   const selection = selectionRanges.codeBasedSelections[0] | ||||
|   const pathToNode = getNodePathFromSourceRange(ast, selection.range) | ||||
|   const nodeTypes = pathToNode.map(([, type]) => type) | ||||
|   if (nodeTypes.includes('FunctionExpression')) return false | ||||
|   if (nodeTypes.includes('PipeExpression')) return true | ||||
|   return false | ||||
| } | ||||
|  | ||||
| @ -920,6 +920,11 @@ export class EngineCommandManager { | ||||
|   outSequence = 1 | ||||
|   inSequence = 1 | ||||
|   engineConnection?: EngineConnection | ||||
|   defaultPlanes: { xy: string; yz: string; xz: string } = { | ||||
|     xy: '', | ||||
|     yz: '', | ||||
|     xz: '', | ||||
|   } | ||||
|   _commandLogs: CommandLog[] = [] | ||||
|   _commandLogCallBack: (command: CommandLog[]) => void = () => {} | ||||
|   // Folks should realize that wait for ready does not get called _everytime_ | ||||
| @ -1014,7 +1019,9 @@ export class EngineCommandManager { | ||||
|         }) | ||||
|         sceneInfra.onStreamStart() | ||||
|  | ||||
|         executeCode(undefined, true) | ||||
|         this.initPlanes().then(() => { | ||||
|           executeCode(undefined, true) | ||||
|         }) | ||||
|       }, | ||||
|       onClose: () => { | ||||
|         setIsStreamReady(false) | ||||
| @ -1142,7 +1149,10 @@ export class EngineCommandManager { | ||||
|         raw: message, | ||||
|       } as const | ||||
|       this.artifactMap[id] = artifact | ||||
|       if (command.commandType === 'entity_linear_pattern') { | ||||
|       if ( | ||||
|         command.commandType === 'entity_linear_pattern' || | ||||
|         command.commandType === 'entity_circular_pattern' | ||||
|       ) { | ||||
|         const entities = (modelingResponse as any)?.data?.entity_ids | ||||
|         entities?.forEach((entity: string) => { | ||||
|           this.artifactMap[entity] = artifact | ||||
| @ -1289,10 +1299,16 @@ export class EngineCommandManager { | ||||
|         // Using an array is the list is likely to grow. | ||||
|         'start_path', | ||||
|         'entity_linear_pattern', | ||||
|         'entity_circular_pattern', | ||||
|       ] | ||||
|       if (artifactTypesToDelete.includes(artifact.commandType)) { | ||||
|         artifactsToDelete[id] = artifact | ||||
|       } | ||||
|       if (artifact.commandType === 'import_files') { | ||||
|         // TODO why is this handled differently from other artifacts, i.e. why does it not use the id from the | ||||
|         // modeling command? We're having to do special clean up for this one special object. | ||||
|         artifactsToDelete[(artifact as any)?.data?.data?.object_id] = artifact | ||||
|       } | ||||
|     }) | ||||
|     Object.keys(artifactsToDelete).forEach((id) => { | ||||
|       const deleteCmd: EngineCommand = { | ||||
| @ -1555,6 +1571,102 @@ export class EngineCommandManager { | ||||
|       artifactMap: this.artifactMap, | ||||
|     } | ||||
|   } | ||||
|   private async initPlanes() { | ||||
|     const [xy, yz, xz] = [ | ||||
|       await this.createPlane({ | ||||
|         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 }, | ||||
|       }), | ||||
|       await this.createPlane({ | ||||
|         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 }, | ||||
|       }), | ||||
|       await this.createPlane({ | ||||
|         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 }, | ||||
|       }), | ||||
|     ] | ||||
|     this.defaultPlanes = { xy, yz, xz } | ||||
|  | ||||
|     this.subscribeTo({ | ||||
|       event: 'select_with_point', | ||||
|       callback: ({ data }) => { | ||||
|         if (!data?.entity_id) return | ||||
|         if ( | ||||
|           ![ | ||||
|             this.defaultPlanes.xy, | ||||
|             this.defaultPlanes.yz, | ||||
|             this.defaultPlanes.xz, | ||||
|           ].includes(data.entity_id) | ||||
|         ) | ||||
|           return | ||||
|         this.onPlaneSelectCallback(data.entity_id) | ||||
|       }, | ||||
|     }) | ||||
|   } | ||||
|   planesInitialized(): boolean { | ||||
|     return ( | ||||
|       this.defaultPlanes.xy !== '' && | ||||
|       this.defaultPlanes.yz !== '' && | ||||
|       this.defaultPlanes.xz !== '' | ||||
|     ) | ||||
|   } | ||||
|  | ||||
|   onPlaneSelectCallback = (id: string) => {} | ||||
|   onPlaneSelected(callback: (id: string) => void) { | ||||
|     this.onPlaneSelectCallback = callback | ||||
|   } | ||||
|  | ||||
|   async setPlaneHidden(id: string, hidden: boolean): Promise<string> { | ||||
|     return await this.sendSceneCommand({ | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd_id: uuidv4(), | ||||
|       cmd: { | ||||
|         type: 'object_visible', | ||||
|         object_id: id, | ||||
|         hidden: hidden, | ||||
|       }, | ||||
|     }) | ||||
|   } | ||||
|  | ||||
|   private async createPlane({ | ||||
|     x_axis, | ||||
|     y_axis, | ||||
|     color, | ||||
|   }: { | ||||
|     x_axis: Models['Point3d_type'] | ||||
|     y_axis: Models['Point3d_type'] | ||||
|     color: Models['Color_type'] | ||||
|   }): Promise<string> { | ||||
|     const planeId = uuidv4() | ||||
|     await this.sendSceneCommand({ | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd: { | ||||
|         type: 'make_plane', | ||||
|         size: 100, | ||||
|         origin: { x: 0, y: 0, z: 0 }, | ||||
|         x_axis, | ||||
|         y_axis, | ||||
|         clobber: false, | ||||
|         hide: true, | ||||
|       }, | ||||
|       cmd_id: planeId, | ||||
|     }) | ||||
|     await this.sendSceneCommand({ | ||||
|       type: 'modeling_cmd_req', | ||||
|       cmd: { | ||||
|         type: 'plane_set_color', | ||||
|         plane_id: planeId, | ||||
|         color, | ||||
|       }, | ||||
|       cmd_id: uuidv4(), | ||||
|     }) | ||||
|     await this.setPlaneHidden(planeId, true) | ||||
|     return planeId | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const engineCommandManager = new EngineCommandManager() | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| import { readFile, exists as tauriExists } from '@tauri-apps/plugin-fs' | ||||
| import { isTauri } from 'lib/isTauri' | ||||
| import { join } from '@tauri-apps/api/path' | ||||
| import { invoke } from '@tauri-apps/api/core' | ||||
| import { FileEntry } from 'lib/types' | ||||
|  | ||||
| /// FileSystemManager is a class that provides a way to read files from the local file system. | ||||
| /// It assumes that you are in a project since it is solely used by the std lib | ||||
| @ -53,6 +55,32 @@ class FileSystemManager { | ||||
|         return tauriExists(file) | ||||
|       }) | ||||
|   } | ||||
|  | ||||
|   getAllFiles(path: string): Promise<string[] | void> { | ||||
|     // Using local file system only works from Tauri. | ||||
|     if (!isTauri()) { | ||||
|       throw new Error( | ||||
|         'This function can only be called from a Tauri application' | ||||
|       ) | ||||
|     } | ||||
|  | ||||
|     return join(this.dir, path) | ||||
|       .catch((error) => { | ||||
|         throw new Error(`Error joining dir: ${error}`) | ||||
|       }) | ||||
|       .then((p) => { | ||||
|         invoke<FileEntry[]>('read_dir_recursive', { | ||||
|           path: p, | ||||
|         }) | ||||
|           .catch((error) => { | ||||
|             throw new Error(`Error reading dir: ${error}`) | ||||
|           }) | ||||
|  | ||||
|           .then((files) => { | ||||
|             return files.map((file) => file.path) | ||||
|           }) | ||||
|       }) | ||||
|   } | ||||
| } | ||||
|  | ||||
| export const fileSystemManager = new FileSystemManager() | ||||
|  | ||||
| @ -136,15 +136,21 @@ export const executor = async ( | ||||
|   return _programMemory | ||||
| } | ||||
|  | ||||
| const getSettingsState = import('components/GlobalStateProvider').then( | ||||
|   (module) => module.getSettingsState | ||||
| ) | ||||
|  | ||||
| export const _executor = async ( | ||||
|   node: Program, | ||||
|   programMemory: ProgramMemory = { root: {}, return: null }, | ||||
|   engineCommandManager: EngineCommandManager | ||||
| ): Promise<ProgramMemory> => { | ||||
|   try { | ||||
|     const baseUnit = (await getSettingsState)()?.baseUnit || 'mm' | ||||
|     const memory: ProgramMemory = await execute_wasm( | ||||
|       JSON.stringify(node), | ||||
|       JSON.stringify(programMemory), | ||||
|       baseUnit, | ||||
|       engineCommandManager, | ||||
|       fileSystemManager | ||||
|     ) | ||||
|  | ||||
| @ -36,7 +36,7 @@ export const settingsCommandBarConfig: CommandSetConfig< | ||||
|   SettingsCommandSchema | ||||
| > = { | ||||
|   'Set Base Unit': { | ||||
|     icon: 'gear', | ||||
|     icon: 'settings', | ||||
|     args: { | ||||
|       baseUnit: { | ||||
|         inputType: 'options', | ||||
| @ -52,7 +52,7 @@ export const settingsCommandBarConfig: CommandSetConfig< | ||||
|     }, | ||||
|   }, | ||||
|   'Set Camera Controls': { | ||||
|     icon: 'gear', | ||||
|     icon: 'settings', | ||||
|     args: { | ||||
|       cameraControls: { | ||||
|         inputType: 'options', | ||||
| @ -68,7 +68,7 @@ export const settingsCommandBarConfig: CommandSetConfig< | ||||
|     }, | ||||
|   }, | ||||
|   'Set Default Project Name': { | ||||
|     icon: 'gear', | ||||
|     icon: 'settings', | ||||
|     hide: 'web', | ||||
|     args: { | ||||
|       defaultProjectName: { | ||||
| @ -79,7 +79,7 @@ export const settingsCommandBarConfig: CommandSetConfig< | ||||
|     }, | ||||
|   }, | ||||
|   'Set Text Wrapping': { | ||||
|     icon: 'gear', | ||||
|     icon: 'settings', | ||||
|     args: { | ||||
|       textWrapping: { | ||||
|         inputType: 'options', | ||||
| @ -101,7 +101,7 @@ export const settingsCommandBarConfig: CommandSetConfig< | ||||
|     }, | ||||
|   }, | ||||
|   'Set Theme': { | ||||
|     icon: 'gear', | ||||
|     icon: 'settings', | ||||
|     args: { | ||||
|       theme: { | ||||
|         inputType: 'options', | ||||
| @ -117,7 +117,7 @@ export const settingsCommandBarConfig: CommandSetConfig< | ||||
|     }, | ||||
|   }, | ||||
|   'Set Unit System': { | ||||
|     icon: 'gear', | ||||
|     icon: 'settings', | ||||
|     args: { | ||||
|       unitSystem: { | ||||
|         inputType: 'options', | ||||
|  | ||||
| @ -9,7 +9,11 @@ import { SelectionRange } from '@uiw/react-codemirror' | ||||
| import { isOverlap } from 'lib/utils' | ||||
| import { isCursorInSketchCommandRange } from 'lang/util' | ||||
| import { Program } from 'lang/wasm' | ||||
| import { doesPipeHaveCallExp, getNodeFromPath } from 'lang/queryAst' | ||||
| import { | ||||
|   doesPipeHaveCallExp, | ||||
|   getNodeFromPath, | ||||
|   isSingleCursorInPipe, | ||||
| } from 'lang/queryAst' | ||||
| import { CommandArgument } from './commandTypes' | ||||
| import { | ||||
|   STRAIGHT_SEGMENT, | ||||
| @ -455,6 +459,7 @@ function resetAndSetEngineEntitySelectionCmds( | ||||
| } | ||||
|  | ||||
| export function isSketchPipe(selectionRanges: Selections) { | ||||
|   if (!isSingleCursorInPipe(selectionRanges, kclManager.ast)) return false | ||||
|   return isCursorInSketchCommandRange( | ||||
|     engineCommandManager.artifactMap, | ||||
|     selectionRanges | ||||
|  | ||||
| @ -37,7 +37,10 @@ export type Events = | ||||
|     } | ||||
|  | ||||
| export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY' | ||||
| const persistedToken = localStorage?.getItem(TOKEN_PERSIST_KEY) || '' | ||||
| const persistedToken = | ||||
|   localStorage?.getItem(TOKEN_PERSIST_KEY) || | ||||
|   getCookie('__Secure-next-auth.session-token') || | ||||
|   '' | ||||
|  | ||||
| export const authMachine = createMachine<UserContext, Events>( | ||||
|   { | ||||
| @ -135,3 +138,23 @@ async function getUser(context: UserContext) { | ||||
|  | ||||
|   return user | ||||
| } | ||||
|  | ||||
| function getCookie(cname: string): string { | ||||
|   if (isTauri()) { | ||||
|     return '' | ||||
|   } | ||||
|  | ||||
|   let name = cname + '=' | ||||
|   let decodedCookie = decodeURIComponent(document.cookie) | ||||
|   let ca = decodedCookie.split(';') | ||||
|   for (let i = 0; i < ca.length; i++) { | ||||
|     let c = ca[i] | ||||
|     while (c.charAt(0) === ' ') { | ||||
|       c = c.substring(1) | ||||
|     } | ||||
|     if (c.indexOf(name) === 0) { | ||||
|       return c.substring(name.length, c.length) | ||||
|     } | ||||
|   } | ||||
|   return '' | ||||
| } | ||||
|  | ||||
| @ -71,7 +71,12 @@ export type SetSelections = | ||||
|     } | ||||
|  | ||||
| export type ModelingMachineEvent = | ||||
|   | { type: 'Enter sketch' } | ||||
|   | { | ||||
|       type: 'Enter sketch' | ||||
|       data?: { | ||||
|         forceNewSketch?: boolean | ||||
|       } | ||||
|     } | ||||
|   | { | ||||
|       type: 'Select default plane' | ||||
|       data: { plane: DefaultPlaneStr; normal: [number, number, number] } | ||||
| @ -114,7 +119,7 @@ export type MoveDesc = { line: number; snippet: string } | ||||
|  | ||||
| export const modelingMachine = createMachine( | ||||
|   { | ||||
|     /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBDOFJYWVlQ2UlY3UrQsQFUQVy0Q00w3FLGVNTBtcQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iZCdKiWZpcQKEyHTKmTLrYrCGb5DRaGHKBGiYzKRoXZqeNodLoEB5PV6wD7sb5UQyRZisMbcQEIQRLGiSdRmfIqUxLCpw5QqcrQ4SGTTWUx5bGXPE3Ql3PjsZ4AVwwv1psXGCSEMjkkhkNFE-MNBxo4vEcM22wqOQxikMKwUktxrUk3nJ32IZEomFdnx+9BGdIBmoQdoUykkxmEyIhELMsjhobKoYUdsOIq2jo8zp9FK+LrdXwAkrcegEgl0BuF-X9AxrQIlDHspPzRKYbAsttYzfojCYZBYaEs5uCaCZuZmrm0c99877i4TkCQPn0oABbMCPfwANxenHIJEwquitYZwaspiT1U0c00qQKPZDGgOKVHcgk+SUGgn0uned-8+6RdlyCNcNwCL5UGebAAC9uHYA8j3+Ot+CMJFJFUMN9TtfI0RkBNxDtcoNHkSx+WMCoZG-bMC1nXMAOIbhYAVEg8H8CCoNgx4D38CBsCYz0wEQk94nrVDdgsJsTU-C91DhNJuQsCpRFHYiLwOKjrl-WjvnoohGOY1id2ePduN4-iKEE6s1XpESUJDGQlkkcUZE2RYnzUPY5M5cprBFDQEWEBzdg0qcaP-Es9NwJjnhY3B-AAQQAIW8fwAA0hPVU9RPszZJByMRG1Oc80jk8UNCcg1FFfVtajOJos00sKC10-SYtYpKUoATQymzGSsXLrDsZQ5EMA0DTwh9DnUUElMWNQoy-c4pWo31tKLCLWti-wyCgLoeqDbKDhyaQlCveQU2EUw5OUxEtnkdE0ROYQQrWtaWqigy4q6fB2D9Glj0y2yGxKGYVANE0Dn2UQ5IWbY5jbQxLujFRnqWp1GtW8LCUi6KtqYF58dwXjyEVTASFeMz4Is-bkIbbVthsIrrEsTDlDklNyuFKpTARaxkTEF6tKx7occ+tjIJguCD0wPRtpwKAhisgHerPBz+wtLQJGIjJ+QTDFw2OLZ-MCsR+TqnEGtCzHmo2j62rioyTMwGW5ewBWaayuyrEuvLZEccVFjMDEE2GkEth5qolmORxzeWjHcze23cdY2BcBIJh-HYVA0o9oGjAIqQHJHBQeeqMw5OyfttQNeZVC2QLKLRy3XuFhi7a21P08z7PuqVpDPeB59MjIhFxGFbI5MbGZdkcOQLyjZQckFpq5yTsWwAAR0VbjvqgX7c7644pAxY2ef1OQx4TWpWRyExR31ZQDVMZfrdX7HNtYphyel6g++EvrZAzFSEXUapwnxRgTKcIBpQFgnEyGoUQL8E6t1FvbfwzwwCrlQDufw5AP6PFgAfVWWh0JWGIsNTQo0dAPhFARCwixbDZBKGoYaSCZytwAEpgEEGAPgIRFRPCIYdOoUgDiGFOKkdIYhg4PnUP2dIqQ0S1yUAsNhf4bayi3tgDOAAZPAYBu6oEPH-QGfVUSCnqLYMQ1CiiBXDMiAi2o1CKPHE3ScLcNH3C0RnKmMBHjYG4uTcg3chF2SGtscEBoCKP0UCIhMxgyg3jrrE5Ei81GSHigAdxYuBCWnF4KYB4nxKmlB-B4AAGaoAIBAbgYB2i4C3KgD4kgYDsEEOxSWXFMCCAqagUJiQ0jZDyhoReJpahPVqHJJ85hNguX2MoWwlV0lZJyeLDiUtCmUwEmU3AlSCAvGeJBSQTAybsEqc8VcLS-DtLyRsnpuy+kmJVtlQZII7TIlTMRFyPM5Lgn7NkQco46g4UMMs7JHBty7mwPuTZxTtm9OqbU+pjTmmtMEI7aFB57mVP6YgfY-YDi2hMGicR+QvJKDZGIOQdhSiBTBasjFMKinmVKQig5RyTkkDOZBS5aLGVYt6bihA+K2T6jEPPI2Xk9gRkfqoNIcj2T0ohR1NKOy9k1NwHUvAKK6lopIAAI1gIIPg2LHn-X7nnYV0TZjDVfBfLYpVhoRgyE+Reshhq1CVQEFVqU1VVPZc8Y5pzzm8uuQao1JrBVPIOmEgiMwcjgiUK2fUWhSqLGkFsR+BFshRmfm4n8NEVnKuSv4TqfrEWauRU03VYbDWCD0KaoV8yM3yAqLYKBxE4SHDRHlbmyktiHGCvmlaCci3epLWWtlzxDmBs5dyi5Vy2nhvrY26NtM8VxosDhMeDl+Suq7W5CwCz5jV0JY3eq7itJju2vgLo5aNVaoadWxdggdpdFXea-+wZ9ig0sAcPIrYA7XQNE5Qdf6KhhgRF6m9u0DFTpnUGrlIaX1vu4VGz9pjv0EThtkfqVg1Dz2uojCM-6shyHPmiaDu9fr3qRdq59aLqNfA-QGTDLyCIghyPJcBew7ww1yoOWlKhEZWCoxuPeXxy0BsQ-O0NbSmMsZrGx2N6bjAmC2Isa8E0igOWlSelE-l7oGmg-jZ4hNiak3JsykpcGHkVsfTql9pnzPQss88QQWyLKKesjGgZasKpKGsMNBy1RoYPjmSCMejZ5iAYA7HdGVtR3goCM5jcFmyYUzhRZKT06OXBp5U5gmaXXMZY81lyg3nla+bxf58UgWRnaijmFnTAVpDChGY-E4cX0l6M1YYzApY+gVjiE2vU5gnx6hFJkdMJVws8wjHVk01hMLVB6-o-rRJvGZx2mBAJhSgkhLXQPPFCJyqjTMKOdQ3M2YPh5vGkGjgsiuIvQW1avWDFZyMZIQsuAOAEFGxIaQ3aITpn8pfB8JL0Il0uhzfUjhQXDvjjOd7-Xvu-fYP96krHnlhJctsE4NhFDKT9ioOE8MnKNgcHUbkpo1t9c+5gSQuAeUHkG+WEII2juWuW-j1suxhTQ7DGTmEGbSULL-XaPNL2R3I-WwzyQAA5bOAAFVAeB2CwAIPFCAEBAjwWMv4Fg6um3zHKjCMabYXKDg0HyBZvsI5ZEUKddJP2-tCoSfYzYWQVDnxNDbmhYI8pKIcoC3TCPpdI7zK7jHVIMM44bFhNkMIYQjNkKORsCZjiJM2OkBYunThYkR4lmcAAVHb-jAnPGCVnNn-ROdx+q8K4a5gFlKFNEt44menxsjcnqSwxEMxF9emX-Au3K-V6qXcLbKOGcm+RL7TImxxSDm04gNs4ZeZpBItbyjQ+tKKiJtnfixlfwAHlcD2arc0+K3gS+CAPzUwQx-2Bn8Vg39dIYoQVQWhiem+o4RGyzALRhguRkJPhqL+DM7+DlIkCUA9DDY8RgAwGkwpZkyapCqCCFQnTE4YiIzEQERXQQ46htjcjRZHAVCowR5tBkDYCrhcqtDdyG5oHdAPpX51I0F0FPCCBZyCAwGUBCoVCXiL4ArAKwhEERJWDMLfJRjqAvQcH0H4CMGcqaq17DaDBNqBQkbUqtgYQQIPgzwRh1DDRqb8g1ByG-acEMFZz+C8LFIMFki+iX70bNLyFcE8EOG5gCGPykKcgnDahpAIhwhPiRZ5AQiLx2ANaaB04faoBfZK7+Cq7q6a6kAWTGLv7HbCpja9piCHA5CIwmBwinDqziKKATbupVAuDnDM4YDwBRBxxQDY6N6CAg7SByCxIYSaA2JCDiQQjTI8x6gORIyIJD4EhgCNEf7MgkKVQ8zgjETXjmgpjlBTTnaSGWBS4WyXoFjjEZHcgMxe7Eq+5thdr9gLKVCHBRaOD8jpLCzbGWoHAnC6iLwphjyhZ1DxLJAHBjypDVAXi-HQYdL5KmTla2aVK3F9RhgMwZClzCg1CpC-LJDAhQjzBWLh4bGvZJYMpQpMqeasoPJglnhyIyrp4iLN6GBeTKRbpPgcwxYYjQY+p+r4mHQlxSBqCOIF52CjiEE6ZFR5SVSYRzSdh0kToMlKbx5GAlz9ihF5G9Gr7FCDggjVwUGthyLrH1GvTXqoYik+Yf4ijijoRQgJpVB5CnDXRqBsjyoVCqBzBb5iY-SSa9KMlewlw3zqaPwpgF7zAwwCiXTiKXFDFXF76FrJaG5FZEwlZWY4kgmoCOkNibAb7cjKS3jtglByS2izC+mtijSVCF5UGvQz6xGYAxl4r2AWC1SbA2DqBLAAGLGnB3Y1AlC7ColqlaT5lfbR5FnCqjRlCZAlDllVAQl8gfHzDpCp4p4LLRGo7M4XIHgdlijxqjzHDOKyCylZBlDtjiKlAuSmETny7xGJEEKznzCIinCyCNgZDQg3a2IIhsh-IUTuqKDnpoky5R7o4dnkQt7azMzghmAZ40IihJgigpjGjpgOiBmrQj5+KcDj7dyzmAG8yGguSVBcm9h5CPFhg8hZC-7DG5n76H567kwv4Fjn5vkLDxp6gLCEqSEAFbDoSXYqAxa3zYVPmR6QHZx8FjGilNF7BhzpDiKqB1CXTDR8g6hPj2D9paB+zmG0EKFQBKHMEkVSA8zi42DyD4aXmIDDThgmDqBzAzxTSUHMXUEWEyWMG2FMT2FbGcU6mOC6gIiYRjzcg0pdpzCCj2AQhRiBQXiLQuBAA */ | ||||
|     /** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBLWFJDWV5hVSZZTrlBUKjS1FZiUrDeWVDasaQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iaF07bCNLiRYlGiZUyZDbFYQzfIaLSQ5Sw0TGZQnM6eNodLoEW73J6wV7sD5UQyRZisMbcP4IQRrGiSdRmfIqUxrCrQlbKcoQ4RHDTWUx5DHNLGXXHXPjsB4AVwwX0psXGCSEohWO3Uh2UOSsmmhhn2jPyNGUolMhjWwvZoo8rUk3mJH2IZEomEdb0+9BGVN+qoQhoUPOMwgRi1BplkBqOZUDCkN4ksClEClt5zaHpJ7wdTveAEkrj0AkEugNwt7vr6VaBEoZQ2UKsIOcmrPMDRCLIKaOa6oKU6I0+LMx8c56C7jkCRXn0oABbMB3fwAN0enHIJEwiuiVZp-qsFskRxkmibmlSBX0Rg0hikifBcgk+SUGkH9uH2ff4+6k+nQTnC4Cd5UAebAAC9uHYDctx+at+CMeFJFUINu0NfJkRkA1xENcoNHkSwVmMCoZFfC531HLMv2IbhYBlEg8H8ICQPAu4N38CBsBo10wGgnd4hreDRA0Ts0hoKp0gtdRoTSdkLAqURwVwi0bxIjNc3Ij5KKIajaPolcHjXVj2M4ihuIrJVqT4uCA2WBRJGFY9UR1YxNAwy8EBkVlymsAVYSBM0X1cU4xTfNTP0LLTcBoh46NwfwAEEACFvH8AANHjlV3fjrOTWZxDEQwbCwmRbHEKThSE4VkUWVJzVqBpAsxELPXU-Nwu06L6MS5KAE10os2k9W2aw7GUOR9jNUboUTdRJCBOTLTUUMAqaO1SNC3NNPamL-DIKAuj6v0spvHJpCUapk0UOtTCk+S4X7Xz1WEUxTGEFSWpazbIp02KunwdgvQpbcMss2sShmFR1VEm80n2KTDlsptTGvJ6wxUV6GuCtbmrC3EIqi7amEeQncHY8hZUwEgniMyCTIO2Daw82ybEjKwMiOVRlCk+MhP5KoXqBBENDEN6yJx7o8e+hjgLAiCN0wPQdpwKAhjMoH+r3ZYZCZFMtAkXCMhWA1HMkYqddhfJ1RKEX1rHNqvo62K9IMzB5cV7BlbpzKrKsJ7cuPJ7HLMVEDVG7YUxeqo1mKxx6pW9N3rFqj7e22BcBIJh-HYVBUs9kGjCwqRlhBNQXuqMwpOyLWPPVeZVBTIFiIx1bVOxja7fx+jU-TzPs961WYK90GbwsJssNhPLjA0KSCpmQTHDkC1Qx1WOgubhO29xrb6LAABHWVWN+qB-tzgbiqkVEhaerI8gkC8ijrRNcpMcFuwm0xrdb23N+T+imEpuXqD914gNWQMwlgER7MjKeblDCRjAaUQ4z1MhqAHE3eOosN7iy3rFB4YBZyoBXP4cg2D2CwBPhrLQiFWbqmPNeFMpUYFKDKPhWw2QwRBkbnHIcNsKKFgAEpgEEGAPgIRZT3HIUdOoUgbywMTGkIWFRDDQnUFrdIqRkS1yUIcD+WYPqFmuHvbAGcAAyeAwA91QJuIBwMBoKBkhIFQzM9aSTckoMOmRhpyDmKiQwOiRyJwMbKIxmddoAWwKxSm5Ae4SKsiNWyIJLaWnkG4hh99jBlFPHXRQ6Q5jLVXugtScUADudFALS2YpBTAbEOI00oP4PAAAzVABAIDcDAO0XAS5UCvEkDAdgghGIyxYpgQQjTUAxMSCJISCIdSiVqM9ceUlrzmGTMeGGyhbCPT8dmYppSpZMVllU6mXF6m4CaQQR4DxgKSCYBTdgTSHizl6X4AZ5TDmjLOeM6x6ssoiW2IaBECZcLHhelJEEWtsimnBHUNCvi0HcOarsjgy5VzYHXEcmpJyxktLaR0rpPS+mCCdmijcHymkTMQDDLWN5LomGRLA-IUljxSEcDfOwpQgTbMkEigIxL0XVOMnU7Flzrm3JIPc4CTzCV8tJWMil7kipMm7GIReOsmWCkPBNVIIJzpKC5Ty+KSVUqnPOa03A7S8D4vaYSkgAAjWAgg+Bkq+YDAeecFWWlmqNe8cg6jrDcsVUah4MjXmXprWo+qSnIq6sa4VDwrkPBuXch5UqXl2odU6uV3zDqxLHrlHVdRIRzzKp6v1ZosLZFDO-eFTVdEGpjd1E1zSzUWs6d061ab7WCD0M6+V6zpCNgqLYOBuEpqmhmDYOx8l6HdjyY1LGdao0BAbU2i58bRXJslc8-p6bu29uzfTSlebzwrD8isUNU1LRCVNOyUaY08qcPyQixdezdpdFXS2vF7bt2CDfYIrNrrgH+hhuDSwN4b7PWDgGioPJIzdjAxUIMsJI2vvwO+uNCak3ipTT+v9+7AM2OA1hBG2Q9RWBLnWG6dZDzgayHIbsyx0ZcNrSOA1h9-oftxZa79hL2PvHwz6QjvysLbByNJKBMhzxwxyqaDlKg6xWBQ8ivjq6RWJrFRKx5P6+MCcrEJ3NnrjAmBTJaE8rkijLA1fMawdiFEonRDWhdrGl3+EJg8YmpNyaUwFbU8x2LP3cYJS8tzHm0VeYeIIY5JldPmRzZMzWdl6HWDvVHUQXM5DSH5HMM0kHfVKYCCFhcnmKZU0xSZVT671Obq04SwrJMwslci2VygMW1ZxcpQl4UShkseVS1JXymWCrzHNPZCQXLTHmosZgIsfRSxxD7ZJ8w15JNHEyEcPIqTKUvUPF10SNmkKPvnS3XRE3zFZ0sXiQxGcaYwDuOEqpkTokHsHpS2EQl9hmHBOoPmnM3L83zYoWEokHPMac9mU7U3JB5lwBwAgC2VBMgSatpGC8uTxhNiCSMkI8pPTSONsxkPoew7JARn5sT8ha3kseK95GNnQlhEtlYEhNkbPBKg0Hx2RwQ-O5gSQuBJUbhmyWEI83nvuusLUaQx54wcIyDXLkQtZrXgUdza8WF8eTZ55IAActnAACqgPApCCBxQgBAQIkF9KucN3cPt8wKqv3NIKRYOouQbNyhHLIigzoa7O6gSxOv9c2+N6QEyVjSftYDJocwJg0jVFErrFxRQshlBsIg0ox4VhcqJ+wOHYuBr4VmBdOlDGE8GmBLMDRywoWWbhRz96Oe4fkkE2T2sKEmSQkhHMWQ4ICrl4PIoPWhxLORhB0+lj2YAAqoS7sRIeFErOQv+ii4j4e9yo1zAbKUMKRMthirl+vEyK9knLC4RTFy6f+Awlz4X80wJwTuf+-Dy3yPlnpmJkH8KU05nEBIx5ObNIPCU0OYJjcfMHSQWUEmbOTifSd8AAeVwBxXNS-R6Tim8En0EEgNaUEBgPYHgJVlXxewDFkCkDmBMFwlEkyEWDvkQHNFshbEQxpScW2X8H538AaRIEoB6DmzYjAA4PJgKwpnNXlUEAKnMASWM0NFDAkFgS5AyyRnZCG0sAfFAKO0kDIGwFnHFVaB7lcyEO6ACzbR6Q0K0PuEECzkEA4MoHlUUUPGoMWAyCWChDclGniSsDBBBVDHUDehMO0PwF0LFXNSXzm0GD7SBBozkAXiQlDGhDnkPFWDsHBBWBqB8Jh1MJ0Kzn8GERqR0KJE9CQNbStXULSPFUEQsLyKzBsLNCoVZGeg8jSFhGhDoQHWPGALsDmFoRcECn5wwHgCiCOxfzX0EEWDKFyGySQk0B0DckEEEiVSgX2DyFGktDehxDAEGKIPpEoUehehBFwhPGUXRwkDUA+3cMsGrXr3fHWPdXZCZmLxUFLyRimi1g2UqEDmcnZzAM5w-EwSuJAWehNh1HjDymqF2GjGSBvDylSGqAtBhPy32SGUqR8yxU+V+L3CDCZgyFLn5BqFSDBWSABDvHmFsH5DhJlQxUFT8xRL01byMBUU1V7ykQ3yUQDXowsG8m5mG1RDhJjRSibVRKOjsSkDUCKlH0SNEjKiwh2CqlUAWhTEO0xi+O5RcxXTGX5O9jsUp1oWI0rjHWqOrkHSqGhLhL-T5OpMjyOGFEQjvByAcDyEjBujUCZDjxg0yFGjqDhJU1VLNLXwtMZAyFWBM1HzbADW5FmhhMcGRCRktjhLq2K28yiyFSpNix9OTH-3ZHkjPDTxKH63R15mTAtEbBtEc0VMf0sTVMmXsBHhKGTEnXRNiNUBNmVxWBl1wmRF90Jxh3YHLMpX2GYTqhrMNLWC5HBPmByRWC7w2XbK1350eQ3G7PchsAnXHmKjUFjx-wQBT1mEUMjCKiDFkCnKf0D38ANyNz6OTKIOsAkBNiYS0HsDSENjcibCrnjATCOCbFvOz07PnMIk331msBMG3z7xgRjBo3UATCTHP2LPekv1u04Bvx7nnLkE0Fmj8hUAciemuhgQYwBKDA5GvisA+LULIiwOgMtzwNzAQO-KtHKF7FTL9VkEwqKEEh5DmAZTPyqnVBYLYKsLWO9I2L7BouWVUDqCekmhcIy2vHsGnS0FkFTCgt8M4H8MyMCN4vPPdUtCkBelZxsHkFpzkJ5AAojLnmmlUIVOKM0L8KgF0OyJolyNzG-McBNlhGQjynZHZSmibF5HsGoKFmKgKi6KcCAA */ | ||||
|     id: 'Modeling', | ||||
|  | ||||
|     tsTypes: {} as import('./modelingMachine.typegen').Typegen0, | ||||
| @ -153,7 +158,7 @@ export const modelingMachine = createMachine( | ||||
|           'Enter sketch': [ | ||||
|             { | ||||
|               target: 'animating to existing sketch', | ||||
|               cond: 'Selection is one face', | ||||
|               cond: 'Selection is on face', | ||||
|               actions: ['set sketch metadata'], | ||||
|             }, | ||||
|             'Sketch no face', | ||||
| @ -166,6 +171,8 @@ export const modelingMachine = createMachine( | ||||
|             internal: true, | ||||
|           }, | ||||
|         }, | ||||
|  | ||||
|         entry: 'reset client scene mouse handlers', | ||||
|       }, | ||||
|  | ||||
|       Sketch: { | ||||
| @ -378,10 +385,7 @@ export const modelingMachine = createMachine( | ||||
|           }, | ||||
|  | ||||
|           'Line tool': { | ||||
|             exit: [ | ||||
|               // 'tear down client sketch', | ||||
|               // 'setup client side sketch segments', | ||||
|             ], | ||||
|             exit: [], | ||||
|  | ||||
|             on: { | ||||
|               'Set selection': { | ||||
| @ -605,7 +609,10 @@ export const modelingMachine = createMachine( | ||||
|         if (!sketchPathToNode) return {} | ||||
|         return getSketchMetadataFromPathToNode(sketchPathToNode) | ||||
|       }), | ||||
|       'hide default planes': () => sceneInfra.removeDefaultPlanes(), | ||||
|       'hide default planes': () => { | ||||
|         sceneInfra.removeDefaultPlanes() | ||||
|         kclManager.hidePlanes() | ||||
|       }, | ||||
|       'reset sketch metadata': assign({ | ||||
|         sketchPathToNode: null, | ||||
|         sketchEnginePathId: '', | ||||
| @ -783,6 +790,7 @@ export const modelingMachine = createMachine( | ||||
|       'show default planes': () => { | ||||
|         sceneInfra.showDefaultPlanes() | ||||
|         sceneEntitiesManager.setupDefaultPlaneHover() | ||||
|         kclManager.showPlanes() | ||||
|       }, | ||||
|       'setup noPoints onClick listener': ({ sketchPathToNode }) => { | ||||
|         sceneEntitiesManager.createIntersectionPlane() | ||||
| @ -799,6 +807,7 @@ export const modelingMachine = createMachine( | ||||
|         sceneInfra.setCallbacks({ | ||||
|           onClick: async (args) => { | ||||
|             if (!args) return | ||||
|             if (args.event.which !== 1) return | ||||
|             const { intersection2d } = args | ||||
|             if (!intersection2d || !sketchPathToNode) return | ||||
|             const { modifiedAst } = addStartProfileAt( | ||||
| @ -814,6 +823,11 @@ export const modelingMachine = createMachine( | ||||
|       }, | ||||
|       'add axis n grid': ({ sketchPathToNode }) => | ||||
|         sceneEntitiesManager.createSketchAxis(sketchPathToNode || []), | ||||
|       'reset client scene mouse handlers': () => { | ||||
|         // when not in sketch mode we don't need any mouse listeners | ||||
|         // (note the orbit controls are always active though) | ||||
|         sceneInfra.resetMouseListeners() | ||||
|       }, | ||||
|     }, | ||||
|     // end actions | ||||
|   } | ||||
|  | ||||
| @ -3,6 +3,10 @@ import { Themes, getSystemTheme, setThemeClass } from '../lib/theme' | ||||
| import { CameraSystem } from 'lib/cameraControls' | ||||
| import { Models } from '@kittycad/lib' | ||||
|  | ||||
| const kclManagerPromise = import('lang/KclSingleton').then( | ||||
|   (module) => module.kclManager | ||||
| ) | ||||
|  | ||||
| export const DEFAULT_PROJECT_NAME = 'project-$nnn' | ||||
|  | ||||
| export enum UnitSystem { | ||||
| @ -29,7 +33,7 @@ export const settingsMachine = createMachine( | ||||
|     id: 'Settings', | ||||
|     predictableActionArguments: true, | ||||
|     context: { | ||||
|       baseUnit: 'in' as BaseUnit, | ||||
|       baseUnit: 'mm' as BaseUnit, | ||||
|       cameraControls: 'KittyCAD' as CameraSystem, | ||||
|       defaultDirectory: '', | ||||
|       defaultProjectName: DEFAULT_PROJECT_NAME, | ||||
| @ -37,7 +41,7 @@ export const settingsMachine = createMachine( | ||||
|       showDebugPanel: false, | ||||
|       textWrapping: 'On' as Toggle, | ||||
|       theme: Themes.System, | ||||
|       unitSystem: UnitSystem.Imperial, | ||||
|       unitSystem: UnitSystem.Metric, | ||||
|     }, | ||||
|     initial: 'idle', | ||||
|     states: { | ||||
| @ -47,13 +51,13 @@ export const settingsMachine = createMachine( | ||||
|           'Set Base Unit': { | ||||
|             actions: [ | ||||
|               assign({ | ||||
|                 baseUnit: (_, event) => { | ||||
|                   console.log('event', event) | ||||
|                   return event.data.baseUnit | ||||
|                 }, | ||||
|                 baseUnit: (_, event) => event.data.baseUnit, | ||||
|               }), | ||||
|               'persistSettings', | ||||
|               'toastSuccess', | ||||
|               async () => { | ||||
|                 ;(await kclManagerPromise).executeAst() | ||||
|               }, | ||||
|             ], | ||||
|             target: 'idle', | ||||
|             internal: true, | ||||
| @ -134,6 +138,9 @@ export const settingsMachine = createMachine( | ||||
|               }), | ||||
|               'persistSettings', | ||||
|               'toastSuccess', | ||||
|               async () => { | ||||
|                 ;(await kclManagerPromise).executeAst() | ||||
|               }, | ||||
|             ], | ||||
|             target: 'idle', | ||||
|             internal: true, | ||||
|  | ||||
| @ -36,6 +36,7 @@ import { sep } from '@tauri-apps/api/path' | ||||
| import { homeCommandBarConfig } from 'lib/commandBarConfigs/homeCommandConfig' | ||||
| import { useHotkeys } from 'react-hotkeys-hook' | ||||
| import { isTauri } from 'lib/isTauri' | ||||
| import { kclManager } from 'lang/KclSingleton' | ||||
|  | ||||
| // This route only opens in the Tauri desktop context for now, | ||||
| // as defined in Router.tsx, so we can use the Tauri APIs and types. | ||||
| @ -55,6 +56,7 @@ const Home = () => { | ||||
|   // during the loading of the home page. This is wrapped | ||||
|   // in a single-use effect to avoid a potential infinite loop. | ||||
|   useEffect(() => { | ||||
|     kclManager.cancelAllExecutions() | ||||
|     if (newDefaultDirectory) { | ||||
|       sendToSettings({ | ||||
|         type: 'Set Default Directory', | ||||
|  | ||||
| @ -3,6 +3,11 @@ import { WebSocket } from 'ws' | ||||
| import { vi } from 'vitest' | ||||
| import 'vitest-webgl-canvas-mock' | ||||
|  | ||||
| import fetch from 'node-fetch' | ||||
|  | ||||
| // @ts-ignore | ||||
| globalThis.fetch = fetch | ||||
|  | ||||
| class MockRTCPeerConnection { | ||||
|   createDataChannel() { | ||||
|     return | ||||
|  | ||||
| @ -66,8 +66,10 @@ export interface StoreState { | ||||
|   setMediaStream: (mediaStream: MediaStream) => void | ||||
|   isStreamReady: boolean | ||||
|   setIsStreamReady: (isStreamReady: boolean) => void | ||||
|   isLSPServerReady: boolean | ||||
|   setIsLSPServerReady: (isLSPServerReady: boolean) => void | ||||
|   isKclLspServerReady: boolean | ||||
|   isCopilotLspServerReady: boolean | ||||
|   setIsKclLspServerReady: (isKclLspServerReady: boolean) => void | ||||
|   setIsCopilotLspServerReady: (isCopilotLspServerReady: boolean) => void | ||||
|   buttonDownInStream: number | undefined | ||||
|   setButtonDownInStream: (buttonDownInStream: number | undefined) => void | ||||
|   didDragInStream: boolean | ||||
| @ -120,8 +122,12 @@ export const useStore = create<StoreState>()( | ||||
|         setMediaStream: (mediaStream) => set({ mediaStream }), | ||||
|         isStreamReady: false, | ||||
|         setIsStreamReady: (isStreamReady) => set({ isStreamReady }), | ||||
|         isLSPServerReady: false, | ||||
|         setIsLSPServerReady: (isLSPServerReady) => set({ isLSPServerReady }), | ||||
|         isKclLspServerReady: false, | ||||
|         isCopilotLspServerReady: false, | ||||
|         setIsKclLspServerReady: (isKclLspServerReady) => | ||||
|           set({ isKclLspServerReady }), | ||||
|         setIsCopilotLspServerReady: (isCopilotLspServerReady) => | ||||
|           set({ isCopilotLspServerReady }), | ||||
|         buttonDownInStream: undefined, | ||||
|         setButtonDownInStream: (buttonDownInStream) => { | ||||
|           set({ buttonDownInStream }) | ||||
|  | ||||
							
								
								
									
										135
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						| @ -246,7 +246,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -257,7 +257,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -522,9 +522,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "chrono" | ||||
| version = "0.4.31" | ||||
| version = "0.4.34" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" | ||||
| checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" | ||||
| dependencies = [ | ||||
|  "android-tzdata", | ||||
|  "iana-time-zone", | ||||
| @ -532,7 +532,7 @@ dependencies = [ | ||||
|  "num-traits 0.2.17", | ||||
|  "serde", | ||||
|  "wasm-bindgen", | ||||
|  "windows-targets 0.48.5", | ||||
|  "windows-targets 0.52.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -606,7 +606,7 @@ dependencies = [ | ||||
|  "heck", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -856,7 +856,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -897,7 +897,7 @@ checksum = "377af281d8f23663862a7c84623bc5dcf7f8c44b13c7496a590bdc157f941a43" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
|  "synstructure 0.13.0", | ||||
| ] | ||||
|  | ||||
| @ -949,7 +949,23 @@ dependencies = [ | ||||
|  "regex", | ||||
|  "serde", | ||||
|  "serde_tokenstream", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "derive-docs" | ||||
| version = "0.1.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "834580a8bd697658876ed8c9f7727e49f01d34f5b859ca921ac5b99ffc6adf77" | ||||
| dependencies = [ | ||||
|  "convert_case", | ||||
|  "once_cell", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "regex", | ||||
|  "serde", | ||||
|  "serde_tokenstream", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -961,7 +977,7 @@ dependencies = [ | ||||
|  "diesel_table_macro_syntax", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -970,7 +986,7 @@ version = "0.1.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" | ||||
| dependencies = [ | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -1020,7 +1036,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -1102,7 +1118,7 @@ checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -1319,7 +1335,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -1424,7 +1440,7 @@ dependencies = [ | ||||
|  "inflections", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -1877,7 +1893,7 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kcl-lib" | ||||
| version = "0.1.40" | ||||
| version = "0.1.42" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "approx 0.5.1", | ||||
| @ -1888,7 +1904,7 @@ dependencies = [ | ||||
|  "criterion", | ||||
|  "dashmap", | ||||
|  "databake", | ||||
|  "derive-docs", | ||||
|  "derive-docs 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
|  "expectorate", | ||||
|  "futures", | ||||
|  "gltf-json", | ||||
| @ -1902,6 +1918,7 @@ dependencies = [ | ||||
|  "parse-display 0.9.0", | ||||
|  "pretty_assertions", | ||||
|  "reqwest", | ||||
|  "ropey", | ||||
|  "schemars", | ||||
|  "serde", | ||||
|  "serde_json", | ||||
| @ -1926,14 +1943,14 @@ dependencies = [ | ||||
|  "pretty_assertions", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "kittycad" | ||||
| version = "0.2.50" | ||||
| version = "0.2.54" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "441d8af990a3aab738f985328aa914a9eee5856131c4c6f1fd2bd61ba9d07f98" | ||||
| checksum = "13958174d876353f429ea8230dc92fe86f164819cea2e51bbf22e01a4c2a496e" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "async-trait", | ||||
| @ -1969,7 +1986,7 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "kittycad-execution-plan" | ||||
| version = "0.1.0" | ||||
| source = "git+https://github.com/KittyCAD/modeling-api?branch=main#632b75a0242400fa34373d7973b9149b0e08aa3f" | ||||
| source = "git+https://github.com/KittyCAD/modeling-api?branch=main#08f05d91062380fe3a69f4baa1f1301532d31977" | ||||
| dependencies = [ | ||||
|  "bytes", | ||||
|  "insta", | ||||
| @ -1993,7 +2010,7 @@ checksum = "71d31b689c944d00aadda2ef83d8422a6efff97e1be5654a61f9d95496f0c19e" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -2003,7 +2020,7 @@ source = "git+https://github.com/KittyCAD/modeling-api?branch=main#632b75a024240 | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -2019,8 +2036,8 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "kittycad-modeling-cmds" | ||||
| version = "0.1.17" | ||||
| source = "git+https://github.com/KittyCAD/modeling-api?branch=main#632b75a0242400fa34373d7973b9149b0e08aa3f" | ||||
| version = "0.1.18" | ||||
| source = "git+https://github.com/KittyCAD/modeling-api?branch=main#08f05d91062380fe3a69f4baa1f1301532d31977" | ||||
| dependencies = [ | ||||
|  "anyhow", | ||||
|  "chrono", | ||||
| @ -2047,7 +2064,7 @@ dependencies = [ | ||||
| [[package]] | ||||
| name = "kittycad-modeling-session" | ||||
| version = "0.1.0" | ||||
| source = "git+https://github.com/KittyCAD/modeling-api?branch=main#632b75a0242400fa34373d7973b9149b0e08aa3f" | ||||
| source = "git+https://github.com/KittyCAD/modeling-api?branch=main#08f05d91062380fe3a69f4baa1f1301532d31977" | ||||
| dependencies = [ | ||||
|  "futures", | ||||
|  "kittycad", | ||||
| @ -2406,7 +2423,7 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" | ||||
| [[package]] | ||||
| name = "openapitor" | ||||
| version = "0.0.9" | ||||
| source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#88b05a638f594c0bf51faf6f4ab57d3327d4d845" | ||||
| source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#8db292eaa7be0292512a2cdbef09f2d37af7c79c" | ||||
| dependencies = [ | ||||
|  "Inflector", | ||||
|  "anyhow", | ||||
| @ -2479,7 +2496,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -2636,7 +2653,7 @@ dependencies = [ | ||||
|  "regex", | ||||
|  "regex-syntax 0.7.5", | ||||
|  "structmeta 0.2.0", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -2650,7 +2667,7 @@ dependencies = [ | ||||
|  "regex", | ||||
|  "regex-syntax 0.8.2", | ||||
|  "structmeta 0.3.0", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -2716,7 +2733,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -3130,10 +3147,12 @@ dependencies = [ | ||||
|  "tokio", | ||||
|  "tokio-native-tls", | ||||
|  "tokio-rustls 0.24.1", | ||||
|  "tokio-util", | ||||
|  "tower-service", | ||||
|  "url", | ||||
|  "wasm-bindgen", | ||||
|  "wasm-bindgen-futures", | ||||
|  "wasm-streams", | ||||
|  "web-sys", | ||||
|  "webpki-roots", | ||||
|  "winreg", | ||||
| @ -3257,6 +3276,16 @@ dependencies = [ | ||||
|  "windows-sys 0.48.0", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "ropey" | ||||
| version = "1.6.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "93411e420bcd1a75ddd1dc3caf18c23155eda2c090631a85af21ba19e97093b5" | ||||
| dependencies = [ | ||||
|  "smallvec", | ||||
|  "str_indices", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "rtcp" | ||||
| version = "0.10.0" | ||||
| @ -3572,7 +3601,7 @@ checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -3606,7 +3635,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -3627,7 +3656,7 @@ dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "serde", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -3863,6 +3892,12 @@ dependencies = [ | ||||
|  "der", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| name = "str_indices" | ||||
| version = "0.4.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "e9557cb6521e8d009c51a8666f09356f4b817ba9ba0981a305bd86aee47bd35c" | ||||
|  | ||||
| [[package]] | ||||
| name = "strsim" | ||||
| version = "0.11.0" | ||||
| @ -3878,7 +3913,7 @@ dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "structmeta-derive 0.2.0", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -3890,7 +3925,7 @@ dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "structmeta-derive 0.3.0", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -3901,7 +3936,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -3912,7 +3947,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -3984,9 +4019,9 @@ dependencies = [ | ||||
|  | ||||
| [[package]] | ||||
| name = "syn" | ||||
| version = "2.0.48" | ||||
| version = "2.0.49" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" | ||||
| checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
| @ -4019,7 +4054,7 @@ checksum = "285ba80e733fac80aa4270fbcdf83772a79b80aa35c97075320abfee4a915b06" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
|  "unicode-xid", | ||||
| ] | ||||
|  | ||||
| @ -4149,7 +4184,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -4256,7 +4291,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -4419,7 +4454,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -4447,7 +4482,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -4531,7 +4566,7 @@ dependencies = [ | ||||
|  "Inflector", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
|  "termcolor", | ||||
| ] | ||||
|  | ||||
| @ -4778,7 +4813,7 @@ dependencies = [ | ||||
|  "once_cell", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
|  "wasm-bindgen-shared", | ||||
| ] | ||||
|  | ||||
| @ -4813,7 +4848,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
|  "wasm-bindgen-backend", | ||||
|  "wasm-bindgen-shared", | ||||
| ] | ||||
| @ -5431,7 +5466,7 @@ checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
| @ -5451,7 +5486,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn 2.0.48", | ||||
|  "syn 2.0.49", | ||||
| ] | ||||
|  | ||||
| [[package]] | ||||
|  | ||||
| @ -58,7 +58,7 @@ members = [ | ||||
| ] | ||||
|  | ||||
| [workspace.dependencies] | ||||
| kittycad = { version = "0.2.50", default-features = false, features = ["js"] } | ||||
| kittycad = { version = "0.2.54", default-features = false, features = ["js", "requests"] } | ||||
| kittycad-execution-plan = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" } | ||||
| kittycad-execution-plan-traits = "0.1.10" | ||||
| kittycad-modeling-session = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" } | ||||
|  | ||||
| @ -19,7 +19,7 @@ quote = "1" | ||||
| regex = "1.10" | ||||
| serde = { version = "1.0.193", features = ["derive"] } | ||||
| serde_tokenstream = "0.2" | ||||
| syn = { version = "2.0.39", features = ["full"] } | ||||
| syn = { version = "2.0.49", features = ["full"] } | ||||
|  | ||||
| [dev-dependencies] | ||||
| expectorate = "1.1.0" | ||||
|  | ||||
| @ -202,12 +202,8 @@ impl Planner { | ||||
|                             ast::types::BinaryOperator::Sub => ep::BinaryOperation::Sub, | ||||
|                             ast::types::BinaryOperator::Mul => ep::BinaryOperation::Mul, | ||||
|                             ast::types::BinaryOperator::Div => ep::BinaryOperation::Div, | ||||
|                             ast::types::BinaryOperator::Mod => { | ||||
|                                 todo!("execution plan instruction set doesn't support Mod yet") | ||||
|                             } | ||||
|                             ast::types::BinaryOperator::Pow => { | ||||
|                                 todo!("execution plan instruction set doesn't support Pow yet") | ||||
|                             } | ||||
|                             ast::types::BinaryOperator::Mod => ep::BinaryOperation::Mod, | ||||
|                             ast::types::BinaryOperator::Pow => ep::BinaryOperation::Pow, | ||||
|                         }, | ||||
|                         operand0: ep::Operand::Reference(l_binding), | ||||
|                         operand1: ep::Operand::Reference(r_binding), | ||||
|  | ||||
| @ -1145,3 +1145,54 @@ fn arrays_as_parameters() { | ||||
|         } | ||||
|     ) | ||||
| } | ||||
|  | ||||
| #[test] | ||||
| fn mod_and_pow() { | ||||
|     let program = " | ||||
|         let x = 2 | ||||
|         let y = x^3 | ||||
|         let z = y % 5 | ||||
|         "; | ||||
|     let (plan, _bindings) = must_plan(program); | ||||
|     let addr0 = Address::ZERO; | ||||
|     let addr1 = Address::ZERO.offset(1); | ||||
|     let addr2 = Address::ZERO.offset(2); | ||||
|     let addr3 = Address::ZERO.offset(3); | ||||
|     let addr4 = Address::ZERO.offset(4); | ||||
|     print!("{:?}", plan); | ||||
|     assert_eq!( | ||||
|         plan, | ||||
|         vec![ | ||||
|             Instruction::SetPrimitive { | ||||
|                 address: addr0, | ||||
|                 value: 2i64.into(), | ||||
|             }, | ||||
|             Instruction::SetPrimitive { | ||||
|                 address: addr1, | ||||
|                 value: 3i64.into(), | ||||
|             }, | ||||
|             // x ^ 3, where x = 2 | ||||
|             Instruction::BinaryArithmetic { | ||||
|                 arithmetic: ep::BinaryArithmetic { | ||||
|                     operation: ep::BinaryOperation::Pow, | ||||
|                     operand0: ep::Operand::Reference(addr0), | ||||
|                     operand1: ep::Operand::Reference(addr1), | ||||
|                 }, | ||||
|                 destination: Destination::Address(addr2), | ||||
|             }, | ||||
|             Instruction::SetPrimitive { | ||||
|                 address: addr3, | ||||
|                 value: 5i64.into(), | ||||
|             }, | ||||
|             // y % 5, where y is 2^3 | ||||
|             Instruction::BinaryArithmetic { | ||||
|                 arithmetic: ep::BinaryArithmetic { | ||||
|                     operation: ep::BinaryOperation::Mod, | ||||
|                     operand0: ep::Operand::Reference(addr2), | ||||
|                     operand1: ep::Operand::Reference(addr3), | ||||
|                 }, | ||||
|                 destination: Destination::Address(addr4), | ||||
|             } | ||||
|         ] | ||||
|     ); | ||||
| } | ||||
|  | ||||
| @ -15,7 +15,7 @@ databake = "0.1.7" | ||||
| kcl-lib = { path = "../kcl" } | ||||
| proc-macro2 = "1" | ||||
| quote = "1" | ||||
| syn = { version = "2.0.39", features = ["full"] } | ||||
| syn = { version = "2.0.49", features = ["full"] } | ||||
|  | ||||
| [dev-dependencies] | ||||
| pretty_assertions = "1.4.0" | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| [package] | ||||
| name = "kcl-lib" | ||||
| description = "KittyCAD Language implementation and tools" | ||||
| version = "0.1.40" | ||||
| version = "0.1.42" | ||||
| edition = "2021" | ||||
| license = "MIT" | ||||
| repository = "https://github.com/KittyCAD/modeling-app" | ||||
| @ -17,14 +17,17 @@ async-trait = "0.1.77" | ||||
| clap = { version = "4.5.0", features = ["cargo", "derive", "env", "unicode"], optional = true } | ||||
| dashmap = "5.5.3" | ||||
| databake = { version = "0.1.7", features = ["derive"] } | ||||
| #derive-docs = { version = "0.1.6" } | ||||
| derive-docs = { path = "../derive-docs" } | ||||
| derive-docs = { version = "0.1.6" } | ||||
| #derive-docs = { path = "../derive-docs" } | ||||
| futures = { version = "0.3.30" } | ||||
| gltf-json = "1.4.0" | ||||
| kittycad = { workspace = true } | ||||
| kittycad-execution-plan-macros = { workspace = true } | ||||
| kittycad-execution-plan-traits = { workspace = true } | ||||
| lazy_static = "1.4.0" | ||||
| parse-display = "0.9.0" | ||||
| reqwest = { version = "0.11.24", default-features = false, features = ["stream", "rustls-tls"] } | ||||
| ropey = "1.6.1" | ||||
| schemars = { version = "0.8.16", features = ["impl_json_schema", "url", "uuid1"] } | ||||
| serde = { version = "1.0.193", features = ["derive"] } | ||||
| serde_json = "1.0.108" | ||||
| @ -43,8 +46,6 @@ web-sys = { version = "0.3.68", features = ["console"] } | ||||
| [target.'cfg(not(target_arch = "wasm32"))'.dependencies] | ||||
| approx = "0.5" | ||||
| bson = { version = "2.9.0", features = ["uuid-1", "chrono"] } | ||||
| futures = { version = "0.3.30" } | ||||
| reqwest = { version = "0.11.24", default-features = false } | ||||
| tokio = { version = "1.36.0", features = ["full"] } | ||||
| tokio-tungstenite = { version = "0.21.0", features = ["rustls-tls-native-roots"] } | ||||
| tower-lsp = { version = "0.20.0", features = ["proposed"] } | ||||
|  | ||||
| @ -10,7 +10,7 @@ use serde::{Deserialize, Serialize}; | ||||
| use serde_json::{Map, Value as JValue}; | ||||
| use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, Range as LspRange, SymbolKind}; | ||||
|  | ||||
| pub use self::{literal_value::LiteralValue, none::KclNone}; | ||||
| pub use crate::ast::types::{literal_value::LiteralValue, none::KclNone}; | ||||
| use crate::{ | ||||
|     docs::StdLibFn, | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|  | ||||
| @ -13,7 +13,7 @@ use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange}; | ||||
|  | ||||
| use crate::{ | ||||
|     ast::types::{BodyItem, FunctionExpression, KclNone, Value}, | ||||
|     engine::EngineConnection, | ||||
|     engine::{EngineConnection, EngineManager}, | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     fs::FileManager, | ||||
|     std::{FunctionKind, StdLib}, | ||||
| @ -418,6 +418,8 @@ pub struct SketchGroup { | ||||
|     pub id: uuid::Uuid, | ||||
|     /// The paths in the sketch group. | ||||
|     pub value: Vec<Path>, | ||||
|     /// What the sketch is on (can be a plane or a face). | ||||
|     pub on: SketchSurface, | ||||
|     /// The starting path. | ||||
|     pub start: BasePath, | ||||
|     /// The position of the sketch group. | ||||
| @ -437,6 +439,42 @@ pub struct SketchGroup { | ||||
|     pub meta: Vec<Metadata>, | ||||
| } | ||||
|  | ||||
| /// A sketch group type. | ||||
| #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| #[ts(export)] | ||||
| #[serde(tag = "type", rename_all = "camelCase")] | ||||
| pub enum SketchSurface { | ||||
|     Plane(Box<Plane>), | ||||
|     Face(Box<Face>), | ||||
| } | ||||
|  | ||||
| impl SketchSurface { | ||||
|     pub fn id(&self) -> uuid::Uuid { | ||||
|         match self { | ||||
|             SketchSurface::Plane(plane) => plane.id, | ||||
|             SketchSurface::Face(face) => face.id, | ||||
|         } | ||||
|     } | ||||
|     pub fn x_axis(&self) -> Point3d { | ||||
|         match self { | ||||
|             SketchSurface::Plane(plane) => plane.x_axis.clone(), | ||||
|             SketchSurface::Face(face) => face.x_axis.clone(), | ||||
|         } | ||||
|     } | ||||
|     pub fn y_axis(&self) -> Point3d { | ||||
|         match self { | ||||
|             SketchSurface::Plane(plane) => plane.y_axis.clone(), | ||||
|             SketchSurface::Face(face) => face.y_axis.clone(), | ||||
|         } | ||||
|     } | ||||
|     pub fn z_axis(&self) -> Point3d { | ||||
|         match self { | ||||
|             SketchSurface::Plane(plane) => plane.z_axis.clone(), | ||||
|             SketchSurface::Face(face) => face.z_axis.clone(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct GetTangentialInfoFromPathsResult { | ||||
|     pub center_or_tangent_point: [f64; 2], | ||||
|     pub is_center: bool, | ||||
| @ -912,27 +950,30 @@ pub struct ExecutorContext { | ||||
|     pub engine: EngineConnection, | ||||
|     pub fs: FileManager, | ||||
|     pub stdlib: Arc<StdLib>, | ||||
|     pub units: kittycad::types::UnitLength, | ||||
| } | ||||
|  | ||||
| impl ExecutorContext { | ||||
|     /// Create a new default executor context. | ||||
|     #[cfg(test)] | ||||
|     pub async fn new() -> Result<Self> { | ||||
|     pub async fn new(units: kittycad::types::UnitLength) -> Result<Self> { | ||||
|         Ok(Self { | ||||
|             engine: EngineConnection::new().await?, | ||||
|             fs: FileManager::new(), | ||||
|             stdlib: Arc::new(StdLib::new()), | ||||
|             units, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /// Create a new default executor context. | ||||
|     #[cfg(not(test))] | ||||
|     #[cfg(not(target_arch = "wasm32"))] | ||||
|     pub async fn new(ws: reqwest::Upgraded) -> Result<Self> { | ||||
|     pub async fn new(ws: reqwest::Upgraded, units: kittycad::types::UnitLength) -> Result<Self> { | ||||
|         Ok(Self { | ||||
|             engine: EngineConnection::new(ws).await?, | ||||
|             fs: FileManager::new(), | ||||
|             stdlib: Arc::new(StdLib::new()), | ||||
|             units, | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| @ -945,6 +986,17 @@ pub async fn execute( | ||||
|     options: BodyType, | ||||
|     ctx: &ExecutorContext, | ||||
| ) -> Result<ProgramMemory, KclError> { | ||||
|     // Before we even start executing the program, set the units. | ||||
|     ctx.engine | ||||
|         .send_modeling_cmd( | ||||
|             uuid::Uuid::new_v4(), | ||||
|             SourceRange::default(), | ||||
|             kittycad::types::ModelingCmd::SetSceneUnits { | ||||
|                 unit: ctx.units.clone(), | ||||
|             }, | ||||
|         ) | ||||
|         .await?; | ||||
|  | ||||
|     let mut pipe_info = PipeInfo::default(); | ||||
|  | ||||
|     // Iterate over the body of the program. | ||||
| @ -1231,7 +1283,7 @@ mod tests { | ||||
|         let parser = crate::parser::Parser::new(tokens); | ||||
|         let program = parser.ast()?; | ||||
|         let mut mem: ProgramMemory = Default::default(); | ||||
|         let ctx = ExecutorContext::new().await?; | ||||
|         let ctx = ExecutorContext::new(kittycad::types::UnitLength::Mm).await?; | ||||
|         let memory = execute(program, &mut mem, BodyType::Root, &ctx).await?; | ||||
|  | ||||
|         Ok(memory) | ||||
|  | ||||
| @ -53,4 +53,38 @@ impl FileSystem for FileManager { | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     async fn get_all_files<P: AsRef<std::path::Path>>( | ||||
|         &self, | ||||
|         path: P, | ||||
|         source_range: crate::executor::SourceRange, | ||||
|     ) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> { | ||||
|         let mut files = vec![]; | ||||
|         let mut stack = vec![path.as_ref().to_path_buf()]; | ||||
|  | ||||
|         while let Some(path) = stack.pop() { | ||||
|             if !path.is_dir() { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             let mut read_dir = tokio::fs::read_dir(&path).await.map_err(|e| { | ||||
|                 KclError::Engine(KclErrorDetails { | ||||
|                     message: format!("Failed to read directory `{}`: {}", path.display(), e), | ||||
|                     source_ranges: vec![source_range], | ||||
|                 }) | ||||
|             })?; | ||||
|  | ||||
|             while let Ok(Some(entry)) = read_dir.next_entry().await { | ||||
|                 let path = entry.path(); | ||||
|                 if path.is_dir() { | ||||
|                     // Iterate over the directory. | ||||
|                     stack.push(path); | ||||
|                 } else { | ||||
|                     files.push(path); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         Ok(files) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -28,4 +28,11 @@ pub trait FileSystem: Clone { | ||||
|         path: P, | ||||
|         source_range: crate::executor::SourceRange, | ||||
|     ) -> Result<bool, crate::errors::KclError>; | ||||
|  | ||||
|     /// Get all the files in a directory recursively. | ||||
|     async fn get_all_files<P: AsRef<std::path::Path>>( | ||||
|         &self, | ||||
|         path: P, | ||||
|         source_range: crate::executor::SourceRange, | ||||
|     ) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError>; | ||||
| } | ||||
|  | ||||
| @ -18,6 +18,9 @@ extern "C" { | ||||
|  | ||||
|     #[wasm_bindgen(method, js_name = exists, catch)] | ||||
|     fn exists(this: &FileSystemManager, path: String) -> Result<js_sys::Promise, js_sys::Error>; | ||||
|  | ||||
|     #[wasm_bindgen(method, js_name = getAllFiles, catch)] | ||||
|     fn get_all_files(this: &FileSystemManager, path: String) -> Result<js_sys::Promise, js_sys::Error>; | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| @ -31,6 +34,9 @@ impl FileManager { | ||||
|     } | ||||
| } | ||||
|  | ||||
| unsafe impl Send for FileManager {} | ||||
| unsafe impl Sync for FileManager {} | ||||
|  | ||||
| #[async_trait::async_trait(?Send)] | ||||
| impl FileSystem for FileManager { | ||||
|     async fn read<P: AsRef<std::path::Path>>( | ||||
| @ -112,4 +118,53 @@ impl FileSystem for FileManager { | ||||
|  | ||||
|         Ok(it_exists) | ||||
|     } | ||||
|  | ||||
|     async fn get_all_files<P: AsRef<std::path::Path>>( | ||||
|         &self, | ||||
|         path: P, | ||||
|         source_range: crate::executor::SourceRange, | ||||
|     ) -> Result<Vec<std::path::PathBuf>, crate::errors::KclError> { | ||||
|         let promise = self | ||||
|             .manager | ||||
|             .get_all_files( | ||||
|                 path.as_ref() | ||||
|                     .to_str() | ||||
|                     .ok_or_else(|| { | ||||
|                         KclError::Engine(KclErrorDetails { | ||||
|                             message: "Failed to convert path to string".to_string(), | ||||
|                             source_ranges: vec![source_range], | ||||
|                         }) | ||||
|                     })? | ||||
|                     .to_string(), | ||||
|             ) | ||||
|             .map_err(|e| { | ||||
|                 KclError::Engine(KclErrorDetails { | ||||
|                     message: e.to_string().into(), | ||||
|                     source_ranges: vec![source_range], | ||||
|                 }) | ||||
|             })?; | ||||
|  | ||||
|         let value = wasm_bindgen_futures::JsFuture::from(promise).await.map_err(|e| { | ||||
|             KclError::Engine(KclErrorDetails { | ||||
|                 message: format!("Failed to wait for promise from javascript: {:?}", e), | ||||
|                 source_ranges: vec![source_range], | ||||
|             }) | ||||
|         })?; | ||||
|  | ||||
|         let s = value.as_string().ok_or_else(|| { | ||||
|             KclError::Engine(KclErrorDetails { | ||||
|                 message: format!("Failed to get string from response from javascript: `{:?}`", value), | ||||
|                 source_ranges: vec![source_range], | ||||
|             }) | ||||
|         })?; | ||||
|  | ||||
|         let files: Vec<String> = serde_json::from_str(&s).map_err(|e| { | ||||
|             KclError::Engine(KclErrorDetails { | ||||
|                 message: format!("Failed to parse json from javascript: `{}` `{:?}`", s, e), | ||||
|                 source_ranges: vec![source_range], | ||||
|             }) | ||||
|         })?; | ||||
|  | ||||
|         Ok(files.into_iter().map(|s| std::path::PathBuf::from(s)).collect()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -10,7 +10,7 @@ pub mod engine; | ||||
| pub mod errors; | ||||
| pub mod executor; | ||||
| pub mod fs; | ||||
| pub mod lsp; | ||||
| pub mod parser; | ||||
| pub mod server; | ||||
| pub mod std; | ||||
| pub mod token; | ||||
|  | ||||
							
								
								
									
										121
									
								
								src/wasm-lib/kcl/src/lsp/backend.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,121 @@ | ||||
| //! A shared backend trait for lsp servers memory and behavior. | ||||
|  | ||||
| use dashmap::DashMap; | ||||
| use tower_lsp::lsp_types::{ | ||||
|     CreateFilesParams, DeleteFilesParams, DidChangeConfigurationParams, DidChangeTextDocumentParams, | ||||
|     DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, | ||||
|     DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializedParams, MessageType, RenameFilesParams, | ||||
|     TextDocumentItem, | ||||
| }; | ||||
|  | ||||
| /// A trait for the backend of the language server. | ||||
| #[async_trait::async_trait] | ||||
| pub trait Backend { | ||||
|     fn client(&self) -> tower_lsp::Client; | ||||
|  | ||||
|     fn fs(&self) -> crate::fs::FileManager; | ||||
|  | ||||
|     /// Get the current code map. | ||||
|     fn current_code_map(&self) -> DashMap<String, String>; | ||||
|  | ||||
|     /// Insert a new code map. | ||||
|     fn insert_current_code_map(&self, uri: String, text: String); | ||||
|  | ||||
|     /// On change event. | ||||
|     async fn on_change(&self, params: TextDocumentItem); | ||||
|  | ||||
|     async fn update_memory(&self, params: TextDocumentItem) { | ||||
|         // Lets update the tokens. | ||||
|         self.insert_current_code_map(params.uri.to_string(), params.text.clone()); | ||||
|     } | ||||
|  | ||||
|     async fn do_initialized(&self, params: InitializedParams) { | ||||
|         self.client() | ||||
|             .log_message(MessageType::INFO, format!("initialized: {:?}", params)) | ||||
|             .await; | ||||
|     } | ||||
|  | ||||
|     async fn do_shutdown(&self) -> tower_lsp::jsonrpc::Result<()> { | ||||
|         self.client() | ||||
|             .log_message(MessageType::INFO, "shutdown".to_string()) | ||||
|             .await; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     async fn do_did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) { | ||||
|         self.client() | ||||
|             .log_message(MessageType::INFO, format!("workspace folders changed: {:?}", params)) | ||||
|             .await; | ||||
|     } | ||||
|  | ||||
|     async fn do_did_change_configuration(&self, params: DidChangeConfigurationParams) { | ||||
|         self.client() | ||||
|             .log_message(MessageType::INFO, format!("configuration changed: {:?}", params)) | ||||
|             .await; | ||||
|     } | ||||
|  | ||||
|     async fn do_did_change_watched_files(&self, params: DidChangeWatchedFilesParams) { | ||||
|         self.client() | ||||
|             .log_message(MessageType::INFO, format!("watched files changed: {:?}", params)) | ||||
|             .await; | ||||
|     } | ||||
|  | ||||
|     async fn do_did_create_files(&self, params: CreateFilesParams) { | ||||
|         self.client() | ||||
|             .log_message(MessageType::INFO, format!("files created: {:?}", params)) | ||||
|             .await; | ||||
|     } | ||||
|  | ||||
|     async fn do_did_rename_files(&self, params: RenameFilesParams) { | ||||
|         self.client() | ||||
|             .log_message(MessageType::INFO, format!("files renamed: {:?}", params)) | ||||
|             .await; | ||||
|     } | ||||
|  | ||||
|     async fn do_did_delete_files(&self, params: DeleteFilesParams) { | ||||
|         self.client() | ||||
|             .log_message(MessageType::INFO, format!("files deleted: {:?}", params)) | ||||
|             .await; | ||||
|     } | ||||
|  | ||||
|     async fn do_did_open(&self, params: DidOpenTextDocumentParams) { | ||||
|         let new_params = TextDocumentItem { | ||||
|             uri: params.text_document.uri, | ||||
|             text: params.text_document.text, | ||||
|             version: params.text_document.version, | ||||
|             language_id: params.text_document.language_id, | ||||
|         }; | ||||
|         self.update_memory(new_params.clone()).await; | ||||
|         self.on_change(new_params).await; | ||||
|     } | ||||
|  | ||||
|     async fn do_did_change(&self, mut params: DidChangeTextDocumentParams) { | ||||
|         let new_params = TextDocumentItem { | ||||
|             uri: params.text_document.uri, | ||||
|             text: std::mem::take(&mut params.content_changes[0].text), | ||||
|             version: params.text_document.version, | ||||
|             language_id: Default::default(), | ||||
|         }; | ||||
|         self.update_memory(new_params.clone()).await; | ||||
|         self.on_change(new_params).await; | ||||
|     } | ||||
|  | ||||
|     async fn do_did_save(&self, params: DidSaveTextDocumentParams) { | ||||
|         if let Some(text) = params.text { | ||||
|             let new_params = TextDocumentItem { | ||||
|                 uri: params.text_document.uri, | ||||
|                 text, | ||||
|                 version: Default::default(), | ||||
|                 language_id: Default::default(), | ||||
|             }; | ||||
|             self.update_memory(new_params.clone()).await; | ||||
|             self.on_change(new_params).await; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async fn do_did_close(&self, params: DidCloseTextDocumentParams) { | ||||
|         self.client() | ||||
|             .log_message(MessageType::INFO, format!("document closed: {:?}", params)) | ||||
|             .await; | ||||
|     } | ||||
| } | ||||