Compare commits
	
		
			22 Commits
		
	
	
		
			pierremtb/
			...
			delete-net
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| dac3c0c224 | |||
| 99cb6a6179 | |||
| 13dbfdfaa4 | |||
| 8603f5c53a | |||
| d5cc9e8386 | |||
| 533e17466b | |||
| 01c7b69f50 | |||
| 47feae3bd9 | |||
| e7ecd655c4 | |||
| d9e538c6ea | |||
| e297f8286f | |||
| b3bc90bbe4 | |||
| 95a02cbcd7 | |||
| a049768f1c | |||
| 818d9a0d77 | |||
| 1a8f80a7dc | |||
| 566c9eaf10 | |||
| e6485c2da1 | |||
| 0479edd36a | |||
| 87c1e92134 | |||
| 8f950ac1b0 | |||
| 78e4f43708 | 
							
								
								
									
										21
									
								
								.github/workflows/build-apps.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -7,11 +7,10 @@ on: | ||||
|       - main | ||||
|     tags: | ||||
|       - 'v[0-9]+.[0-9]+.[0-9]+' | ||||
|       - 'nightly-v[0-9]+.[0-9]+.[0-9]+' | ||||
|  | ||||
| env: | ||||
|   IS_RELEASE: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }} | ||||
|   IS_NIGHTLY: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'nightly-v') }} | ||||
|   IS_NIGHTLY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} | ||||
|  | ||||
| concurrency: | ||||
|   group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} | ||||
| @ -95,7 +94,9 @@ jobs: | ||||
|       - name: Set nightly version, product name, release notes, and icons | ||||
|         if: ${{ env.IS_NIGHTLY == 'true' }} | ||||
|         run: | | ||||
|           export VERSION=${GITHUB_REF_NAME#nightly-v} | ||||
|           COMMIT=$(git rev-parse --short HEAD) | ||||
|           DATE=$(date +'%-y.%-m.%-d') | ||||
|           export VERSION=$DATE-main.$COMMIT | ||||
|           npm run files:set-version | ||||
|           npm run files:flip-to-nightly | ||||
|  | ||||
| @ -306,7 +307,8 @@ jobs: | ||||
|     runs-on: ubuntu-22.04 | ||||
|     permissions: | ||||
|       contents: write | ||||
|     if: ${{ github.ref_type == 'tag' }} | ||||
|     # Equivalent to IS_RELEASE || IS_NIGHTLY (but we can't access those env vars here) | ||||
|     if: ${{ (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) || (github.event_name == 'push' && github.ref == 'refs/heads/main') }} | ||||
|     env: | ||||
|       VERSION_NO_V: ${{ needs.prepare-files.outputs.version }} | ||||
|       VERSION: ${{ format('v{0}', needs.prepare-files.outputs.version) }} | ||||
| @ -412,17 +414,6 @@ jobs: | ||||
|       - name: List artifacts | ||||
|         run: "ls -R out" | ||||
|  | ||||
|       - name: Set more complete nightly release notes | ||||
|         if: ${{ env.IS_NIGHTLY == 'true' }} | ||||
|         run: | | ||||
|           # Note: preferred going this way instead of a full clone in the checkout step, | ||||
|           # see https://github.com/actions/checkout/issues/1471 | ||||
|           git fetch --prune --unshallow --tags | ||||
|           export TAG="nightly-${VERSION}" | ||||
|           export PREVIOUS_TAG=$(git tag --list --sort=-committerdate "nightly-v[0-9]*" | head -n2 | tail -n1) | ||||
|           export NOTES=$(./scripts/get-nightly-changelog.sh) | ||||
|           npm run files:set-notes | ||||
|  | ||||
|       - name: Authenticate to Google Cloud | ||||
|         if: ${{ env.IS_NIGHTLY == 'true' }} | ||||
|         uses: 'google-github-actions/auth@v2.1.8' | ||||
|  | ||||
							
								
								
									
										47
									
								
								.github/workflows/check-exampleKcl.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,47 +0,0 @@ | ||||
| name: Check Onboarding KCL | ||||
|  | ||||
| on: | ||||
|   pull_request: | ||||
|     types: [opened, synchronize] | ||||
|     paths: | ||||
|       - 'src/lib/exampleKcl.ts' | ||||
|       - 'public/kcl-samples/bracket/main.kcl' | ||||
|  | ||||
| permissions: | ||||
|   contents: read | ||||
|   issues: write | ||||
|   pull-requests: write | ||||
|  | ||||
| jobs: | ||||
|   comment: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout repository | ||||
|         uses: actions/checkout@v4 | ||||
|  | ||||
|       - name: Comment on PR | ||||
|         uses: actions/github-script@v7 | ||||
|         with: | ||||
|           script: | | ||||
|             const message = '`public/kcl-samples/bracket/main.kcl` or `src/lib/exampleKcl.ts` has been updated in this PR, please review and update the `src/routes/onboarding`, if needed.'; | ||||
|             const issue_number = context.payload.pull_request.number; | ||||
|             const owner = context.repo.owner; | ||||
|             const repo = context.repo.repo; | ||||
|  | ||||
|             const { data: comments } = await github.rest.issues.listComments({ | ||||
|               owner, | ||||
|               repo, | ||||
|               issue_number | ||||
|             }); | ||||
|  | ||||
|             const commentExists = comments.some(comment => comment.body === message); | ||||
|  | ||||
|             if (!commentExists) { | ||||
|               // Post a comment on the PR | ||||
|               await github.rest.issues.createComment({ | ||||
|                 owner, | ||||
|                 repo, | ||||
|                 issue_number, | ||||
|                 body: message, | ||||
|               }); | ||||
|             } | ||||
							
								
								
									
										114
									
								
								.github/workflows/e2e-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -18,73 +18,13 @@ permissions: | ||||
|  | ||||
| jobs: | ||||
|  | ||||
|   conditions: | ||||
|     runs-on: ubuntu-latest | ||||
|     outputs: | ||||
|       significant: ${{ steps.path-changes.outputs.significant }} | ||||
|       should-run: ${{ steps.should-run.outputs.should-run }} | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|  | ||||
|       - name: Fetch the base branch | ||||
|         if: ${{ github.event_name == 'pull_request' }} | ||||
|         run: git fetch origin ${{ github.base_ref }} --depth=1 | ||||
|  | ||||
|       - name: Check for path changes | ||||
|         id: path-changes | ||||
|         shell: bash | ||||
|         run: | | ||||
|           set -euo pipefail | ||||
|  | ||||
|           # Manual runs or push should run all tests. | ||||
|           if [[ ${{ github.event_name }} != 'pull_request' ]]; then | ||||
|             echo "significant=true" >> $GITHUB_OUTPUT | ||||
|             exit 0 | ||||
|           fi | ||||
|  | ||||
|           changed_files=$(git diff --name-only origin/${{ github.base_ref }}) | ||||
|           echo "$changed_files" | ||||
|           if grep -Evq '^README.md|^public/kcl-samples/|^rust/kcl-lib/tests/kcl_samples/' <<< "$changed_files" ; then | ||||
|             echo "significant=true" >> $GITHUB_OUTPUT | ||||
|           else | ||||
|             echo "significant=false" >> $GITHUB_OUTPUT | ||||
|           fi | ||||
|  | ||||
|       - name: Should run | ||||
|         id: should-run | ||||
|         shell: bash | ||||
|         run: | | ||||
|           set -euo pipefail | ||||
|           # We should run when this is a scheduled run or if there are | ||||
|           # significant changes in the diff. | ||||
|           if [[ ${{ github.event_name }} == 'schedule' || ${{ steps.path-changes.outputs.significant }} == 'true' ]]; then | ||||
|             echo "should-run=true" >> $GITHUB_OUTPUT | ||||
|           else | ||||
|             echo "should-run=false" >> $GITHUB_OUTPUT | ||||
|           fi | ||||
|  | ||||
|       - name: Display conditions | ||||
|         shell: bash | ||||
|         run: | | ||||
|           # For debugging purposes | ||||
|           set -euo pipefail | ||||
|           echo "GITHUB_REF: $GITHUB_REF" | ||||
|           echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF" | ||||
|           echo "GITHUB_BASE_REF: $GITHUB_BASE_REF" | ||||
|           echo "significant: ${{ steps.path-changes.outputs.significant }}" | ||||
|           echo "should-run: ${{ steps.should-run.outputs.should-run }}" | ||||
|  | ||||
|   prepare-wasm: | ||||
|     # separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner | ||||
|     runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64 | ||||
|     needs: conditions | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|  | ||||
|       - id: filter | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         name: Check for Rust changes | ||||
|         uses: dorny/paths-filter@v3 | ||||
|         with: | ||||
| @ -93,18 +33,16 @@ jobs: | ||||
|               - 'rust/**' | ||||
|  | ||||
|       - uses: actions/setup-node@v4 | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         with: | ||||
|           node-version-file: '.nvmrc' | ||||
|           cache: 'npm' | ||||
|  | ||||
|       - name: Install dependencies | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         run: npm install | ||||
|  | ||||
|       - name: Download Wasm Cache | ||||
|         id: download-wasm | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }} | ||||
|         if: ${{ github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }} | ||||
|         uses: dawidd6/action-download-artifact@v7 | ||||
|         continue-on-error: true | ||||
|         with: | ||||
| @ -116,7 +54,6 @@ jobs: | ||||
|  | ||||
|       - name: Build WASM condition | ||||
|         id: wasm | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         run: | | ||||
|           set -euox pipefail | ||||
|           # Build wasm if this is a scheduled run, there are Rust changes, or | ||||
| @ -128,35 +65,34 @@ jobs: | ||||
|           fi | ||||
|  | ||||
|       - name: Use correct Rust toolchain | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }} | ||||
|         if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} | ||||
|         shell: bash | ||||
|         run: | | ||||
|           [ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ | ||||
|  | ||||
|       - name: Install rust | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }} | ||||
|         if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} | ||||
|         uses: actions-rust-lang/setup-rust-toolchain@v1 | ||||
|         with: | ||||
|           cache: false # Configured below. | ||||
|  | ||||
|       - uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }} | ||||
|         if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} | ||||
|         with: | ||||
|           tool: wasm-pack | ||||
|  | ||||
|       - name: Rust Cache | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }} | ||||
|         if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} | ||||
|         uses: Swatinem/rust-cache@v2 | ||||
|         with: | ||||
|           workspaces: './rust' | ||||
|  | ||||
|       - name: Build Wasm | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }} | ||||
|         if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }} | ||||
|         shell: bash | ||||
|         run: npm run build:wasm | ||||
|  | ||||
|       - uses: actions/upload-artifact@v4 | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         with: | ||||
|           name: prepared-wasm | ||||
|           path: | | ||||
| @ -165,10 +101,9 @@ jobs: | ||||
|   snapshots: | ||||
|     name: playwright:snapshots:ubuntu | ||||
|     runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64 | ||||
|     needs: [conditions, prepare-wasm] | ||||
|     needs: [prepare-wasm] | ||||
|     steps: | ||||
|       - uses: actions/create-github-app-token@v1 | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         id: app-token | ||||
|         with: | ||||
|           app-id: ${{ secrets.MODELING_APP_GH_APP_ID }} | ||||
| @ -176,16 +111,13 @@ jobs: | ||||
|           owner: ${{ github.repository_owner }} | ||||
|  | ||||
|       - uses: actions/checkout@v4 | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         with: | ||||
|           token: ${{ steps.app-token.outputs.token }} | ||||
|  | ||||
|       - uses: actions/download-artifact@v4 | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         name: prepared-wasm | ||||
|  | ||||
|       - name: Copy prepared wasm | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         run: | | ||||
|           ls -R prepared-wasm | ||||
|           cp prepared-wasm/kcl_wasm_lib_bg.wasm public | ||||
| @ -193,18 +125,15 @@ jobs: | ||||
|           cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg | ||||
|  | ||||
|       - uses: actions/setup-node@v4 | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         with: | ||||
|           node-version-file: '.nvmrc' | ||||
|           cache: 'npm' | ||||
|  | ||||
|       - name: Install dependencies | ||||
|         id: deps-install | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         run: npm install | ||||
|  | ||||
|       - name: Cache Playwright Browsers | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: | | ||||
| @ -212,15 +141,12 @@ jobs: | ||||
|           key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} | ||||
|  | ||||
|       - name: Install Playwright Browsers | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         run: npm run playwright install --with-deps | ||||
|  | ||||
|       - name: build web | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         run: npm run tronb:vite:dev | ||||
|  | ||||
|       - name: Run ubuntu/chrome snapshots | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         uses: nick-fields/retry@v3.0.2 | ||||
|         with: | ||||
|           shell: bash | ||||
| @ -236,7 +162,7 @@ jobs: | ||||
|           TARGET: web | ||||
|  | ||||
|       - uses: actions/upload-artifact@v4 | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }} | ||||
|         if: ${{ !cancelled() && (success() || failure()) }} | ||||
|         with: | ||||
|           name: playwright-report-ubuntu-snapshot-${{ github.sha }} | ||||
|           path: playwright-report/ | ||||
| @ -245,7 +171,7 @@ jobs: | ||||
|           overwrite: true | ||||
|  | ||||
|       - name: Check for changes | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && github.ref != 'refs/heads/main' }} | ||||
|         if: ${{ github.ref != 'refs/heads/main' }} | ||||
|         shell: bash | ||||
|         id: git-check | ||||
|         run: | | ||||
| @ -257,7 +183,7 @@ jobs: | ||||
|  | ||||
|       - name: Commit changes, if any | ||||
|         # TODO: find a more reliable way to detect visual changes | ||||
|         if: ${{ false && needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }} | ||||
|         if: ${{ false && steps.git-check.outputs.modified == 'true' }} | ||||
|         shell: bash | ||||
|         run: | | ||||
|           git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots | ||||
| @ -272,7 +198,7 @@ jobs: | ||||
|           git push origin ${{ github.head_ref }} | ||||
|  | ||||
|   electron: | ||||
|     needs: [conditions, prepare-wasm] | ||||
|     needs: [prepare-wasm] | ||||
|     timeout-minutes: 60 | ||||
|     env: | ||||
|       OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }} | ||||
| @ -315,14 +241,11 @@ jobs: | ||||
|     runs-on: ${{ matrix.os }} | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|  | ||||
|       - uses: actions/download-artifact@v4 | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         name: prepared-wasm | ||||
|  | ||||
|       - name: Copy prepared wasm | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         run: | | ||||
|           ls -R prepared-wasm | ||||
|           cp prepared-wasm/kcl_wasm_lib_bg.wasm public | ||||
| @ -330,18 +253,15 @@ jobs: | ||||
|           cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg | ||||
|  | ||||
|       - uses: actions/setup-node@v4 | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         with: | ||||
|           node-version-file: '.nvmrc' | ||||
|           cache: 'npm' | ||||
|  | ||||
|       - name: Install dependencies | ||||
|         id: deps-install | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         run: npm install | ||||
|  | ||||
|       - name: Cache Playwright Browsers | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         uses: actions/cache@v4 | ||||
|         with: | ||||
|           path: | | ||||
| @ -349,22 +269,20 @@ jobs: | ||||
|           key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }} | ||||
|  | ||||
|       - name: Install Playwright Browsers | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         run: npm run playwright install --with-deps | ||||
|  | ||||
|       - name: Build web | ||||
|         if: needs.conditions.outputs.should-run == 'true' | ||||
|         run: npm run tronb:vite:dev | ||||
|  | ||||
|       - name: Start Vector | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && !contains(matrix.os, 'windows') }} | ||||
|         if: ${{ !contains(matrix.os, 'windows') }} | ||||
|         run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh | ||||
|         env: | ||||
|           GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }} | ||||
|           OS_NAME: ${{ env.OS_NAME }} | ||||
|  | ||||
|       - uses: actions/download-artifact@v4 | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }} | ||||
|         if: ${{ !cancelled() && (success() || failure()) }} | ||||
|         continue-on-error: true | ||||
|         with: | ||||
|           name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} | ||||
| @ -372,7 +290,7 @@ jobs: | ||||
|  | ||||
|       - name: Run playwright/electron flow (with retries) | ||||
|         id: retry | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && steps.deps-install.outcome == 'success' }} | ||||
|         if:  ${{ !cancelled() && steps.deps-install.outcome == 'success' }} | ||||
|         uses: nick-fields/retry@v3.0.2 | ||||
|         with: | ||||
|           shell: bash | ||||
| @ -389,7 +307,7 @@ jobs: | ||||
|           TARGET: desktop | ||||
|  | ||||
|       - uses: actions/upload-artifact@v4 | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && always() }} | ||||
|         if: always() | ||||
|         with: | ||||
|           name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} | ||||
|           path: test-results/ | ||||
| @ -398,7 +316,7 @@ jobs: | ||||
|           overwrite: true | ||||
|  | ||||
|       - uses: actions/upload-artifact@v4 | ||||
|         if: ${{ needs.conditions.outputs.should-run == 'true' && always() }} | ||||
|         if: always() | ||||
|         with: | ||||
|           name: playwright-report-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} | ||||
|           path: playwright-report/ | ||||
|  | ||||
							
								
								
									
										8
									
								
								.github/workflows/static-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -213,7 +213,13 @@ jobs: | ||||
|         if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} | ||||
|         run: npm run playwright install chromium --with-deps | ||||
|  | ||||
|       - name: run unit tests for kcl samples | ||||
|       - name: Download internal KCL samples | ||||
|         run: git clone --depth=1 https://x-access-token:${{ secrets.GH_PAT_KCL_SAMPLES_INTERNAL }}@github.com/KittyCAD/kcl-samples-internal public/kcl-samples/internal | ||||
|  | ||||
|       - name: Regenerate KCL samples manifest | ||||
|         run: cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest | ||||
|  | ||||
|       - name: Check public and internal KCL samples | ||||
|         if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} | ||||
|         run: npm run test:unit:kcl-samples | ||||
|         env: | ||||
|  | ||||
							
								
								
									
										39
									
								
								.github/workflows/tag-nightly.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -1,39 +0,0 @@ | ||||
| name: tag-nightly | ||||
|  | ||||
| permissions: | ||||
|   contents: write | ||||
| on: | ||||
|   schedule: | ||||
|     - cron: '0 4 * * *' | ||||
|   # Daily at 04:00 AM UTC | ||||
|   # Will checkout the last commit from the default branch (main as of 2023-10-04) | ||||
|    | ||||
| jobs: | ||||
|   tag-nightly: | ||||
|     runs-on: ubuntu-22.04 | ||||
|     steps: | ||||
|       - uses: actions/create-github-app-token@v1 | ||||
|         id: app-token | ||||
|         with: | ||||
|           app-id: ${{ secrets.MODELING_APP_GH_APP_ID }} | ||||
|           private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }} | ||||
|           owner: ${{ github.repository_owner }} | ||||
|  | ||||
|       - uses: actions/checkout@v4 | ||||
|         with: | ||||
|           token: ${{ steps.app-token.outputs.token }} | ||||
|  | ||||
|       - uses: actions/setup-node@v4 | ||||
|         with: | ||||
|           node-version-file: '.nvmrc' | ||||
|  | ||||
|       - run: npm install | ||||
|  | ||||
|       - name: Push tag | ||||
|         run: | | ||||
|           VERSION_NO_V=$(date +'%-y.%-m.%-d') | ||||
|           TAG="nightly-v$VERSION_NO_V" | ||||
|           git config --local user.email "github-actions[bot]@users.noreply.github.com" | ||||
|           git config --local user.name "github-actions[bot]" | ||||
|           git tag $TAG | ||||
|           git push origin tag $TAG | ||||
							
								
								
									
										10
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						| @ -23,6 +23,7 @@ endif | ||||
| install: node_modules/.package-lock.json $(CARGO) $(WASM_PACK) ## Install dependencies | ||||
|  | ||||
| node_modules/.package-lock.json: package.json package-lock.json | ||||
| 	npm prune | ||||
| 	npm install | ||||
|  | ||||
| $(CARGO): | ||||
| @ -43,15 +44,15 @@ endif | ||||
| # BUILD | ||||
|  | ||||
| CARGO_SOURCES := rust/.cargo/config.toml $(wildcard rust/Cargo.*) $(wildcard rust/**/Cargo.*) | ||||
| KCL_SOURCES := $(wildcard public/kcl-samples/**/*.kcl) | ||||
| RUST_SOURCES := $(wildcard rust/**/*.rs) | ||||
|  | ||||
| REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx) | ||||
| TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts) | ||||
| VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx) | ||||
|  | ||||
|  | ||||
| .PHONY: build | ||||
| build: install public/kcl_wasm_lib_bg.wasm .vite/build/main.js | ||||
| build: install public/kcl_wasm_lib_bg.wasm public/kcl-samples/manifest.json .vite/build/main.js | ||||
|  | ||||
| public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES) $(RUST_SOURCES) | ||||
| ifdef WINDOWS | ||||
| @ -60,6 +61,9 @@ else | ||||
| 	npm run build:wasm:dev | ||||
| endif | ||||
|  | ||||
| public/kcl-samples/manifest.json: $(KCL_SOURCES) | ||||
| 	cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest | ||||
|  | ||||
| .vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES) | ||||
| 	npm run tronb:vite:dev | ||||
|  | ||||
| @ -107,8 +111,10 @@ test: test-unit test-e2e | ||||
| .PHONY: test-unit | ||||
| test-unit: install ## Run the unit tests | ||||
| 	npm run test:rust | ||||
| 	npm run test:unit:components | ||||
| 	@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 ) | ||||
| 	npm run test:unit | ||||
| 	npm run test:unit:kcl-samples | ||||
|  | ||||
| .PHONY: test-e2e | ||||
| test-e2e: test-e2e-$(TARGET) | ||||
|  | ||||
							
								
								
									
										32
									
								
								docs/kcl-lang/arrays.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,32 @@ | ||||
| --- | ||||
| title: "Arrays and ranges" | ||||
| excerpt: "Documentation of the KCL language for the Zoo Design Studio." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Arrays are sequences of values. | ||||
|  | ||||
| Arrays can be written out as *array literals* using a sequence of expressions surrounded by square brackets, e.g., `['hello', 'world']` is an array of strings, `[x, x + 1, x + 2]` is an array of numbers (assuming `x` is a number), `[]` is an empty array, and `['hello', 42, true]` is a mixed array. | ||||
|  | ||||
| A value in an array can be accessed by indexing using square brackets where the index is a number, for example, `arr[0]`, `arr[42]`, `arr[i]` (where `arr` is an array and `i` is a (whole) number). | ||||
|  | ||||
| There are some useful functions for working with arrays in the standard library, see [std::array](/docs/kcl-std/modules/std-array) for details. | ||||
|  | ||||
| ## Array types | ||||
|  | ||||
| Arrays have their own types: `[T]` where `T` is the type of the elements of the array, for example, `[string]` means an array of `string`s and `[any]` means an array of any values. | ||||
|  | ||||
| Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; 1+]` denotes an array whose length is at least one (i.e., a non-empty array). E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types. | ||||
|  | ||||
| ## Ranges | ||||
|  | ||||
| Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. Examples: | ||||
|  | ||||
| ```kcl,norun | ||||
| [0..3]      // [0, 1, 2, 3] | ||||
| [3..10]     // [3, 4, 5, 6, 7, 8, 9, 10] | ||||
| x = 2 | ||||
| [x..x+1]    // [2, 3] | ||||
| ``` | ||||
|  | ||||
| The units of the start and end numbers must be the same and the result inherits those units.  | ||||
| @ -14,6 +14,7 @@ things in a more tutorial fashion. See also our documentation of the [standard l | ||||
| * [Values and types](/docs/kcl-lang/types) | ||||
| * [Numeric types and units](/docs/kcl-lang/numeric) | ||||
| * [Functions](/docs/kcl-lang/functions) | ||||
| * [Arrays and ranges](/docs/kcl-lang/arrays) | ||||
| * [Projects and modules](/docs/kcl-lang/modules) | ||||
| * [Attributes](/docs/kcl-lang/attributes) | ||||
| * [Importing geometry from other CAD systems](/docs/kcl-lang/foreign-imports) | ||||
|  | ||||
| @ -19,18 +19,6 @@ myBool = false | ||||
|  | ||||
| Currently you cannot redeclare a constant. | ||||
|  | ||||
| ## Arrays | ||||
|  | ||||
| An array is defined with `[]` braces. What is inside the brackets can | ||||
| be of any type. For example, the following is completely valid: | ||||
|  | ||||
| ``` | ||||
| myArray = ["thing", 2, false] | ||||
| ``` | ||||
|  | ||||
| If you want to get a value from an array you can use the index like so: | ||||
| `myArray[0]`. | ||||
|  | ||||
|  | ||||
| ## Objects | ||||
|  | ||||
| @ -40,8 +28,8 @@ An object is defined with `{}` braces. Here is an example object: | ||||
| myObj = { a = 0, b = "thing" } | ||||
| ``` | ||||
|  | ||||
| We support two different ways of getting properties from objects, you can call | ||||
| `myObj.a` or `myObj["a"]` both work. | ||||
| To get the property of an object, you can call `myObj.a`, which in the above | ||||
| example returns 0. | ||||
|  | ||||
| ## `ImportedGeometry` | ||||
|  | ||||
|  | ||||
| @ -22,14 +22,14 @@ This will work on any solid, including extruded solids, revolved solids, and she | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid(s) whose appearance is being set | Yes | | ||||
| | `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid(s) whose appearance is being set | Yes | | ||||
| | `color` | [`string`](/docs/kcl-std/types/std-types-string) | Color of the new material, a hex string like '#ff0000' | Yes | | ||||
| | `metalness` | [`number`](/docs/kcl-std/types/std-types-number) | Metalness of the new material, a percentage like 95.7. | No | | ||||
| | `roughness` | [`number`](/docs/kcl-std/types/std-types-number) | Roughness of the new material, a percentage like 95.7. | No | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid or an imported geometry. | ||||
| [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid or an imported geometry. | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
							
								
								
									
										38
									
								
								docs/kcl-std/functions/std-math-legAngX.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,38 @@ | ||||
| --- | ||||
| title: "legAngX" | ||||
| subtitle: "Function in std::math" | ||||
| excerpt: "Compute the angle of the given leg for x." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Compute the angle of the given leg for x. | ||||
|  | ||||
| ```kcl | ||||
| legAngX( | ||||
|   hypotenuse: number(Length), | ||||
|   leg: number(Length), | ||||
| ): number(deg) | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Arguments | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes | | ||||
| | `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`number(deg)`](/docs/kcl-std/types/std-types-number) - A number. | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| ```kcl | ||||
| legAngX(hypotenuse = 5, leg = 3) | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										38
									
								
								docs/kcl-std/functions/std-math-legAngY.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,38 @@ | ||||
| --- | ||||
| title: "legAngY" | ||||
| subtitle: "Function in std::math" | ||||
| excerpt: "Compute the angle of the given leg for y." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Compute the angle of the given leg for y. | ||||
|  | ||||
| ```kcl | ||||
| legAngY( | ||||
|   hypotenuse: number(Length), | ||||
|   leg: number(Length), | ||||
| ): number(deg) | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Arguments | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes | | ||||
| | `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`number(deg)`](/docs/kcl-std/types/std-types-number) - A number. | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| ```kcl | ||||
| legAngY(hypotenuse = 5, leg = 3) | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										38
									
								
								docs/kcl-std/functions/std-math-legLen.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,38 @@ | ||||
| --- | ||||
| title: "legLen" | ||||
| subtitle: "Function in std::math" | ||||
| excerpt: "Compute the length of the given leg." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Compute the length of the given leg. | ||||
|  | ||||
| ```kcl | ||||
| legLen( | ||||
|   hypotenuse: number(Length), | ||||
|   leg: number(Length), | ||||
| ): number(deg) | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Arguments | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes | | ||||
| | `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`number(deg)`](/docs/kcl-std/types/std-types-number) - A number. | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| ```kcl | ||||
| legLen(hypotenuse = 5, leg = 3) | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -12,15 +12,15 @@ layout: manual | ||||
|   * [`appearance`](/docs/kcl-std/appearance) | ||||
|   * [`assert`](/docs/kcl-std/assert) | ||||
|   * [`assertIs`](/docs/kcl-std/assertIs) | ||||
|   * [`clone`](/docs/kcl-std/clone) | ||||
|   * [`clone`](/docs/kcl-std/functions/std-clone) | ||||
|   * [`helix`](/docs/kcl-std/functions/std-helix) | ||||
|   * [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane) | ||||
|   * [`patternLinear2d`](/docs/kcl-std/patternLinear2d) | ||||
| * [**std::array**](/docs/kcl-std/modules/std-array) | ||||
|   * [`map`](/docs/kcl-std/map) | ||||
|   * [`pop`](/docs/kcl-std/pop) | ||||
|   * [`push`](/docs/kcl-std/push) | ||||
|   * [`reduce`](/docs/kcl-std/reduce) | ||||
|   * [`map`](/docs/kcl-std/functions/std-array-map) | ||||
|   * [`pop`](/docs/kcl-std/functions/std-array-pop) | ||||
|   * [`push`](/docs/kcl-std/functions/std-array-push) | ||||
|   * [`reduce`](/docs/kcl-std/functions/std-array-reduce) | ||||
| * [**std::math**](/docs/kcl-std/modules/std-math) | ||||
|   * [`abs`](/docs/kcl-std/functions/std-math-abs) | ||||
|   * [`acos`](/docs/kcl-std/functions/std-math-acos) | ||||
| @ -30,9 +30,9 @@ layout: manual | ||||
|   * [`ceil`](/docs/kcl-std/functions/std-math-ceil) | ||||
|   * [`cos`](/docs/kcl-std/functions/std-math-cos) | ||||
|   * [`floor`](/docs/kcl-std/functions/std-math-floor) | ||||
|   * [`legAngX`](/docs/kcl-std/legAngX) | ||||
|   * [`legAngY`](/docs/kcl-std/legAngY) | ||||
|   * [`legLen`](/docs/kcl-std/legLen) | ||||
|   * [`legAngX`](/docs/kcl-std/functions/std-math-legAngX) | ||||
|   * [`legAngY`](/docs/kcl-std/functions/std-math-legAngY) | ||||
|   * [`legLen`](/docs/kcl-std/functions/std-math-legLen) | ||||
|   * [`ln`](/docs/kcl-std/functions/std-math-ln) | ||||
|   * [`log`](/docs/kcl-std/functions/std-math-log) | ||||
|   * [`log10`](/docs/kcl-std/functions/std-math-log10) | ||||
| @ -140,12 +140,13 @@ See also the [types overview](/docs/kcl-lang/types) | ||||
|  | ||||
| * [**Primitive types**](/docs/kcl-lang/types) | ||||
|   * [`End`](/docs/kcl-lang/types#End) | ||||
|   * [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | ||||
|   * [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | ||||
|   * [`Start`](/docs/kcl-lang/types#Start) | ||||
|   * [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | ||||
|   * [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) | ||||
|   * [`any`](/docs/kcl-std/types/std-types-any) | ||||
|   * [`bool`](/docs/kcl-std/types/std-types-bool) | ||||
|   * [`fn`](/docs/kcl-std/types/std-types-fn) | ||||
|   * [`number`](/docs/kcl-std/types/std-types-number) | ||||
|   * [`string`](/docs/kcl-std/types/std-types-string) | ||||
|   * [`tag`](/docs/kcl-std/types/std-types-tag) | ||||
|  | ||||
| @ -1,38 +0,0 @@ | ||||
| --- | ||||
| title: "legAngX" | ||||
| subtitle: "Function in std::math" | ||||
| excerpt: "Compute the angle of the given leg for x." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Compute the angle of the given leg for x. | ||||
|  | ||||
| ```kcl | ||||
| legAngX( | ||||
|   hypotenuse: number, | ||||
|   leg: number, | ||||
| ): number | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Arguments | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes | | ||||
| | `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`number`](/docs/kcl-std/types/std-types-number) - A number. | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| ```kcl | ||||
| legAngX(hypotenuse = 5, leg = 3) | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -1,38 +0,0 @@ | ||||
| --- | ||||
| title: "legAngY" | ||||
| subtitle: "Function in std::math" | ||||
| excerpt: "Compute the angle of the given leg for y." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Compute the angle of the given leg for y. | ||||
|  | ||||
| ```kcl | ||||
| legAngY( | ||||
|   hypotenuse: number, | ||||
|   leg: number, | ||||
| ): number | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Arguments | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes | | ||||
| | `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`number`](/docs/kcl-std/types/std-types-number) - A number. | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| ```kcl | ||||
| legAngY(hypotenuse = 5, leg = 3) | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -1,38 +0,0 @@ | ||||
| --- | ||||
| title: "legLen" | ||||
| subtitle: "Function in std::math" | ||||
| excerpt: "Compute the length of the given leg." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Compute the length of the given leg. | ||||
|  | ||||
| ```kcl | ||||
| legLen( | ||||
|   hypotenuse: number, | ||||
|   leg: number, | ||||
| ): number | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Arguments | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes | | ||||
| | `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`number`](/docs/kcl-std/types/std-types-number) - A number. | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| ```kcl | ||||
| legLen(hypotenuse = 5, leg = 3) | ||||
| ``` | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -12,8 +12,8 @@ Functions for manipulating arrays of values. | ||||
|  | ||||
| ## Functions and constants | ||||
|  | ||||
| * [`map`](/docs/kcl-std/map) | ||||
| * [`pop`](/docs/kcl-std/pop) | ||||
| * [`push`](/docs/kcl-std/push) | ||||
| * [`reduce`](/docs/kcl-std/reduce) | ||||
| * [`map`](/docs/kcl-std/functions/std-array-map) | ||||
| * [`pop`](/docs/kcl-std/functions/std-array-pop) | ||||
| * [`push`](/docs/kcl-std/functions/std-array-push) | ||||
| * [`reduce`](/docs/kcl-std/functions/std-array-reduce) | ||||
|  | ||||
|  | ||||
| @ -23,9 +23,9 @@ Functions for mathematical operations and some useful constants. | ||||
| * [`ceil`](/docs/kcl-std/functions/std-math-ceil) | ||||
| * [`cos`](/docs/kcl-std/functions/std-math-cos) | ||||
| * [`floor`](/docs/kcl-std/functions/std-math-floor) | ||||
| * [`legAngX`](/docs/kcl-std/legAngX) | ||||
| * [`legAngY`](/docs/kcl-std/legAngY) | ||||
| * [`legLen`](/docs/kcl-std/legLen) | ||||
| * [`legAngX`](/docs/kcl-std/functions/std-math-legAngX) | ||||
| * [`legAngY`](/docs/kcl-std/functions/std-math-legAngY) | ||||
| * [`legLen`](/docs/kcl-std/functions/std-math-legLen) | ||||
| * [`ln`](/docs/kcl-std/functions/std-math-ln) | ||||
| * [`log`](/docs/kcl-std/functions/std-math-log) | ||||
| * [`log10`](/docs/kcl-std/functions/std-math-log10) | ||||
|  | ||||
| @ -18,6 +18,7 @@ Types can (optionally) be used to describe a function's arguments and returned v | ||||
| * [`Edge`](/docs/kcl-std/types/std-types-Edge) | ||||
| * [`Face`](/docs/kcl-std/types/std-types-Face) | ||||
| * [`Helix`](/docs/kcl-std/types/std-types-Helix) | ||||
| * [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | ||||
| * [`Plane`](/docs/kcl-std/types/std-types-Plane) | ||||
| * [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | ||||
| * [`Point3d`](/docs/kcl-std/types/std-types-Point3d) | ||||
| @ -25,6 +26,7 @@ Types can (optionally) be used to describe a function's arguments and returned v | ||||
| * [`Solid`](/docs/kcl-std/types/std-types-Solid) | ||||
| * [`any`](/docs/kcl-std/types/std-types-any) | ||||
| * [`bool`](/docs/kcl-std/types/std-types-bool) | ||||
| * [`fn`](/docs/kcl-std/types/std-types-fn) | ||||
| * [`number`](/docs/kcl-std/types/std-types-number) | ||||
| * [`string`](/docs/kcl-std/types/std-types-string) | ||||
| * [`tag`](/docs/kcl-std/types/std-types-tag) | ||||
|  | ||||
| @ -37,7 +37,7 @@ You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL gui | ||||
| * [`appearance`](/docs/kcl-std/appearance) | ||||
| * [`assert`](/docs/kcl-std/assert) | ||||
| * [`assertIs`](/docs/kcl-std/assertIs) | ||||
| * [`clone`](/docs/kcl-std/clone) | ||||
| * [`clone`](/docs/kcl-std/functions/std-clone) | ||||
| * [`helix`](/docs/kcl-std/functions/std-helix) | ||||
| * [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane) | ||||
| * [`patternLinear2d`](/docs/kcl-std/patternLinear2d) | ||||
|  | ||||
| @ -43,7 +43,7 @@ When rotating a part around an axis, you specify the axis of rotation and the an | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid, sketch, or set of solids or sketches to rotate. | Yes | | ||||
| | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid, sketch, or set of solids or sketches to rotate. | Yes | | ||||
| | `roll` | [`number`](/docs/kcl-std/types/std-types-number) | The roll angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No | | ||||
| | `pitch` | [`number`](/docs/kcl-std/types/std-types-number) | The pitch angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No | | ||||
| | `yaw` | [`number`](/docs/kcl-std/types/std-types-number) | The yaw angle in degrees. Must be between -360 and 360. Default is 0 if not given. | No | | ||||
| @ -53,7 +53,7 @@ When rotating a part around an axis, you specify the axis of rotation and the an | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid, sketch, or an imported geometry. | ||||
| [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid, sketch, or an imported geometry. | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| @ -29,7 +29,7 @@ If you want to apply the transform in global space, set `global` to `true`. The | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid, sketch, or set of solids or sketches to scale. | Yes | | ||||
| | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid, sketch, or set of solids or sketches to scale. | Yes | | ||||
| | `x` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the x axis. Default is 1 if not provided. | No | | ||||
| | `y` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the y axis. Default is 1 if not provided. | No | | ||||
| | `z` | [`number`](/docs/kcl-std/types/std-types-number) | The scale factor for the z axis. Default is 1 if not provided. | No | | ||||
| @ -37,7 +37,7 @@ If you want to apply the transform in global space, set `global` to `true`. The | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid, sketch, or an imported geometry. | ||||
| [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid, sketch, or an imported geometry. | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
							
								
								
									
										34924
									
								
								docs/kcl-std/std.json
									
									
									
									
									
								
							
							
						
						| @ -25,7 +25,7 @@ Translate is really useful for sketches if you want to move a sketch and then ro | ||||
|  | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) | The solid, sketch, or set of solids or sketches to move. | Yes | | ||||
| | `objects` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) | The solid, sketch, or set of solids or sketches to move. | Yes | | ||||
| | `x` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the x axis. Defaults to 0 if not provided. | No | | ||||
| | `y` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the y axis. Defaults to 0 if not provided. | No | | ||||
| | `z` | [`number`](/docs/kcl-std/types/std-types-number) | The amount to move the solid or sketch along the z axis. Defaults to 0 if not provided. | No | | ||||
| @ -33,7 +33,7 @@ Translate is really useful for sketches if you want to move a sketch and then ro | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry) - Data for a solid, sketch, or an imported geometry. | ||||
| [`[Solid]`](/docs/kcl-std/types/std-types-Solid) or [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) or [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry) - Data for a solid, sketch, or an imported geometry. | ||||
|  | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
							
								
								
									
										13
									
								
								docs/kcl-std/types/std-types-ImportedGeometry.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,13 @@ | ||||
| --- | ||||
| title: "ImportedGeometry" | ||||
| subtitle: "Type in std::types" | ||||
| excerpt: "Represents geometry which is defined using some other CAD system and imported into KCL." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| Represents geometry which is defined using some other CAD system and imported into KCL. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										13
									
								
								docs/kcl-std/types/std-types-fn.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,13 @@ | ||||
| --- | ||||
| title: "fn" | ||||
| subtitle: "Type in std::types" | ||||
| excerpt: "The type of any function in KCL." | ||||
| layout: manual | ||||
| --- | ||||
|  | ||||
| The type of any function in KCL. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| @ -549,48 +549,6 @@ extrude002 = extrude(profile002, length = 150) | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   test( | ||||
|     `Network health indicator only appears in modeling view`, | ||||
|     { tag: '@electron' }, | ||||
|     async ({ context, page }, testInfo) => { | ||||
|       await context.folderSetupFn(async (dir) => { | ||||
|         const bracketDir = path.join(dir, 'bracket') | ||||
|         await fsp.mkdir(bracketDir, { recursive: true }) | ||||
|         await fsp.copyFile( | ||||
|           executorInputPath('cylinder-inches.kcl'), | ||||
|           path.join(bracketDir, 'main.kcl') | ||||
|         ) | ||||
|       }) | ||||
|  | ||||
|       await page.setBodyDimensions({ width: 1200, height: 500 }) | ||||
|       const u = await getUtils(page) | ||||
|  | ||||
|       // Locators | ||||
|       const projectsHeading = page.getByRole('heading', { | ||||
|         name: 'Projects', | ||||
|       }) | ||||
|       const projectLink = page.getByRole('link', { name: 'bracket' }) | ||||
|       const networkHealthIndicator = page.getByTestId('network-toggle') | ||||
|  | ||||
|       await test.step('Check the home page', async () => { | ||||
|         await expect(projectsHeading).toBeVisible() | ||||
|         await expect(projectLink).toBeVisible() | ||||
|         await expect(networkHealthIndicator).not.toBeVisible() | ||||
|       }) | ||||
|  | ||||
|       await test.step('Open the project', async () => { | ||||
|         await projectLink.click() | ||||
|       }) | ||||
|  | ||||
|       await test.step('Check the modeling view', async () => { | ||||
|         await expect(networkHealthIndicator).toBeVisible() | ||||
|         await expect(networkHealthIndicator).toContainText('Problem') | ||||
|         await u.waitForPageLoad() | ||||
|         await expect(networkHealthIndicator).toContainText('Connected') | ||||
|       }) | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test(`View gizmo stays visible even when zoomed out all the way`, async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|  | ||||
| Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB | 
| Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB | 
| Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB | 
| Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB | 
| Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB | 
| Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB | 
| Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB | 
| Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB | 
| Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 132 KiB | 
| Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB | 
| Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB | 
| Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB | 
| Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB | 
| Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB | 
| Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB | 
| Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 63 KiB | 
| @ -905,7 +905,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => { | ||||
|       await page.keyboard.press('Enter') | ||||
|  | ||||
|       // Go into the project that was created from Text to CAD | ||||
|       await page.getByText(projectName).click() | ||||
|       await homePage.openProject(projectName) | ||||
|  | ||||
|       await expect(page.getByTestId('app-header-project-name')).toBeVisible() | ||||
|       await expect(page.getByTestId('app-header-project-name')).toContainText( | ||||
| @ -951,7 +951,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => { | ||||
|       await page.keyboard.press('Enter') | ||||
|  | ||||
|       // Go into the project that was created from Text to CAD | ||||
|       await page.getByText(projectName).click() | ||||
|       await homePage.openProject(projectName) | ||||
|  | ||||
|       await page.getByRole('button', { name: 'Accept' }).click() | ||||
|  | ||||
|  | ||||
| @ -107,7 +107,6 @@ | ||||
|     "check": "biome check ./src ./e2e ./packages/codemirror-lsp-client/src ./rust/kcl-language-server/client/src", | ||||
|     "fetch:wasm": "./scripts/get-latest-wasm-bundle.sh", | ||||
|     "fetch:wasm:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/get-latest-wasm-bundle.ps1", | ||||
|     "fetch:samples": "rm -rf public/kcl-samples* && curl -L -o public/kcl-samples.zip https://github.com/KittyCAD/kcl-samples/archive/refs/heads/achalmers/kw-args-xylineto.zip && unzip -o public/kcl-samples.zip -d public && mv public/kcl-samples-* public/kcl-samples", | ||||
|     "remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\" || echo \"sed for both mac and linux\"", | ||||
|     "lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src", | ||||
|     "lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src", | ||||
| @ -123,7 +122,6 @@ | ||||
|     "files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly", | ||||
|     "postinstall": "electron-rebuild", | ||||
|     "generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts", | ||||
|     "generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js", | ||||
|     "tron:start": "electron-forge start", | ||||
|     "chrome:test": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert=@snapshot", | ||||
|     "tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development", | ||||
|  | ||||
							
								
								
									
										1
									
								
								rust/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						| @ -1959,6 +1959,7 @@ dependencies = [ | ||||
|  "url", | ||||
|  "uuid", | ||||
|  "validator", | ||||
|  "walkdir", | ||||
|  "wasm-bindgen", | ||||
|  "wasm-bindgen-futures", | ||||
|  "web-sys", | ||||
|  | ||||
| @ -85,6 +85,7 @@ tynm = "0.1.10" | ||||
| url = { version = "2.5.4", features = ["serde"] } | ||||
| uuid = { workspace = true, features = ["v4", "v5", "js", "serde"] } | ||||
| validator = { version = "0.20.0", features = ["derive"] } | ||||
| walkdir = "2.5.0" | ||||
| web-time = "1.1" | ||||
| winnow = "=0.6.24" | ||||
| zip = { workspace = true } | ||||
|  | ||||
| @ -16,7 +16,7 @@ use crate::{ | ||||
| }; | ||||
|  | ||||
| // Types with special handling. | ||||
| const SPECIAL_TYPES: [&str; 5] = ["TagDeclarator", "TagIdentifier", "Start", "End", "ImportedGeometry"]; | ||||
| const SPECIAL_TYPES: [&str; 4] = ["TagDeclarator", "TagIdentifier", "Start", "End"]; | ||||
|  | ||||
| const TYPE_REWRITES: [(&str, &str); 11] = [ | ||||
|     ("TagNode", "TagDeclarator"), | ||||
| @ -674,6 +674,8 @@ fn cleanup_type_string(input: &str, fmt_for_text: bool) -> String { | ||||
|  | ||||
|             if fmt_for_text && ty.starts_with("number") { | ||||
|                 format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-number)") | ||||
|             } else if fmt_for_text && ty.starts_with("fn") { | ||||
|                 format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-fn)") | ||||
|             } else if fmt_for_text && SPECIAL_TYPES.contains(&ty) { | ||||
|                 format!("[{prefix}{ty}{suffix}](/docs/kcl-lang/types#{ty})") | ||||
|             } else if fmt_for_text && DECLARED_TYPES.contains(&ty) { | ||||
|  | ||||
| @ -626,6 +626,8 @@ impl FnData { | ||||
|     pub(super) fn to_autocomplete_snippet(&self) -> String { | ||||
|         if self.name == "loft" { | ||||
|             return "loft([${0:sketch000}, ${1:sketch001}])".to_owned(); | ||||
|         } else if self.name == "clone" { | ||||
|             return "clone(${0:part001})".to_owned(); | ||||
|         } else if self.name == "hole" { | ||||
|             return "hole(${0:holeSketch}, ${1:%})".to_owned(); | ||||
|         } | ||||
|  | ||||
| @ -11,6 +11,7 @@ use std::{ | ||||
|  | ||||
| use anyhow::Result; | ||||
| use kcl_doc::ModData; | ||||
| use parse_display::Display; | ||||
| use schemars::JsonSchema; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use tower_lsp::lsp_types::{ | ||||
| @ -18,15 +19,27 @@ use tower_lsp::lsp_types::{ | ||||
|     MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation, | ||||
| }; | ||||
|  | ||||
| use crate::{ | ||||
|     execution::{types::NumericType, Sketch}, | ||||
|     std::Primitive, | ||||
| }; | ||||
| use crate::execution::{types::NumericType, Sketch}; | ||||
|  | ||||
| // These types are declared in (KCL) std. | ||||
| const DECLARED_TYPES: [&str; 15] = [ | ||||
|     "any", "number", "string", "tag", "bool", "Sketch", "Solid", "Plane", "Helix", "Face", "Edge", "Point2d", | ||||
|     "Point3d", "Axis2d", "Axis3d", | ||||
| const DECLARED_TYPES: [&str; 17] = [ | ||||
|     "any", | ||||
|     "number", | ||||
|     "string", | ||||
|     "tag", | ||||
|     "bool", | ||||
|     "Sketch", | ||||
|     "Solid", | ||||
|     "Plane", | ||||
|     "Helix", | ||||
|     "Face", | ||||
|     "Edge", | ||||
|     "Point2d", | ||||
|     "Point3d", | ||||
|     "Axis2d", | ||||
|     "Axis3d", | ||||
|     "ImportedGeometry", | ||||
|     "fn", | ||||
| ]; | ||||
|  | ||||
| lazy_static::lazy_static! { | ||||
| @ -38,6 +51,21 @@ lazy_static::lazy_static! { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| /// The primitive types that can be used in a KCL file. | ||||
| #[derive(Debug, Clone, PartialEq, Serialize, JsonSchema, Display)] | ||||
| #[serde(rename_all = "lowercase")] | ||||
| #[display(style = "lowercase")] | ||||
| enum Primitive { | ||||
|     /// A boolean value. | ||||
|     Bool, | ||||
|     /// A number value. | ||||
|     Number, | ||||
|     /// A string value. | ||||
|     String, | ||||
|     /// A uuid value. | ||||
|     Uuid, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)] | ||||
| #[ts(export)] | ||||
| #[serde(rename_all = "camelCase")] | ||||
| @ -534,8 +562,6 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync { | ||||
|     fn to_autocomplete_snippet(&self) -> Result<String> { | ||||
|         if self.name() == "loft" { | ||||
|             return Ok("loft([${0:sketch000}, ${1:sketch001}])".to_string()); | ||||
|         } else if self.name() == "clone" { | ||||
|             return Ok("clone(${0:part001})".to_string()); | ||||
|         } else if self.name() == "union" { | ||||
|             return Ok("union([${0:extrude001}, ${1:extrude002}])".to_string()); | ||||
|         } else if self.name() == "subtract" { | ||||
| @ -702,7 +728,7 @@ pub fn get_description_string_from_schema(schema: &schemars::schema::RootSchema) | ||||
|     None | ||||
| } | ||||
|  | ||||
| pub fn is_primitive(schema: &schemars::schema::Schema) -> Result<Option<Primitive>> { | ||||
| fn is_primitive(schema: &schemars::schema::Schema) -> Result<Option<Primitive>> { | ||||
|     match schema { | ||||
|         schemars::schema::Schema::Object(o) => { | ||||
|             if o.enum_values.is_some() { | ||||
| @ -1008,9 +1034,12 @@ mod tests { | ||||
|  | ||||
|     #[test] | ||||
|     fn get_autocomplete_snippet_map() { | ||||
|         let map_fn: Box<dyn StdLibFn> = Box::new(crate::std::array::Map); | ||||
|         let snippet = map_fn.to_autocomplete_snippet().unwrap(); | ||||
|         assert_eq!(snippet, r#"map(${0:[0..9]})"#); | ||||
|         let data = kcl_doc::walk_prelude(); | ||||
|         let DocData::Fn(map_fn) = data.find_by_name("map").unwrap() else { | ||||
|             panic!(); | ||||
|         }; | ||||
|         let snippet = map_fn.to_autocomplete_snippet(); | ||||
|         assert_eq!(snippet, r#"map()"#); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
| @ -1130,8 +1159,11 @@ mod tests { | ||||
|     #[test] | ||||
|     #[allow(clippy::literal_string_with_formatting_args)] | ||||
|     fn get_autocomplete_snippet_clone() { | ||||
|         let clone_fn: Box<dyn StdLibFn> = Box::new(crate::std::clone::Clone); | ||||
|         let snippet = clone_fn.to_autocomplete_snippet().unwrap(); | ||||
|         let data = kcl_doc::walk_prelude(); | ||||
|         let DocData::Fn(clone_fn) = data.find_by_name("clone").unwrap() else { | ||||
|             panic!(); | ||||
|         }; | ||||
|         let snippet = clone_fn.to_autocomplete_snippet(); | ||||
|         assert_eq!(snippet, r#"clone(${0:part001})"#); | ||||
|     } | ||||
|  | ||||
|  | ||||
| @ -182,6 +182,8 @@ impl RuntimeType { | ||||
|             } | ||||
|             AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?, | ||||
|             AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag), | ||||
|             AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry), | ||||
|             AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function), | ||||
|         }) | ||||
|     } | ||||
|  | ||||
| @ -363,6 +365,7 @@ pub enum PrimitiveType { | ||||
|     Axis2d, | ||||
|     Axis3d, | ||||
|     ImportedGeometry, | ||||
|     Function, | ||||
| } | ||||
|  | ||||
| impl PrimitiveType { | ||||
| @ -382,6 +385,7 @@ impl PrimitiveType { | ||||
|             PrimitiveType::Axis2d => "2d axes".to_owned(), | ||||
|             PrimitiveType::Axis3d => "3d axes".to_owned(), | ||||
|             PrimitiveType::ImportedGeometry => "imported geometries".to_owned(), | ||||
|             PrimitiveType::Function => "functions".to_owned(), | ||||
|             PrimitiveType::Tag => "tags".to_owned(), | ||||
|             PrimitiveType::TagId => "tag identifiers".to_owned(), | ||||
|         } | ||||
| @ -418,6 +422,7 @@ impl fmt::Display for PrimitiveType { | ||||
|             PrimitiveType::Axis3d => write!(f, "Axis3d"), | ||||
|             PrimitiveType::Helix => write!(f, "Helix"), | ||||
|             PrimitiveType::ImportedGeometry => write!(f, "imported geometry"), | ||||
|             PrimitiveType::Function => write!(f, "function"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -1184,6 +1189,10 @@ impl KclValue { | ||||
|                 KclValue::ImportedGeometry { .. } => Ok(value.clone()), | ||||
|                 _ => Err(self.into()), | ||||
|             }, | ||||
|             PrimitiveType::Function => match value { | ||||
|                 KclValue::Function { .. } => Ok(value.clone()), | ||||
|                 _ => Err(self.into()), | ||||
|             }, | ||||
|             PrimitiveType::TagId => match value { | ||||
|                 KclValue::TagIdentifier { .. } => Ok(value.clone()), | ||||
|                 _ => Err(self.into()), | ||||
| @ -1372,12 +1381,10 @@ impl KclValue { | ||||
|             KclValue::HomArray { ty, value, .. } => { | ||||
|                 Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len()))) | ||||
|             } | ||||
|             KclValue::TagIdentifier(_) | KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => { | ||||
|                 Some(RuntimeType::Primitive(PrimitiveType::Tag)) | ||||
|             } | ||||
|             KclValue::Function { .. } | KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => { | ||||
|                 None | ||||
|             } | ||||
|             KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TagId)), | ||||
|             KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Tag)), | ||||
|             KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)), | ||||
|             KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -2,8 +2,8 @@ use sha2::{Digest as DigestTrait, Sha256}; | ||||
|  | ||||
| use crate::parsing::ast::types::{ | ||||
|     Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryPart, BodyItem, | ||||
|     CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression, Identifier, IfExpression, | ||||
|     ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression, Literal, | ||||
|     CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression, FunctionType, Identifier, | ||||
|     IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression, Literal, | ||||
|     LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, ObjectExpression, ObjectProperty, Parameter, | ||||
|     PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, TagDeclarator, Type, TypeDeclaration, | ||||
|     UnaryExpression, VariableDeclaration, VariableDeclarator, VariableKind, | ||||
| @ -232,12 +232,29 @@ impl PrimitiveType { | ||||
|             PrimitiveType::Number(suffix) => hasher.update(suffix.digestable_id()), | ||||
|             PrimitiveType::Boolean => hasher.update(b"bool"), | ||||
|             PrimitiveType::Tag => hasher.update(b"tag"), | ||||
|             PrimitiveType::ImportedGeometry => hasher.update(b"ImportedGeometry"), | ||||
|             PrimitiveType::Function(f) => hasher.update(f.compute_digest()), | ||||
|         } | ||||
|  | ||||
|         hasher.finalize().into() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl FunctionType { | ||||
|     compute_digest!(|slf, hasher| { | ||||
|         if let Some(u) = &mut slf.unnamed_arg { | ||||
|             hasher.update(u.compute_digest()); | ||||
|         } | ||||
|         slf.named_args.iter_mut().for_each(|(a, t)| { | ||||
|             a.compute_digest(); | ||||
|             t.compute_digest(); | ||||
|         }); | ||||
|         if let Some(r) = &mut slf.return_type { | ||||
|             hasher.update(r.compute_digest()); | ||||
|         } | ||||
|     }); | ||||
| } | ||||
|  | ||||
| impl Parameter { | ||||
|     compute_digest!(|slf, hasher| { | ||||
|         hasher.update(slf.identifier.compute_digest()); | ||||
|  | ||||
| @ -3197,6 +3197,10 @@ pub enum PrimitiveType { | ||||
|     Boolean, | ||||
|     /// A tag. | ||||
|     Tag, | ||||
|     /// Imported from other CAD system. | ||||
|     ImportedGeometry, | ||||
|     /// `fn`, type of functions. | ||||
|     Function(FunctionType), | ||||
|     /// An identifier used as a type (not really a primitive type, but whatever). | ||||
|     Named(Node<Identifier>), | ||||
| } | ||||
| @ -3210,6 +3214,7 @@ impl PrimitiveType { | ||||
|             ("tag", None) => Some(PrimitiveType::Tag), | ||||
|             ("number", None) => Some(PrimitiveType::Number(NumericSuffix::None)), | ||||
|             ("number", Some(s)) => Some(PrimitiveType::Number(s)), | ||||
|             ("ImportedGeometry", None) => Some(PrimitiveType::ImportedGeometry), | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| @ -3229,11 +3234,58 @@ impl fmt::Display for PrimitiveType { | ||||
|             PrimitiveType::String => write!(f, "string"), | ||||
|             PrimitiveType::Boolean => write!(f, "bool"), | ||||
|             PrimitiveType::Tag => write!(f, "tag"), | ||||
|             PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"), | ||||
|             PrimitiveType::Function(t) => { | ||||
|                 write!(f, "fn")?; | ||||
|                 if t.unnamed_arg.is_some() || !t.named_args.is_empty() || t.return_type.is_some() { | ||||
|                     write!(f, "(")?; | ||||
|                     if let Some(u) = &t.unnamed_arg { | ||||
|                         write!(f, "{u}")?; | ||||
|                         if !t.named_args.is_empty() { | ||||
|                             write!(f, ", ")?; | ||||
|                         } | ||||
|                     } | ||||
|                     for (i, (a, t)) in t.named_args.iter().enumerate() { | ||||
|                         if i != 0 { | ||||
|                             write!(f, ", ")?; | ||||
|                         } | ||||
|                         write!(f, "{}: {t}", a.name)?; | ||||
|                     } | ||||
|                     write!(f, ")")?; | ||||
|                     if let Some(r) = &t.return_type { | ||||
|                         write!(f, ": {r}")?; | ||||
|                     } | ||||
|                 } | ||||
|                 Ok(()) | ||||
|             } | ||||
|             PrimitiveType::Named(n) => write!(f, "{}", n.name), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| #[ts(export)] | ||||
| pub struct FunctionType { | ||||
|     pub unnamed_arg: Option<BoxNode<Type>>, | ||||
|     pub named_args: Vec<(Node<Identifier>, Node<Type>)>, | ||||
|     pub return_type: Option<BoxNode<Type>>, | ||||
|  | ||||
|     #[serde(default, skip_serializing_if = "Option::is_none")] | ||||
|     #[ts(optional)] | ||||
|     pub digest: Option<Digest>, | ||||
| } | ||||
|  | ||||
| impl FunctionType { | ||||
|     pub fn empty_fn_type() -> Self { | ||||
|         FunctionType { | ||||
|             unnamed_arg: None, | ||||
|             named_args: Vec::new(), | ||||
|             return_type: None, | ||||
|             digest: None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| #[ts(export)] | ||||
| #[serde(tag = "type")] | ||||
| @ -3285,7 +3337,7 @@ impl fmt::Display for Type { | ||||
|                     } else { | ||||
|                         write!(f, ",")?; | ||||
|                     } | ||||
|                     write!(f, "{}: ", p.identifier.name)?; | ||||
|                     write!(f, " {}:", p.identifier.name)?; | ||||
|                     if let Some(ty) = &p.type_ { | ||||
|                         write!(f, " {}", ty.inner)?; | ||||
|                     } | ||||
|  | ||||
| @ -25,11 +25,11 @@ use crate::{ | ||||
|         ast::types::{ | ||||
|             Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, | ||||
|             BoxNode, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr, ExpressionStatement, | ||||
|             FunctionExpression, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility, | ||||
|             LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeList, | ||||
|             NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, | ||||
|             PipeSubstitution, PrimitiveType, Program, ReturnStatement, Shebang, TagDeclarator, Type, TypeDeclaration, | ||||
|             UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind, | ||||
|             FunctionExpression, FunctionType, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement, | ||||
|             ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, | ||||
|             Node, NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, | ||||
|             PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, Shebang, TagDeclarator, Type, | ||||
|             TypeDeclaration, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind, | ||||
|         }, | ||||
|         math::BinaryExpressionToken, | ||||
|         token::{Token, TokenSlice, TokenType}, | ||||
| @ -1261,7 +1261,7 @@ fn function_decl(i: &mut TokenSlice) -> PResult<Node<FunctionExpression>> { | ||||
|     fn return_type(i: &mut TokenSlice) -> PResult<Node<Type>> { | ||||
|         colon(i)?; | ||||
|         ignore_whitespace(i); | ||||
|         argument_type(i) | ||||
|         type_(i) | ||||
|     } | ||||
|  | ||||
|     let open = open_paren(i)?; | ||||
| @ -2013,7 +2013,7 @@ fn expression_but_not_pipe(i: &mut TokenSlice) -> PResult<Expr> { | ||||
|     .context(expected("a KCL value")) | ||||
|     .parse_next(i)?; | ||||
|  | ||||
|     let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?; | ||||
|     let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?; | ||||
|     if let Some((_, _, ty)) = ty { | ||||
|         expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty))) | ||||
|     } | ||||
| @ -2083,7 +2083,7 @@ fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> { | ||||
|     )) | ||||
|     .parse_next(i)?; | ||||
|  | ||||
|     let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?; | ||||
|     let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?; | ||||
|     if let Some((_, _, ty)) = ty { | ||||
|         expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty))) | ||||
|     } | ||||
| @ -2233,7 +2233,21 @@ fn ty_decl(i: &mut TokenSlice) -> PResult<BoxNode<TypeDeclaration>> { | ||||
|     let start = visibility_token.map(|t| t.start).unwrap_or_else(|| decl_token.start); | ||||
|     whitespace(i)?; | ||||
|  | ||||
|     let name = identifier(i)?; | ||||
|     let name = alt(( | ||||
|         fun.map(|t| { | ||||
|             Node::new( | ||||
|                 Identifier { | ||||
|                     name: "fn".to_owned(), | ||||
|                     digest: None, | ||||
|                 }, | ||||
|                 t.start, | ||||
|                 t.end, | ||||
|                 t.module_id, | ||||
|             ) | ||||
|         }), | ||||
|         identifier, | ||||
|     )) | ||||
|     .parse_next(i)?; | ||||
|     let mut end = name.end; | ||||
|  | ||||
|     let args = if peek((opt(whitespace), open_paren)).parse_next(i).is_ok() { | ||||
| @ -2253,7 +2267,7 @@ fn ty_decl(i: &mut TokenSlice) -> PResult<BoxNode<TypeDeclaration>> { | ||||
|         ignore_whitespace(i); | ||||
|         equals(i)?; | ||||
|         ignore_whitespace(i); | ||||
|         let ty = argument_type(i)?; | ||||
|         let ty = type_(i)?; | ||||
|  | ||||
|         ParseContext::warn(CompilationError::err( | ||||
|             ty.as_source_range(), | ||||
| @ -2755,12 +2769,8 @@ fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> { | ||||
|         .parse_next(i) | ||||
| } | ||||
|  | ||||
| /// A type of a function argument. | ||||
| /// This can be: | ||||
| /// - a primitive type, e.g. `number` or `string` or `bool` | ||||
| /// - an array type, e.g. `[number]` or `[string]` or `[bool]` | ||||
| /// - an object type, e.g. `{x: number, y: number}` or `{name: string, age: number}` | ||||
| fn argument_type(i: &mut TokenSlice) -> PResult<Node<Type>> { | ||||
| /// Parse a type in various positions. | ||||
| fn type_(i: &mut TokenSlice) -> PResult<Node<Type>> { | ||||
|     let type_ = alt(( | ||||
|         // Object types | ||||
|         // TODO it is buggy to treat object fields like parameters since the parameters parser assumes a terminating `)`. | ||||
| @ -2800,14 +2810,69 @@ fn argument_type(i: &mut TokenSlice) -> PResult<Node<Type>> { | ||||
| } | ||||
|  | ||||
| fn primitive_type(i: &mut TokenSlice) -> PResult<Node<PrimitiveType>> { | ||||
|     let ident = identifier(i)?; | ||||
|     alt(( | ||||
|         // A function type: `fn` (`(` type?, (id: type,)* `)` (`:` type)?)? | ||||
|         ( | ||||
|             fun, | ||||
|             opt(( | ||||
|                 // `(` type?, (id: type,)* `)` | ||||
|                 delimited( | ||||
|                     open_paren, | ||||
|                     opt(alt(( | ||||
|                         // type, (id: type,)+ | ||||
|                         ( | ||||
|                             type_, | ||||
|                             comma, | ||||
|                             opt(whitespace), | ||||
|                             separated( | ||||
|                                 1.., | ||||
|                                 (identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)), | ||||
|                                 comma_sep, | ||||
|                             ), | ||||
|                         ) | ||||
|                             .map(|(t, _, _, args)| (Some(t), args)), | ||||
|                         // (id: type,)+ | ||||
|                         separated( | ||||
|                             1.., | ||||
|                             (identifier, colon, opt(whitespace), type_).map(|(id, _, _, ty)| (id, ty)), | ||||
|                             comma_sep, | ||||
|                         ) | ||||
|                         .map(|args| (None, args)), | ||||
|                         // type | ||||
|                         type_.map(|t| (Some(t), Vec::new())), | ||||
|                     ))), | ||||
|                     close_paren, | ||||
|                 ), | ||||
|                 // `:` type | ||||
|                 opt((colon, opt(whitespace), type_)), | ||||
|             )), | ||||
|         ) | ||||
|             .map(|(t, tys)| { | ||||
|                 let mut ft = FunctionType::empty_fn_type(); | ||||
|  | ||||
|     let suffix = opt(delimited(open_paren, uom_for_type, close_paren)).parse_next(i)?; | ||||
|                 if let Some((args, ret)) = tys { | ||||
|                     if let Some((unnamed, named)) = args { | ||||
|                         if let Some(unnamed) = unnamed { | ||||
|                             ft.unnamed_arg = Some(Box::new(unnamed)); | ||||
|                         } | ||||
|                         ft.named_args = named; | ||||
|                     } | ||||
|                     if let Some((_, _, ty)) = ret { | ||||
|                         ft.return_type = Some(Box::new(ty)); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|     let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id); | ||||
|     result.inner = PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident)); | ||||
|  | ||||
|     Ok(result) | ||||
|                 Node::new(PrimitiveType::Function(ft), t.start, t.end, t.module_id) | ||||
|             }), | ||||
|         // A named type, possibly with a numeric suffix. | ||||
|         (identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| { | ||||
|             let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id); | ||||
|             result.inner = | ||||
|                 PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident)); | ||||
|             result | ||||
|         }), | ||||
|     )) | ||||
|     .parse_next(i) | ||||
| } | ||||
|  | ||||
| fn array_type(i: &mut TokenSlice) -> PResult<Node<Type>> { | ||||
| @ -2817,7 +2882,7 @@ fn array_type(i: &mut TokenSlice) -> PResult<Node<Type>> { | ||||
|     } | ||||
|  | ||||
|     open_bracket(i)?; | ||||
|     let ty = argument_type(i)?; | ||||
|     let ty = type_(i)?; | ||||
|     let len = opt(( | ||||
|         semi_colon, | ||||
|         opt_whitespace, | ||||
| @ -2905,7 +2970,7 @@ fn parameter(i: &mut TokenSlice) -> PResult<ParamDescription> { | ||||
|         any.verify(|token: &Token| !matches!(token.token_type, TokenType::Brace) || token.value != ")"), | ||||
|         opt(question_mark), | ||||
|         opt(whitespace), | ||||
|         opt((colon, opt(whitespace), argument_type).map(|tup| tup.2)), | ||||
|         opt((colon, opt(whitespace), type_).map(|tup| tup.2)), | ||||
|         opt(whitespace), | ||||
|         opt((equals, opt(whitespace), literal).map(|(_, _, literal)| literal)), | ||||
|     ) | ||||
| @ -4777,6 +4842,20 @@ let myBox = box(p=[0,0], h=-3, l=-16, w=-10) | ||||
|         assert_no_err(some_program_string); | ||||
|     } | ||||
|     #[test] | ||||
|     fn parse_function_types() { | ||||
|         let code = r#"foo = x: fn | ||||
| foo = x: fn(number) | ||||
| fn foo(x: fn(): number): fn { return 0 } | ||||
| fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn { return 0 } | ||||
| type fn | ||||
| type foo = fn | ||||
| type foo = fn(a: string, b: { f: fn(): any }) | ||||
| type foo = fn([fn]) | ||||
| type foo = fn(fn, f: fn(number(_))): [fn([any]): string] | ||||
|     "#; | ||||
|         assert_no_err(code); | ||||
|     } | ||||
|     #[test] | ||||
|     fn test_parse_tag_starting_with_bang() { | ||||
|         let some_program_string = r#"startSketchOn(XY) | ||||
|     |> startProfile(at = [0, 0]) | ||||
|  | ||||
| @ -34,14 +34,12 @@ lazy_static! { | ||||
|         set.insert("true", TokenType::Keyword); | ||||
|         set.insert("false", TokenType::Keyword); | ||||
|         set.insert("nil", TokenType::Keyword); | ||||
|         // This isn't a type because brackets are used for the type. | ||||
|         set.insert("array", TokenType::Keyword); | ||||
|         set.insert("and", TokenType::Keyword); | ||||
|         set.insert("or", TokenType::Keyword); | ||||
|         set.insert("not", TokenType::Keyword); | ||||
|         set.insert("var", TokenType::Keyword); | ||||
|         set.insert("const", TokenType::Keyword); | ||||
|         // "import" is special because of import(). | ||||
|         set.insert("import", TokenType::Keyword); | ||||
|         set.insert("export", TokenType::Keyword); | ||||
|         set.insert("type", TokenType::Keyword); | ||||
|         set.insert("interface", TokenType::Keyword); | ||||
|  | ||||
| @ -8,6 +8,7 @@ use std::{ | ||||
| use anyhow::Result; | ||||
| use fnv::FnvHashSet; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use walkdir::WalkDir; | ||||
|  | ||||
| use super::Test; | ||||
|  | ||||
| @ -250,19 +251,12 @@ fn get_kcl_metadata(project_path: &Path, files: &[String]) -> Option<KclMetadata | ||||
|     let title = lines[0].trim_start_matches(COMMENT_PREFIX).trim().to_string(); | ||||
|     let description = lines[1].trim_start_matches(COMMENT_PREFIX).trim().to_string(); | ||||
|  | ||||
|     // Get the path components | ||||
|     let path_components: Vec<String> = full_path_to_primary_kcl | ||||
|         .components() | ||||
|         .map(|comp| comp.as_os_str().to_string_lossy().to_string()) | ||||
|         .collect(); | ||||
|  | ||||
|     // Get the last two path components | ||||
|     let len = path_components.len(); | ||||
|     let path_from_project_dir = if len >= 2 { | ||||
|         format!("{}/{}", path_components[len - 2], path_components[len - 1]) | ||||
|     } else { | ||||
|         primary_kcl_file.clone() | ||||
|     }; | ||||
|     // Get the relative path from the project directory to the primary KCL file | ||||
|     let path_from_project_dir = full_path_to_primary_kcl | ||||
|         .strip_prefix(INPUTS_DIR.as_path()) | ||||
|         .unwrap_or(&full_path_to_primary_kcl) | ||||
|         .to_string_lossy() | ||||
|         .to_string(); | ||||
|  | ||||
|     let mut files = files.to_vec(); | ||||
|     files.sort(); | ||||
| @ -281,21 +275,23 @@ fn get_kcl_metadata(project_path: &Path, files: &[String]) -> Option<KclMetadata | ||||
| fn generate_kcl_manifest(dir: &Path) -> Result<()> { | ||||
|     let mut manifest = Vec::new(); | ||||
|  | ||||
|     // Collect all directory entries first and sort them by name for consistent ordering | ||||
|     let mut entries: Vec<_> = fs::read_dir(dir)? | ||||
|         .filter_map(Result::ok) | ||||
|         .filter(|e| e.path().is_dir()) | ||||
|     // Collect all directory entries first | ||||
|     let mut entries: Vec<_> = WalkDir::new(dir) | ||||
|         .follow_links(true) | ||||
|         .into_iter() | ||||
|         .filter_map(|e| e.ok()) | ||||
|         .collect(); | ||||
|  | ||||
|     // Sort directories by name for consistent ordering | ||||
|     entries.sort_by_key(|a| a.file_name()); | ||||
|     entries.sort_by_key(|a| a.file_name().to_string_lossy().to_string()); | ||||
|  | ||||
|     // Loop through all directories and add to manifest if KCL sample | ||||
|     for entry in entries { | ||||
|         let project_path = entry.path(); | ||||
|         let path = entry.path(); | ||||
|  | ||||
|         if project_path.is_dir() { | ||||
|         if path.is_dir() { | ||||
|             // Get all .kcl files in the directory | ||||
|             let files: Vec<String> = fs::read_dir(&project_path)? | ||||
|             let files: Vec<String> = fs::read_dir(path)? | ||||
|                 .filter_map(Result::ok) | ||||
|                 .filter(|e| { | ||||
|                     if let Some(ext) = e.path().extension() { | ||||
| @ -311,7 +307,7 @@ fn generate_kcl_manifest(dir: &Path) -> Result<()> { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             if let Some(metadata) = get_kcl_metadata(&project_path, &files) { | ||||
|             if let Some(metadata) = get_kcl_metadata(path, &files) { | ||||
|                 manifest.push(metadata); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| use indexmap::IndexMap; | ||||
| use kcl_derive_docs::stdlib; | ||||
|  | ||||
| use super::{ | ||||
|     args::{Arg, KwArgs}, | ||||
| @ -27,46 +26,6 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl | ||||
|     }) | ||||
| } | ||||
|  | ||||
| /// Apply a function to every element of a list. | ||||
| /// | ||||
| /// Given a list like `[a, b, c]`, and a function like `f`, returns | ||||
| /// `[f(a), f(b), f(c)]` | ||||
| /// ```no_run | ||||
| /// r = 10 // radius | ||||
| /// fn drawCircle(@id) { | ||||
| ///   return startSketchOn(XY) | ||||
| ///     |> circle( center= [id * 2 * r, 0], radius= r) | ||||
| /// } | ||||
| /// | ||||
| /// // Call `drawCircle`, passing in each element of the array. | ||||
| /// // The outputs from each `drawCircle` form a new array, | ||||
| /// // which is the return value from `map`. | ||||
| /// circles = map( | ||||
| ///   [1..3], | ||||
| ///   f = drawCircle | ||||
| /// ) | ||||
| /// ``` | ||||
| /// ```no_run | ||||
| /// r = 10 // radius | ||||
| /// // Call `map`, using an anonymous function instead of a named one. | ||||
| /// circles = map( | ||||
| ///   [1..3], | ||||
| ///   f = fn(@id) { | ||||
| ///     return startSketchOn(XY) | ||||
| ///       |> circle( center= [id * 2 * r, 0], radius= r) | ||||
| ///   } | ||||
| /// ) | ||||
| /// ``` | ||||
| #[stdlib { | ||||
|     name = "map", | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         array = { docs = "Input array. The output array is this input array, but every element has had the function `f` run on it." }, | ||||
|         f = { docs = "A function. The output array is just the input array, but `f` has been run on every item." }, | ||||
|     }, | ||||
|     tags = ["array"] | ||||
| }] | ||||
| async fn inner_map<'a>( | ||||
|     array: Vec<KclValue>, | ||||
|     f: &'a FunctionSource, | ||||
| @ -118,96 +77,6 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, | ||||
|     inner_reduce(array, initial, f, exec_state, &args).await | ||||
| } | ||||
|  | ||||
| /// Take a starting value. Then, for each element of an array, calculate the next value, | ||||
| /// using the previous value and the element. | ||||
| /// ```no_run | ||||
| /// // This function adds two numbers. | ||||
| /// fn add(@a, accum) { return a + accum } | ||||
| /// | ||||
| /// // This function adds an array of numbers. | ||||
| /// // It uses the `reduce` function, to call the `add` function on every | ||||
| /// // element of the `arr` parameter. The starting value is 0. | ||||
| /// fn sum(@arr) { return reduce(arr, initial = 0, f = add) } | ||||
| /// | ||||
| /// /* | ||||
| /// The above is basically like this pseudo-code: | ||||
| /// fn sum(arr): | ||||
| ///     sumSoFar = 0 | ||||
| ///     for i in arr: | ||||
| ///         sumSoFar = add(i, sumSoFar) | ||||
| ///     return sumSoFar | ||||
| /// */ | ||||
| /// | ||||
| /// // We use `assert` to check that our `sum` function gives the | ||||
| /// // expected result. It's good to check your work! | ||||
| /// assert(sum([1, 2, 3]), isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6") | ||||
| /// ``` | ||||
| /// ```no_run | ||||
| /// // This example works just like the previous example above, but it uses | ||||
| /// // an anonymous `add` function as its parameter, instead of declaring a | ||||
| /// // named function outside. | ||||
| /// arr = [1, 2, 3] | ||||
| /// sum = reduce(arr, initial = 0, f = fn (@i, accum) { return i + accum }) | ||||
| /// | ||||
| /// // We use `assert` to check that our `sum` function gives the | ||||
| /// // expected result. It's good to check your work! | ||||
| /// assert(sum, isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6") | ||||
| /// ``` | ||||
| /// ```no_run | ||||
| /// // Declare a function that sketches a decagon. | ||||
| /// fn decagon(@radius) { | ||||
| ///   // Each side of the decagon is turned this many radians from the previous angle. | ||||
| ///   stepAngle = ((1/10) * TAU): number(rad) | ||||
| /// | ||||
| ///   // Start the decagon sketch at this point. | ||||
| ///   startOfDecagonSketch = startSketchOn(XY) | ||||
| ///     |> startProfile(at = [(cos(0)*radius), (sin(0) * radius)]) | ||||
| /// | ||||
| ///   // Use a `reduce` to draw the remaining decagon sides. | ||||
| ///   // For each number in the array 1..10, run the given function, | ||||
| ///   // which takes a partially-sketched decagon and adds one more edge to it. | ||||
| ///   fullDecagon = reduce([1..10], initial = startOfDecagonSketch, f = fn(@i, accum) { | ||||
| ///       // Draw one edge of the decagon. | ||||
| ///       x = cos(stepAngle * i) * radius | ||||
| ///       y = sin(stepAngle * i) * radius | ||||
| ///       return line(accum, end = [x, y]) | ||||
| ///   }) | ||||
| /// | ||||
| ///   return fullDecagon | ||||
| /// | ||||
| /// } | ||||
| /// | ||||
| /// /* | ||||
| /// The `decagon` above is basically like this pseudo-code: | ||||
| /// fn decagon(radius): | ||||
| ///     stepAngle = ((1/10) * TAU): number(rad) | ||||
| ///     plane = startSketchOn(XY) | ||||
| ///     startOfDecagonSketch = startProfile(plane, at = [(cos(0)*radius), (sin(0) * radius)]) | ||||
| /// | ||||
| ///     // Here's the reduce part. | ||||
| ///     partialDecagon = startOfDecagonSketch | ||||
| ///     for i in [1..10]: | ||||
| ///         x = cos(stepAngle * i) * radius | ||||
| ///         y = sin(stepAngle * i) * radius | ||||
| ///         partialDecagon = line(partialDecagon, end = [x, y]) | ||||
| ///     fullDecagon = partialDecagon // it's now full | ||||
| ///     return fullDecagon | ||||
| /// */ | ||||
| /// | ||||
| /// // Use the `decagon` function declared above, to sketch a decagon with radius 5. | ||||
| /// decagon(5.0) |> close() | ||||
| /// ``` | ||||
| #[stdlib { | ||||
|     name = "reduce", | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         array = { docs = "Each element of this array gets run through the function `f`, combined with the previous output from `f`, and then used for the next run." }, | ||||
|         initial = { docs = "The first time `f` is run, it will be called with the first item of `array` and this initial starting value."}, | ||||
|         f = { docs = "Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`." }, | ||||
|     }, | ||||
|     tags = ["array"] | ||||
| }] | ||||
| async fn inner_reduce<'a>( | ||||
|     array: Vec<KclValue>, | ||||
|     initial: KclValue, | ||||
| @ -285,28 +154,8 @@ pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K | ||||
|     Ok(KclValue::HomArray { value: new_array, ty }) | ||||
| } | ||||
|  | ||||
| /// Append an element to the end of an array. | ||||
| /// | ||||
| /// Returns a new array with the element appended. | ||||
| /// | ||||
| /// ```no_run | ||||
| /// arr = [1, 2, 3] | ||||
| /// new_arr = push(arr, item = 4) | ||||
| /// assert(new_arr[3], isEqualTo = 4, tolerance = 0.1, error = "4 was added to the end of the array") | ||||
| /// ``` | ||||
| #[stdlib { | ||||
|     name = "push", | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         array = { docs = "The array which you're adding a new item to." }, | ||||
|         item = { docs = "The new item to add to the array" }, | ||||
|     }, | ||||
|     tags = ["array"] | ||||
| }] | ||||
| fn inner_push(mut array: Vec<KclValue>, item: KclValue) -> Vec<KclValue> { | ||||
|     array.push(item); | ||||
|  | ||||
|     array | ||||
| } | ||||
|  | ||||
| @ -322,30 +171,9 @@ pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc | ||||
|     }; | ||||
|  | ||||
|     let new_array = inner_pop(values, &args)?; | ||||
|  | ||||
|     Ok(KclValue::HomArray { value: new_array, ty }) | ||||
| } | ||||
|  | ||||
| /// Remove the last element from an array. | ||||
| /// | ||||
| /// Returns a new array with the last element removed. | ||||
| /// | ||||
| /// ```no_run | ||||
| /// arr = [1, 2, 3, 4] | ||||
| /// new_arr = pop(arr) | ||||
| /// assert(new_arr[0], isEqualTo = 1, tolerance = 0.00001, error = "1 is the first element of the array") | ||||
| /// assert(new_arr[1], isEqualTo = 2, tolerance = 0.00001, error = "2 is the second element of the array") | ||||
| /// assert(new_arr[2], isEqualTo = 3, tolerance = 0.00001, error = "3 is the third element of the array") | ||||
| /// ``` | ||||
| #[stdlib { | ||||
|     name = "pop", | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         array = { docs = "The array to pop from. Must not be empty."}, | ||||
|     }, | ||||
|     tags = ["array"] | ||||
| }] | ||||
| fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<Vec<KclValue>, KclError> { | ||||
|     if array.is_empty() { | ||||
|         return Err(KclError::Semantic(KclErrorDetails { | ||||
|  | ||||
| @ -3,7 +3,6 @@ | ||||
| use std::collections::HashMap; | ||||
|  | ||||
| use anyhow::Result; | ||||
| use kcl_derive_docs::stdlib; | ||||
| use kcmc::{ | ||||
|     each_cmd as mcmd, | ||||
|     ok_response::{output::EntityGetAllChildUuids, OkModelingCmdResponse}, | ||||
| @ -41,240 +40,6 @@ pub async fn clone(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K | ||||
|     Ok(cloned.into()) | ||||
| } | ||||
|  | ||||
| /// Clone a sketch or solid. | ||||
| /// | ||||
| /// This works essentially like a copy-paste operation. It creates a perfect replica | ||||
| /// at that point in time that you can manipulate individually afterwards. | ||||
| /// | ||||
| /// This doesn't really have much utility unless you need the equivalent of a double | ||||
| /// instance pattern with zero transformations. | ||||
| /// | ||||
| /// Really only use this function if YOU ARE SURE you need it. In most cases you | ||||
| /// do not need clone and using a pattern with `instance = 2` is more appropriate. | ||||
| /// | ||||
| /// ```no_run | ||||
| /// // Clone a basic sketch and move it and extrude it. | ||||
| /// exampleSketch = startSketchOn(XY) | ||||
| ///   |> startProfile(at = [0, 0]) | ||||
| ///   |> line(end = [10, 0]) | ||||
| ///   |> line(end = [0, 10]) | ||||
| ///   |> line(end = [-10, 0]) | ||||
| ///   |> close() | ||||
| /// | ||||
| /// clonedSketch = clone(exampleSketch) | ||||
| ///     |> scale( | ||||
| ///     x = 1.0, | ||||
| ///     y = 1.0, | ||||
| ///     z = 2.5, | ||||
| ///     ) | ||||
| ///     |> translate( | ||||
| ///         x = 15.0, | ||||
| ///         y = 0, | ||||
| ///         z = 0, | ||||
| ///     ) | ||||
| ///     |> extrude(length = 5) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```no_run | ||||
| /// // Clone a basic solid and move it. | ||||
| /// | ||||
| /// exampleSketch = startSketchOn(XY) | ||||
| ///   |> startProfile(at = [0, 0]) | ||||
| ///   |> line(end = [10, 0]) | ||||
| ///   |> line(end = [0, 10]) | ||||
| ///   |> line(end = [-10, 0]) | ||||
| ///   |> close() | ||||
| /// | ||||
| /// myPart = extrude(exampleSketch, length = 5) | ||||
| /// clonedPart = clone(myPart) | ||||
| ///     |> translate( | ||||
| ///         x = 25.0, | ||||
| ///     ) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```no_run | ||||
| /// // Translate and rotate a cloned sketch to create a loft. | ||||
| /// | ||||
| /// sketch001 = startSketchOn(XY) | ||||
| ///         |> startProfile(at = [-10, 10]) | ||||
| ///         |> xLine(length = 20) | ||||
| ///         |> yLine(length = -20) | ||||
| ///         |> xLine(length = -20) | ||||
| ///         |> close() | ||||
| /// | ||||
| /// sketch002 = clone(sketch001) | ||||
| ///     |> translate(x = 0, y = 0, z = 20) | ||||
| ///     |> rotate(axis = [0, 0, 1.0], angle = 45) | ||||
| /// | ||||
| /// loft([sketch001, sketch002]) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```no_run | ||||
| /// // Translate a cloned solid. Fillet only the clone. | ||||
| /// | ||||
| /// sketch001 = startSketchOn(XY) | ||||
| ///         |> startProfile(at = [-10, 10]) | ||||
| ///         |> xLine(length = 20) | ||||
| ///         |> yLine(length = -20) | ||||
| ///         |> xLine(length = -20, tag = $filletTag) | ||||
| ///         |> close() | ||||
| ///         |> extrude(length = 5) | ||||
| /// | ||||
| /// | ||||
| /// sketch002 = clone(sketch001) | ||||
| ///     |> translate(x = 0, y = 0, z = 20) | ||||
| ///     |> fillet( | ||||
| ///     radius = 2, | ||||
| ///     tags = [getNextAdjacentEdge(filletTag)], | ||||
| ///     ) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```no_run | ||||
| /// // You can reuse the tags from the original geometry with the cloned geometry. | ||||
| /// | ||||
| /// sketch001 = startSketchOn(XY) | ||||
| ///   |> startProfile(at = [0, 0]) | ||||
| ///   |> line(end = [10, 0]) | ||||
| ///   |> line(end = [0, 10], tag = $sketchingFace) | ||||
| ///   |> line(end = [-10, 0]) | ||||
| ///   |> close() | ||||
| /// | ||||
| /// sketch002 = clone(sketch001) | ||||
| ///     |> translate(x = 10, y = 20, z = 0) | ||||
| ///     |> extrude(length = 5) | ||||
| /// | ||||
| /// startSketchOn(sketch002, face = sketchingFace) | ||||
| ///   |> startProfile(at = [1, 1]) | ||||
| ///   |> line(end = [8, 0]) | ||||
| ///   |> line(end = [0, 8]) | ||||
| ///   |> line(end = [-8, 0]) | ||||
| ///   |> close(tag = $sketchingFace002) | ||||
| ///   |> extrude(length = 10) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```no_run | ||||
| /// // You can also use the tags from the original geometry to fillet the cloned geometry. | ||||
| /// | ||||
| /// width = 20 | ||||
| /// length = 10 | ||||
| /// thickness = 1 | ||||
| /// filletRadius = 2 | ||||
| /// | ||||
| /// mountingPlateSketch = startSketchOn(XY) | ||||
| ///   |> startProfile(at = [-width/2, -length/2]) | ||||
| ///   |> line(endAbsolute = [width/2, -length/2], tag = $edge1) | ||||
| ///   |> line(endAbsolute = [width/2, length/2], tag = $edge2) | ||||
| ///   |> line(endAbsolute = [-width/2, length/2], tag = $edge3) | ||||
| ///   |> close(tag = $edge4) | ||||
| /// | ||||
| /// mountingPlate = extrude(mountingPlateSketch, length = thickness) | ||||
| /// | ||||
| /// clonedMountingPlate = clone(mountingPlate) | ||||
| ///   |> fillet( | ||||
| ///     radius = filletRadius, | ||||
| ///     tags = [ | ||||
| ///       getNextAdjacentEdge(edge1), | ||||
| ///       getNextAdjacentEdge(edge2), | ||||
| ///       getNextAdjacentEdge(edge3), | ||||
| ///       getNextAdjacentEdge(edge4) | ||||
| ///     ], | ||||
| ///   ) | ||||
| ///   |> translate(x = 0, y = 50, z = 0) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```no_run | ||||
| /// // Create a spring by sweeping around a helix path from a cloned sketch. | ||||
| /// | ||||
| /// // Create a helix around the Z axis. | ||||
| /// helixPath = helix( | ||||
| ///     angleStart = 0, | ||||
| ///     ccw = true, | ||||
| ///     revolutions = 4, | ||||
| ///     length = 10, | ||||
| ///     radius = 5, | ||||
| ///     axis = Z, | ||||
| ///  ) | ||||
| /// | ||||
| /// | ||||
| /// springSketch = startSketchOn(YZ) | ||||
| ///     |> circle( center = [0, 0], radius = 1) | ||||
| /// | ||||
| /// // Create a spring by sweeping around the helix path. | ||||
| /// sweepedSpring = clone(springSketch) | ||||
| ///     |> translate(x=100) | ||||
| ///     |> sweep(path = helixPath) | ||||
| /// ``` | ||||
| /// | ||||
| /// ``` | ||||
| /// // A donut shape from a cloned sketch. | ||||
| /// sketch001 = startSketchOn(XY) | ||||
| ///     |> circle( center = [15, 0], radius = 5 ) | ||||
| /// | ||||
| /// sketch002 = clone(sketch001) | ||||
| ///    |> translate( z = 30) | ||||
| ///     |> revolve( | ||||
| ///         angle = 360, | ||||
| ///         axis = Y, | ||||
| ///     ) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```no_run | ||||
| /// // Sketch on the end of a revolved face by tagging the end face. | ||||
| /// // This shows the cloned geometry will have the same tags as the original geometry. | ||||
| /// | ||||
| /// exampleSketch = startSketchOn(XY) | ||||
| ///   |> startProfile(at = [4, 12]) | ||||
| ///   |> line(end = [2, 0]) | ||||
| ///   |> line(end = [0, -6]) | ||||
| ///   |> line(end = [4, -6]) | ||||
| ///   |> line(end = [0, -6]) | ||||
| ///   |> line(end = [-3.75, -4.5]) | ||||
| ///   |> line(end = [0, -5.5]) | ||||
| ///   |> line(end = [-2, 0]) | ||||
| ///   |> close() | ||||
| /// | ||||
| /// example001 = revolve(exampleSketch, axis = Y, angle = 180, tagEnd = $end01) | ||||
| /// | ||||
| /// // example002 = clone(example001) | ||||
| /// // |> translate(x = 0, y = 20, z = 0) | ||||
| /// | ||||
| /// // Sketch on the cloned face. | ||||
| /// // exampleSketch002 = startSketchOn(example002, face = end01) | ||||
| /// //  |> startProfile(at = [4.5, -5]) | ||||
| /// //  |> line(end = [0, 5]) | ||||
| /// //  |> line(end = [5, 0]) | ||||
| /// //  |> line(end = [0, -5]) | ||||
| /// //  |> close() | ||||
| /// | ||||
| /// // example003 = extrude(exampleSketch002, length = 5) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```no_run | ||||
| /// // Clone an imported model. | ||||
| /// | ||||
| /// import "tests/inputs/cube.sldprt" as cube | ||||
| /// | ||||
| /// myCube = cube | ||||
| /// | ||||
| /// clonedCube = clone(myCube) | ||||
| ///    |> translate( | ||||
| ///    x = 1020, | ||||
| ///    ) | ||||
| ///    |> appearance( | ||||
| ///        color = "#ff0000", | ||||
| ///        metalness = 50, | ||||
| ///        roughness = 50 | ||||
| ///    ) | ||||
| /// ``` | ||||
| #[stdlib { | ||||
|     name = "clone", | ||||
|     feature_tree_operation = true, | ||||
|     keywords = true, | ||||
|     unlabeled_first = true, | ||||
|     args = { | ||||
|         geometry = { docs = "The sketch, solid, or imported geometry to be cloned" }, | ||||
|     } | ||||
| }] | ||||
| async fn inner_clone( | ||||
|     geometry: GeometryWithImportedGeometry, | ||||
|     exec_state: &mut ExecState, | ||||
|  | ||||
| @ -231,3 +231,38 @@ pub async fn ln(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclE | ||||
|  | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, exec_state.current_default_units()))) | ||||
| } | ||||
|  | ||||
| /// Compute the length of the given leg. | ||||
| pub async fn leg_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?; | ||||
|     let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?; | ||||
|     let (hypotenuse, leg, ty) = NumericType::combine_eq_coerce(hypotenuse, leg); | ||||
|     let result = (hypotenuse.powi(2) - f64::min(hypotenuse.abs(), leg.abs()).powi(2)).sqrt(); | ||||
|     Ok(KclValue::from_number_with_type(result, ty, vec![args.into()])) | ||||
| } | ||||
|  | ||||
| /// Compute the angle of the given leg for x. | ||||
| pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?; | ||||
|     let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?; | ||||
|     let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg); | ||||
|     let result = (leg.min(hypotenuse) / hypotenuse).acos().to_degrees(); | ||||
|     Ok(KclValue::from_number_with_type( | ||||
|         result, | ||||
|         NumericType::degrees(), | ||||
|         vec![args.into()], | ||||
|     )) | ||||
| } | ||||
|  | ||||
| /// Compute the angle of the given leg for y. | ||||
| pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?; | ||||
|     let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?; | ||||
|     let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg); | ||||
|     let result = (leg.min(hypotenuse) / hypotenuse).asin().to_degrees(); | ||||
|     Ok(KclValue::from_number_with_type( | ||||
|         result, | ||||
|         NumericType::degrees(), | ||||
|         vec![args.into()], | ||||
|     )) | ||||
| } | ||||
|  | ||||
| @ -28,21 +28,13 @@ pub mod utils; | ||||
|  | ||||
| use anyhow::Result; | ||||
| pub use args::Args; | ||||
| use args::TyF64; | ||||
| use indexmap::IndexMap; | ||||
| use kcl_derive_docs::stdlib; | ||||
| use lazy_static::lazy_static; | ||||
| use parse_display::{Display, FromStr}; | ||||
| use schemars::JsonSchema; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use crate::{ | ||||
|     docs::StdLibFn, | ||||
|     errors::KclError, | ||||
|     execution::{ | ||||
|         types::{NumericType, PrimitiveType, RuntimeType, UnitAngle, UnitType}, | ||||
|         ExecState, KclValue, | ||||
|     }, | ||||
|     execution::{types::PrimitiveType, ExecState, KclValue}, | ||||
|     parsing::ast::types::Name, | ||||
| }; | ||||
|  | ||||
| @ -53,9 +45,6 @@ pub type StdFn = fn( | ||||
|  | ||||
| lazy_static! { | ||||
|     static ref CORE_FNS: Vec<Box<dyn StdLibFn>> = vec![ | ||||
|         Box::new(LegLen), | ||||
|         Box::new(LegAngX), | ||||
|         Box::new(LegAngY), | ||||
|         Box::new(crate::std::appearance::Appearance), | ||||
|         Box::new(crate::std::extrude::Extrude), | ||||
|         Box::new(crate::std::segment::SegEnd), | ||||
| @ -87,17 +76,12 @@ lazy_static! { | ||||
|         Box::new(crate::std::sketch::TangentialArc), | ||||
|         Box::new(crate::std::sketch::BezierCurve), | ||||
|         Box::new(crate::std::sketch::Subtract2D), | ||||
|         Box::new(crate::std::clone::Clone), | ||||
|         Box::new(crate::std::patterns::PatternLinear2D), | ||||
|         Box::new(crate::std::patterns::PatternLinear3D), | ||||
|         Box::new(crate::std::patterns::PatternCircular2D), | ||||
|         Box::new(crate::std::patterns::PatternCircular3D), | ||||
|         Box::new(crate::std::patterns::PatternTransform), | ||||
|         Box::new(crate::std::patterns::PatternTransform2D), | ||||
|         Box::new(crate::std::array::Reduce), | ||||
|         Box::new(crate::std::array::Map), | ||||
|         Box::new(crate::std::array::Push), | ||||
|         Box::new(crate::std::array::Pop), | ||||
|         Box::new(crate::std::edge::GetOppositeEdge), | ||||
|         Box::new(crate::std::edge::GetNextAdjacentEdge), | ||||
|         Box::new(crate::std::edge::GetPreviousAdjacentEdge), | ||||
| @ -149,84 +133,96 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp | ||||
|     match (path, fn_name) { | ||||
|         ("math", "cos") => ( | ||||
|             |e, a| Box::pin(crate::std::math::cos(e, a)), | ||||
|             StdFnProps::default("std::cos"), | ||||
|             StdFnProps::default("std::math::cos"), | ||||
|         ), | ||||
|         ("math", "sin") => ( | ||||
|             |e, a| Box::pin(crate::std::math::sin(e, a)), | ||||
|             StdFnProps::default("std::sin"), | ||||
|             StdFnProps::default("std::math::sin"), | ||||
|         ), | ||||
|         ("math", "tan") => ( | ||||
|             |e, a| Box::pin(crate::std::math::tan(e, a)), | ||||
|             StdFnProps::default("std::tan"), | ||||
|             StdFnProps::default("std::math::tan"), | ||||
|         ), | ||||
|         ("math", "acos") => ( | ||||
|             |e, a| Box::pin(crate::std::math::acos(e, a)), | ||||
|             StdFnProps::default("std::acos"), | ||||
|             StdFnProps::default("std::math::acos"), | ||||
|         ), | ||||
|         ("math", "asin") => ( | ||||
|             |e, a| Box::pin(crate::std::math::asin(e, a)), | ||||
|             StdFnProps::default("std::asin"), | ||||
|             StdFnProps::default("std::math::asin"), | ||||
|         ), | ||||
|         ("math", "atan") => ( | ||||
|             |e, a| Box::pin(crate::std::math::atan(e, a)), | ||||
|             StdFnProps::default("std::atan"), | ||||
|             StdFnProps::default("std::math::atan"), | ||||
|         ), | ||||
|         ("math", "atan2") => ( | ||||
|             |e, a| Box::pin(crate::std::math::atan2(e, a)), | ||||
|             StdFnProps::default("std::atan2"), | ||||
|             StdFnProps::default("std::math::atan2"), | ||||
|         ), | ||||
|         ("math", "sqrt") => ( | ||||
|             |e, a| Box::pin(crate::std::math::sqrt(e, a)), | ||||
|             StdFnProps::default("std::sqrt"), | ||||
|             StdFnProps::default("std::math::sqrt"), | ||||
|         ), | ||||
|  | ||||
|         ("math", "abs") => ( | ||||
|             |e, a| Box::pin(crate::std::math::abs(e, a)), | ||||
|             StdFnProps::default("std::abs"), | ||||
|             StdFnProps::default("std::math::abs"), | ||||
|         ), | ||||
|         ("math", "rem") => ( | ||||
|             |e, a| Box::pin(crate::std::math::rem(e, a)), | ||||
|             StdFnProps::default("std::rem"), | ||||
|             StdFnProps::default("std::math::rem"), | ||||
|         ), | ||||
|         ("math", "round") => ( | ||||
|             |e, a| Box::pin(crate::std::math::round(e, a)), | ||||
|             StdFnProps::default("std::round"), | ||||
|             StdFnProps::default("std::math::round"), | ||||
|         ), | ||||
|         ("math", "floor") => ( | ||||
|             |e, a| Box::pin(crate::std::math::floor(e, a)), | ||||
|             StdFnProps::default("std::floor"), | ||||
|             StdFnProps::default("std::math::floor"), | ||||
|         ), | ||||
|         ("math", "ceil") => ( | ||||
|             |e, a| Box::pin(crate::std::math::ceil(e, a)), | ||||
|             StdFnProps::default("std::ceil"), | ||||
|             StdFnProps::default("std::math::ceil"), | ||||
|         ), | ||||
|         ("math", "min") => ( | ||||
|             |e, a| Box::pin(crate::std::math::min(e, a)), | ||||
|             StdFnProps::default("std::min"), | ||||
|             StdFnProps::default("std::math::min"), | ||||
|         ), | ||||
|         ("math", "max") => ( | ||||
|             |e, a| Box::pin(crate::std::math::max(e, a)), | ||||
|             StdFnProps::default("std::max"), | ||||
|             StdFnProps::default("std::math::max"), | ||||
|         ), | ||||
|         ("math", "pow") => ( | ||||
|             |e, a| Box::pin(crate::std::math::pow(e, a)), | ||||
|             StdFnProps::default("std::pow"), | ||||
|             StdFnProps::default("std::math::pow"), | ||||
|         ), | ||||
|         ("math", "log") => ( | ||||
|             |e, a| Box::pin(crate::std::math::log(e, a)), | ||||
|             StdFnProps::default("std::log"), | ||||
|             StdFnProps::default("std::math::log"), | ||||
|         ), | ||||
|         ("math", "log2") => ( | ||||
|             |e, a| Box::pin(crate::std::math::log2(e, a)), | ||||
|             StdFnProps::default("std::log2"), | ||||
|             StdFnProps::default("std::math::log2"), | ||||
|         ), | ||||
|         ("math", "log10") => ( | ||||
|             |e, a| Box::pin(crate::std::math::log10(e, a)), | ||||
|             StdFnProps::default("std::log10"), | ||||
|             StdFnProps::default("std::math::log10"), | ||||
|         ), | ||||
|         ("math", "ln") => ( | ||||
|             |e, a| Box::pin(crate::std::math::ln(e, a)), | ||||
|             StdFnProps::default("std::ln"), | ||||
|             StdFnProps::default("std::math::ln"), | ||||
|         ), | ||||
|         ("math", "legLen") => ( | ||||
|             |e, a| Box::pin(crate::std::math::leg_length(e, a)), | ||||
|             StdFnProps::default("std::math::legLen"), | ||||
|         ), | ||||
|         ("math", "legAngX") => ( | ||||
|             |e, a| Box::pin(crate::std::math::leg_angle_x(e, a)), | ||||
|             StdFnProps::default("std::math::legAngX"), | ||||
|         ), | ||||
|         ("math", "legAngY") => ( | ||||
|             |e, a| Box::pin(crate::std::math::leg_angle_y(e, a)), | ||||
|             StdFnProps::default("std::math::legAngY"), | ||||
|         ), | ||||
|         ("sketch", "circle") => ( | ||||
|             |e, a| Box::pin(crate::std::shapes::circle(e, a)), | ||||
| @ -264,6 +260,26 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp | ||||
|             |e, a| Box::pin(crate::std::shell::hollow(e, a)), | ||||
|             StdFnProps::default("std::solid::hollow").include_in_feature_tree(), | ||||
|         ), | ||||
|         ("array", "map") => ( | ||||
|             |e, a| Box::pin(crate::std::array::map(e, a)), | ||||
|             StdFnProps::default("std::array::map"), | ||||
|         ), | ||||
|         ("array", "reduce") => ( | ||||
|             |e, a| Box::pin(crate::std::array::reduce(e, a)), | ||||
|             StdFnProps::default("std::array::reduce"), | ||||
|         ), | ||||
|         ("array", "push") => ( | ||||
|             |e, a| Box::pin(crate::std::array::push(e, a)), | ||||
|             StdFnProps::default("std::array::push"), | ||||
|         ), | ||||
|         ("array", "pop") => ( | ||||
|             |e, a| Box::pin(crate::std::array::pop(e, a)), | ||||
|             StdFnProps::default("std::array::pop"), | ||||
|         ), | ||||
|         ("prelude", "clone") => ( | ||||
|             |e, a| Box::pin(crate::std::clone::clone(e, a)), | ||||
|             StdFnProps::default("std::clone").include_in_feature_tree(), | ||||
|         ), | ||||
|         _ => unreachable!(), | ||||
|     } | ||||
| } | ||||
| @ -341,110 +357,3 @@ pub enum FunctionKind { | ||||
|  | ||||
| /// The default tolerance for modeling commands in [`kittycad_modeling_cmds::length_unit::LengthUnit`]. | ||||
| const DEFAULT_TOLERANCE: f64 = 0.0000001; | ||||
|  | ||||
| /// Compute the length of the given leg. | ||||
| pub async fn leg_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?; | ||||
|     let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?; | ||||
|     let (hypotenuse, leg, ty) = NumericType::combine_eq_coerce(hypotenuse, leg); | ||||
|     let result = inner_leg_length(hypotenuse, leg); | ||||
|     Ok(KclValue::from_number_with_type(result, ty, vec![args.into()])) | ||||
| } | ||||
|  | ||||
| /// Compute the length of the given leg. | ||||
| /// | ||||
| /// ```kcl,no_run | ||||
| /// legLen(hypotenuse = 5, leg = 3) | ||||
| /// ``` | ||||
| #[stdlib { | ||||
|     name = "legLen", | ||||
|     keywords = true, | ||||
|     unlabeled_first = false, | ||||
|     args = { | ||||
|         hypotenuse = { docs = "The length of the triangle's hypotenuse" }, | ||||
|         leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" }, | ||||
|     }, | ||||
|     tags = ["math"], | ||||
| }] | ||||
| fn inner_leg_length(hypotenuse: f64, leg: f64) -> f64 { | ||||
|     (hypotenuse.powi(2) - f64::min(hypotenuse.abs(), leg.abs()).powi(2)).sqrt() | ||||
| } | ||||
|  | ||||
| /// Compute the angle of the given leg for x. | ||||
| pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?; | ||||
|     let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?; | ||||
|     let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg); | ||||
|     let result = inner_leg_angle_x(hypotenuse, leg); | ||||
|     Ok(KclValue::from_number_with_type( | ||||
|         result, | ||||
|         NumericType::Known(UnitType::Angle(UnitAngle::Degrees)), | ||||
|         vec![args.into()], | ||||
|     )) | ||||
| } | ||||
|  | ||||
| /// Compute the angle of the given leg for x. | ||||
| /// | ||||
| /// ```kcl,no_run | ||||
| /// legAngX(hypotenuse = 5, leg = 3) | ||||
| /// ``` | ||||
| #[stdlib { | ||||
|     name = "legAngX", | ||||
|     keywords = true, | ||||
|     unlabeled_first = false, | ||||
|     args = { | ||||
|         hypotenuse = { docs = "The length of the triangle's hypotenuse" }, | ||||
|         leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" }, | ||||
|     }, | ||||
|     tags = ["math"], | ||||
| }] | ||||
| fn inner_leg_angle_x(hypotenuse: f64, leg: f64) -> f64 { | ||||
|     (leg.min(hypotenuse) / hypotenuse).acos().to_degrees() | ||||
| } | ||||
|  | ||||
| /// Compute the angle of the given leg for y. | ||||
| pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?; | ||||
|     let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?; | ||||
|     let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg); | ||||
|     let result = inner_leg_angle_y(hypotenuse, leg); | ||||
|     Ok(KclValue::from_number_with_type( | ||||
|         result, | ||||
|         NumericType::Known(UnitType::Angle(UnitAngle::Degrees)), | ||||
|         vec![args.into()], | ||||
|     )) | ||||
| } | ||||
|  | ||||
| /// Compute the angle of the given leg for y. | ||||
| /// | ||||
| /// ```kcl,no_run | ||||
| /// legAngY(hypotenuse = 5, leg = 3) | ||||
| /// ``` | ||||
| #[stdlib { | ||||
|     name = "legAngY", | ||||
|     keywords = true, | ||||
|     unlabeled_first = false, | ||||
|     args = { | ||||
|         hypotenuse = { docs = "The length of the triangle's hypotenuse" }, | ||||
|         leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" }, | ||||
|     }, | ||||
|     tags = ["math"], | ||||
| }] | ||||
| fn inner_leg_angle_y(hypotenuse: f64, leg: f64) -> f64 { | ||||
|     (leg.min(hypotenuse) / hypotenuse).asin().to_degrees() | ||||
| } | ||||
|  | ||||
| /// The primitive types that can be used in a KCL file. | ||||
| #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Display, FromStr)] | ||||
| #[serde(rename_all = "lowercase")] | ||||
| #[display(style = "lowercase")] | ||||
| pub enum Primitive { | ||||
|     /// A boolean value. | ||||
|     Bool, | ||||
|     /// A number value. | ||||
|     Number, | ||||
|     /// A string value. | ||||
|     String, | ||||
|     /// A uuid value. | ||||
|     Uuid, | ||||
| } | ||||
|  | ||||
| @ -278,6 +278,7 @@ async fn inner_line( | ||||
|             end_absolute, | ||||
|             end, | ||||
|             tag, | ||||
|             relative_name: "end", | ||||
|         }, | ||||
|         exec_state, | ||||
|         args, | ||||
| @ -290,6 +291,7 @@ struct StraightLineParams { | ||||
|     end_absolute: Option<[TyF64; 2]>, | ||||
|     end: Option<[TyF64; 2]>, | ||||
|     tag: Option<TagNode>, | ||||
|     relative_name: &'static str, | ||||
| } | ||||
|  | ||||
| impl StraightLineParams { | ||||
| @ -299,6 +301,7 @@ impl StraightLineParams { | ||||
|             tag, | ||||
|             end: Some(p), | ||||
|             end_absolute: None, | ||||
|             relative_name: "end", | ||||
|         } | ||||
|     } | ||||
|     fn absolute(p: [TyF64; 2], sketch: Sketch, tag: Option<TagNode>) -> Self { | ||||
| @ -307,6 +310,7 @@ impl StraightLineParams { | ||||
|             tag, | ||||
|             end: None, | ||||
|             end_absolute: Some(p), | ||||
|             relative_name: "end", | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -317,6 +321,7 @@ async fn straight_line( | ||||
|         end, | ||||
|         end_absolute, | ||||
|         tag, | ||||
|         relative_name, | ||||
|     }: StraightLineParams, | ||||
|     exec_state: &mut ExecState, | ||||
|     args: Args, | ||||
| @ -335,7 +340,7 @@ async fn straight_line( | ||||
|         (None, None) => { | ||||
|             return Err(KclError::Semantic(KclErrorDetails { | ||||
|                 source_ranges: vec![args.source_range], | ||||
|                 message: "You must supply either `end` or `endAbsolute` arguments".to_owned(), | ||||
|                 message: format!("You must supply either `{relative_name}` or `endAbsolute` arguments"), | ||||
|             })); | ||||
|         } | ||||
|     }; | ||||
| @ -447,6 +452,7 @@ async fn inner_x_line( | ||||
|             end_absolute: end_absolute.map(|x| [x, from.into_y()]), | ||||
|             end: length.map(|x| [x, TyF64::new(0.0, NumericType::mm())]), | ||||
|             tag, | ||||
|             relative_name: "length", | ||||
|         }, | ||||
|         exec_state, | ||||
|         args, | ||||
| @ -512,6 +518,7 @@ async fn inner_y_line( | ||||
|             end_absolute: end_absolute.map(|y| [from.into_x(), y]), | ||||
|             end: length.map(|y| [TyF64::new(0.0, NumericType::mm()), y]), | ||||
|             tag, | ||||
|             relative_name: "length", | ||||
|         }, | ||||
|         exec_state, | ||||
|         args, | ||||
|  | ||||
| @ -2575,6 +2575,27 @@ sketch002 = startSketchOn({ | ||||
|         assert_eq!(actual, input); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn recast_function_types() { | ||||
|         let input = r#"foo = x: fn | ||||
| foo = x: fn(number) | ||||
| fn foo(x: fn(): number): fn { | ||||
|   return 0 | ||||
| } | ||||
| fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn { | ||||
|   return 0 | ||||
| } | ||||
| type fn | ||||
| type foo = fn | ||||
| type foo = fn(a: string, b: { f: fn(): any }) | ||||
| type foo = fn([fn]) | ||||
| type foo = fn(fn, f: fn(number(_))): [fn([any]): string] | ||||
| "#; | ||||
|         let ast = crate::parsing::top_level_parse(input).unwrap(); | ||||
|         let actual = ast.recast(&FormatOptions::new(), 0); | ||||
|         assert_eq!(actual, input); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn unparse_call_inside_function_args_multiple_lines() { | ||||
|         let input = r#"fn foo() { | ||||
|  | ||||
| @ -2,3 +2,169 @@ | ||||
|  | ||||
| @no_std | ||||
| @settings(defaultLengthUnit = mm, kclVersion = 1.0) | ||||
|  | ||||
| /// Apply a function to every element of a list. | ||||
| /// | ||||
| /// Given a list like `[a, b, c]`, and a function like `f`, returns | ||||
| /// `[f(a), f(b), f(c)]` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// r = 10 // radius | ||||
| /// fn drawCircle(@id) { | ||||
| ///   return startSketchOn(XY) | ||||
| ///     |> circle( center= [id * 2 * r, 0], radius= r) | ||||
| /// } | ||||
| /// | ||||
| /// // Call `drawCircle`, passing in each element of the array. | ||||
| /// // The outputs from each `drawCircle` form a new array, | ||||
| /// // which is the return value from `map`. | ||||
| /// circles = map( | ||||
| ///   [1..3], | ||||
| ///   f = drawCircle | ||||
| /// ) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// r = 10 // radius | ||||
| /// // Call `map`, using an anonymous function instead of a named one. | ||||
| /// circles = map( | ||||
| ///   [1..3], | ||||
| ///   f = fn(@id) { | ||||
| ///     return startSketchOn(XY) | ||||
| ///       |> circle( center= [id * 2 * r, 0], radius= r) | ||||
| ///   } | ||||
| /// ) | ||||
| /// ``` | ||||
| @(impl = std_rust) | ||||
| export fn map( | ||||
|   /// Input array. The output array is this input array, but every element has had the function `f` run on it. | ||||
|   @array: [any], | ||||
|   /// A function. The output array is just the input array, but `f` has been run on every item. | ||||
|   f: fn(any): any, | ||||
| ): [any] {} | ||||
|  | ||||
| /// Take a starting value. Then, for each element of an array, calculate the next value, | ||||
| /// using the previous value and the element. | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // This function adds two numbers. | ||||
| /// fn add(@a, accum) { return a + accum } | ||||
| /// | ||||
| /// // This function adds an array of numbers. | ||||
| /// // It uses the `reduce` function, to call the `add` function on every | ||||
| /// // element of the `arr` parameter. The starting value is 0. | ||||
| /// fn sum(@arr) { return reduce(arr, initial = 0, f = add) } | ||||
| /// | ||||
| /// /* | ||||
| /// The above is basically like this pseudo-code: | ||||
| /// fn sum(arr): | ||||
| ///     sumSoFar = 0 | ||||
| ///     for i in arr: | ||||
| ///         sumSoFar = add(i, sumSoFar) | ||||
| ///     return sumSoFar | ||||
| /// */ | ||||
| /// | ||||
| /// // We use `assert` to check that our `sum` function gives the | ||||
| /// // expected result. It's good to check your work! | ||||
| /// assert(sum([1, 2, 3]), isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6") | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // This example works just like the previous example above, but it uses | ||||
| /// // an anonymous `add` function as its parameter, instead of declaring a | ||||
| /// // named function outside. | ||||
| /// arr = [1, 2, 3] | ||||
| /// sum = reduce(arr, initial = 0, f = fn (@i, accum) { return i + accum }) | ||||
| /// | ||||
| /// // We use `assert` to check that our `sum` function gives the | ||||
| /// // expected result. It's good to check your work! | ||||
| /// assert(sum, isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6") | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // Declare a function that sketches a decagon. | ||||
| /// fn decagon(@radius) { | ||||
| ///   // Each side of the decagon is turned this many radians from the previous angle. | ||||
| ///   stepAngle = ((1/10) * TAU): number(rad) | ||||
| /// | ||||
| ///   // Start the decagon sketch at this point. | ||||
| ///   startOfDecagonSketch = startSketchOn(XY) | ||||
| ///     |> startProfile(at = [(cos(0)*radius), (sin(0) * radius)]) | ||||
| /// | ||||
| ///   // Use a `reduce` to draw the remaining decagon sides. | ||||
| ///   // For each number in the array 1..10, run the given function, | ||||
| ///   // which takes a partially-sketched decagon and adds one more edge to it. | ||||
| ///   fullDecagon = reduce([1..10], initial = startOfDecagonSketch, f = fn(@i, accum) { | ||||
| ///       // Draw one edge of the decagon. | ||||
| ///       x = cos(stepAngle * i) * radius | ||||
| ///       y = sin(stepAngle * i) * radius | ||||
| ///       return line(accum, end = [x, y]) | ||||
| ///   }) | ||||
| /// | ||||
| ///   return fullDecagon | ||||
| /// | ||||
| /// } | ||||
| /// | ||||
| /// /* | ||||
| /// The `decagon` above is basically like this pseudo-code: | ||||
| /// fn decagon(radius): | ||||
| ///     stepAngle = ((1/10) * TAU): number(rad) | ||||
| ///     plane = startSketchOn(XY) | ||||
| ///     startOfDecagonSketch = startProfile(plane, at = [(cos(0)*radius), (sin(0) * radius)]) | ||||
| /// | ||||
| ///     // Here's the reduce part. | ||||
| ///     partialDecagon = startOfDecagonSketch | ||||
| ///     for i in [1..10]: | ||||
| ///         x = cos(stepAngle * i) * radius | ||||
| ///         y = sin(stepAngle * i) * radius | ||||
| ///         partialDecagon = line(partialDecagon, end = [x, y]) | ||||
| ///     fullDecagon = partialDecagon // it's now full | ||||
| ///     return fullDecagon | ||||
| /// */ | ||||
| /// | ||||
| /// // Use the `decagon` function declared above, to sketch a decagon with radius 5. | ||||
| /// decagon(5.0) |> close() | ||||
| /// ``` | ||||
| @(impl = std_rust) | ||||
| export fn reduce( | ||||
|   /// Each element of this array gets run through the function `f`, combined with the previous output from `f`, and then used for the next run. | ||||
|   @array: [any], | ||||
|   /// The first time `f` is run, it will be called with the first item of `array` and this initial starting value. | ||||
|   initial: any, | ||||
|   /// Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`. | ||||
|   f: fn(any, accum: any): any, | ||||
| ): [any] {} | ||||
|  | ||||
| /// Append an element to the end of an array. | ||||
| /// | ||||
| /// Returns a new array with the element appended. | ||||
| /// | ||||
| /// ```kcl | ||||
| /// arr = [1, 2, 3] | ||||
| /// new_arr = push(arr, item = 4) | ||||
| /// assert(new_arr[3], isEqualTo = 4, tolerance = 0.1, error = "4 was added to the end of the array") | ||||
| /// ``` | ||||
| @(impl = std_rust) | ||||
| export fn push( | ||||
|   /// The array which you're adding a new item to. | ||||
|   @array: [any], | ||||
|   /// The new item to add to the array | ||||
|   item: any, | ||||
| ): [any; 1+] {} | ||||
|  | ||||
| /// Remove the last element from an array. | ||||
| /// | ||||
| /// Returns a new array with the last element removed. | ||||
| /// | ||||
| /// ```kcl | ||||
| /// arr = [1, 2, 3, 4] | ||||
| /// new_arr = pop(arr) | ||||
| /// assert(new_arr[0], isEqualTo = 1, tolerance = 0.00001, error = "1 is the first element of the array") | ||||
| /// assert(new_arr[1], isEqualTo = 2, tolerance = 0.00001, error = "2 is the second element of the array") | ||||
| /// assert(new_arr[2], isEqualTo = 3, tolerance = 0.00001, error = "3 is the third element of the array") | ||||
| /// ``` | ||||
| @(impl = std_rust) | ||||
| export fn pop( | ||||
|   /// The array to pop from. Must not be empty. | ||||
|   @array: [any; 1+], | ||||
| ): [any] {} | ||||
|  | ||||
| @ -436,3 +436,42 @@ export fn log10(@input: number): number {} | ||||
| /// ``` | ||||
| @(impl = std_rust) | ||||
| export fn ln(@input: number): number {} | ||||
|  | ||||
| /// Compute the length of the given leg. | ||||
| /// | ||||
| /// ```kcl,no_run | ||||
| /// legLen(hypotenuse = 5, leg = 3) | ||||
| /// ``` | ||||
| @(impl = std_rust) | ||||
| export fn legLen( | ||||
|   /// The length of the triangle's hypotenuse. | ||||
|   hypotenuse: number(Length), | ||||
|   /// The length of one of the triangle's legs (i.e. non-hypotenuse side). | ||||
|   leg: number(Length), | ||||
| ): number(deg) {} | ||||
|  | ||||
| /// Compute the angle of the given leg for x. | ||||
| /// | ||||
| /// ```kcl,no_run | ||||
| /// legAngX(hypotenuse = 5, leg = 3) | ||||
| /// ``` | ||||
| @(impl = std_rust) | ||||
| export fn legAngX( | ||||
|   /// The length of the triangle's hypotenuse. | ||||
|   hypotenuse: number(Length), | ||||
|   /// The length of one of the triangle's legs (i.e. non-hypotenuse side). | ||||
|   leg: number(Length), | ||||
| ): number(deg) {} | ||||
|  | ||||
| /// Compute the angle of the given leg for y. | ||||
| /// | ||||
| /// ```kcl,no_run | ||||
| /// legAngY(hypotenuse = 5, leg = 3) | ||||
| /// ``` | ||||
| @(impl = std_rust) | ||||
| export fn legAngY( | ||||
|   /// The length of the triangle's hypotenuse. | ||||
|   hypotenuse: number(Length), | ||||
|   /// The length of one of the triangle's legs (i.e. non-hypotenuse side). | ||||
|   leg: number(Length), | ||||
| ): number(deg) {} | ||||
|  | ||||
| @ -251,3 +251,234 @@ export fn offsetPlane( | ||||
|   /// Distance from the standard plane this new plane will be created at. | ||||
|   offset: number(Length), | ||||
| ): Plane {} | ||||
|  | ||||
| /// Clone a sketch or solid. | ||||
| /// | ||||
| /// This works essentially like a copy-paste operation. It creates a perfect replica | ||||
| /// at that point in time that you can manipulate individually afterwards. | ||||
| /// | ||||
| /// This doesn't really have much utility unless you need the equivalent of a double | ||||
| /// instance pattern with zero transformations. | ||||
| /// | ||||
| /// Really only use this function if YOU ARE SURE you need it. In most cases you | ||||
| /// do not need clone and using a pattern with `instance = 2` is more appropriate. | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // Clone a basic sketch and move it and extrude it. | ||||
| /// exampleSketch = startSketchOn(XY) | ||||
| ///   |> startProfile(at = [0, 0]) | ||||
| ///   |> line(end = [10, 0]) | ||||
| ///   |> line(end = [0, 10]) | ||||
| ///   |> line(end = [-10, 0]) | ||||
| ///   |> close() | ||||
| /// | ||||
| /// clonedSketch = clone(exampleSketch) | ||||
| ///     |> scale( | ||||
| ///     x = 1.0, | ||||
| ///     y = 1.0, | ||||
| ///     z = 2.5, | ||||
| ///     ) | ||||
| ///     |> translate( | ||||
| ///         x = 15.0, | ||||
| ///         y = 0, | ||||
| ///         z = 0, | ||||
| ///     ) | ||||
| ///     |> extrude(length = 5) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // Clone a basic solid and move it. | ||||
| /// | ||||
| /// exampleSketch = startSketchOn(XY) | ||||
| ///   |> startProfile(at = [0, 0]) | ||||
| ///   |> line(end = [10, 0]) | ||||
| ///   |> line(end = [0, 10]) | ||||
| ///   |> line(end = [-10, 0]) | ||||
| ///   |> close() | ||||
| /// | ||||
| /// myPart = extrude(exampleSketch, length = 5) | ||||
| /// clonedPart = clone(myPart) | ||||
| ///     |> translate( | ||||
| ///         x = 25.0, | ||||
| ///     ) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // Translate and rotate a cloned sketch to create a loft. | ||||
| /// | ||||
| /// sketch001 = startSketchOn(XY) | ||||
| ///         |> startProfile(at = [-10, 10]) | ||||
| ///         |> xLine(length = 20) | ||||
| ///         |> yLine(length = -20) | ||||
| ///         |> xLine(length = -20) | ||||
| ///         |> close() | ||||
| /// | ||||
| /// sketch002 = clone(sketch001) | ||||
| ///     |> translate(x = 0, y = 0, z = 20) | ||||
| ///     |> rotate(axis = [0, 0, 1.0], angle = 45) | ||||
| /// | ||||
| /// loft([sketch001, sketch002]) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // Translate a cloned solid. Fillet only the clone. | ||||
| /// | ||||
| /// sketch001 = startSketchOn(XY) | ||||
| ///         |> startProfile(at = [-10, 10]) | ||||
| ///         |> xLine(length = 20) | ||||
| ///         |> yLine(length = -20) | ||||
| ///         |> xLine(length = -20, tag = $filletTag) | ||||
| ///         |> close() | ||||
| ///         |> extrude(length = 5) | ||||
| /// | ||||
| /// | ||||
| /// sketch002 = clone(sketch001) | ||||
| ///     |> translate(x = 0, y = 0, z = 20) | ||||
| ///     |> fillet( | ||||
| ///     radius = 2, | ||||
| ///     tags = [getNextAdjacentEdge(filletTag)], | ||||
| ///     ) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // You can reuse the tags from the original geometry with the cloned geometry. | ||||
| /// | ||||
| /// sketch001 = startSketchOn(XY) | ||||
| ///   |> startProfile(at = [0, 0]) | ||||
| ///   |> line(end = [10, 0]) | ||||
| ///   |> line(end = [0, 10], tag = $sketchingFace) | ||||
| ///   |> line(end = [-10, 0]) | ||||
| ///   |> close() | ||||
| /// | ||||
| /// sketch002 = clone(sketch001) | ||||
| ///     |> translate(x = 10, y = 20, z = 0) | ||||
| ///     |> extrude(length = 5) | ||||
| /// | ||||
| /// startSketchOn(sketch002, face = sketchingFace) | ||||
| ///   |> startProfile(at = [1, 1]) | ||||
| ///   |> line(end = [8, 0]) | ||||
| ///   |> line(end = [0, 8]) | ||||
| ///   |> line(end = [-8, 0]) | ||||
| ///   |> close(tag = $sketchingFace002) | ||||
| ///   |> extrude(length = 10) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // You can also use the tags from the original geometry to fillet the cloned geometry. | ||||
| /// | ||||
| /// width = 20 | ||||
| /// length = 10 | ||||
| /// thickness = 1 | ||||
| /// filletRadius = 2 | ||||
| /// | ||||
| /// mountingPlateSketch = startSketchOn(XY) | ||||
| ///   |> startProfile(at = [-width/2, -length/2]) | ||||
| ///   |> line(endAbsolute = [width/2, -length/2], tag = $edge1) | ||||
| ///   |> line(endAbsolute = [width/2, length/2], tag = $edge2) | ||||
| ///   |> line(endAbsolute = [-width/2, length/2], tag = $edge3) | ||||
| ///   |> close(tag = $edge4) | ||||
| /// | ||||
| /// mountingPlate = extrude(mountingPlateSketch, length = thickness) | ||||
| /// | ||||
| /// clonedMountingPlate = clone(mountingPlate) | ||||
| ///   |> fillet( | ||||
| ///     radius = filletRadius, | ||||
| ///     tags = [ | ||||
| ///       getNextAdjacentEdge(edge1), | ||||
| ///       getNextAdjacentEdge(edge2), | ||||
| ///       getNextAdjacentEdge(edge3), | ||||
| ///       getNextAdjacentEdge(edge4) | ||||
| ///     ], | ||||
| ///   ) | ||||
| ///   |> translate(x = 0, y = 50, z = 0) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // Create a spring by sweeping around a helix path from a cloned sketch. | ||||
| /// | ||||
| /// // Create a helix around the Z axis. | ||||
| /// helixPath = helix( | ||||
| ///     angleStart = 0, | ||||
| ///     ccw = true, | ||||
| ///     revolutions = 4, | ||||
| ///     length = 10, | ||||
| ///     radius = 5, | ||||
| ///     axis = Z, | ||||
| ///  ) | ||||
| /// | ||||
| /// | ||||
| /// springSketch = startSketchOn(YZ) | ||||
| ///     |> circle( center = [0, 0], radius = 1) | ||||
| /// | ||||
| /// // Create a spring by sweeping around the helix path. | ||||
| /// sweepedSpring = clone(springSketch) | ||||
| ///     |> translate(x=100) | ||||
| ///     |> sweep(path = helixPath) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // A donut shape from a cloned sketch. | ||||
| /// sketch001 = startSketchOn(XY) | ||||
| ///     |> circle( center = [15, 0], radius = 5 ) | ||||
| /// | ||||
| /// sketch002 = clone(sketch001) | ||||
| ///    |> translate( z = 30) | ||||
| ///     |> revolve( | ||||
| ///         angle = 360, | ||||
| ///         axis = Y, | ||||
| ///     ) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // Sketch on the end of a revolved face by tagging the end face. | ||||
| /// // This shows the cloned geometry will have the same tags as the original geometry. | ||||
| /// | ||||
| /// exampleSketch = startSketchOn(XY) | ||||
| ///   |> startProfile(at = [4, 12]) | ||||
| ///   |> line(end = [2, 0]) | ||||
| ///   |> line(end = [0, -6]) | ||||
| ///   |> line(end = [4, -6]) | ||||
| ///   |> line(end = [0, -6]) | ||||
| ///   |> line(end = [-3.75, -4.5]) | ||||
| ///   |> line(end = [0, -5.5]) | ||||
| ///   |> line(end = [-2, 0]) | ||||
| ///   |> close() | ||||
| /// | ||||
| /// example001 = revolve(exampleSketch, axis = Y, angle = 180, tagEnd = $end01) | ||||
| /// | ||||
| /// // example002 = clone(example001) | ||||
| /// // |> translate(x = 0, y = 20, z = 0) | ||||
| /// | ||||
| /// // Sketch on the cloned face. | ||||
| /// // exampleSketch002 = startSketchOn(example002, face = end01) | ||||
| /// //  |> startProfile(at = [4.5, -5]) | ||||
| /// //  |> line(end = [0, 5]) | ||||
| /// //  |> line(end = [5, 0]) | ||||
| /// //  |> line(end = [0, -5]) | ||||
| /// //  |> close() | ||||
| /// | ||||
| /// // example003 = extrude(exampleSketch002, length = 5) | ||||
| /// ``` | ||||
| /// | ||||
| /// ```kcl | ||||
| /// // Clone an imported model. | ||||
| /// | ||||
| /// import "tests/inputs/cube.sldprt" as cube | ||||
| /// | ||||
| /// myCube = cube | ||||
| /// | ||||
| /// clonedCube = clone(myCube) | ||||
| ///    |> translate( | ||||
| ///    x = 1020, | ||||
| ///    ) | ||||
| ///    |> appearance( | ||||
| ///        color = "#ff0000", | ||||
| ///        metalness = 50, | ||||
| ///        roughness = 50 | ||||
| ///    ) | ||||
| /// ``` | ||||
| @(impl = std_rust) | ||||
| export fn clone( | ||||
|   /// The sketch, solid, or imported geometry to be cloned. | ||||
|   @geometry: Sketch | Solid | ImportedGeometry, | ||||
| ): Sketch | Solid | ImportedGeometry {} | ||||
|  | ||||
| @ -163,6 +163,14 @@ export type string | ||||
| @(impl = primitive) | ||||
| export type tag | ||||
|  | ||||
| /// Represents geometry which is defined using some other CAD system and imported into KCL. | ||||
| @(impl = primitive) | ||||
| export type ImportedGeometry | ||||
|  | ||||
| /// The type of any function in KCL. | ||||
| @(impl = primitive) | ||||
| export type fn | ||||
|  | ||||
| /// An abstract plane. | ||||
| /// | ||||
| /// A plane has a position and orientation in space defined by its origin and axes. A plane is abstract | ||||
|  | ||||
| @ -4,11 +4,22 @@ description: Error from executing argument_error.kcl | ||||
| --- | ||||
| KCL Semantic error | ||||
|  | ||||
|   × semantic: This function expected the input argument to be of type | ||||
|   │ Vec<KclValue> but it's actually of type Function | ||||
|    ╭─[5:5] | ||||
|   × semantic: f requires a value with type `fn(any): any`, but found array | ||||
|   │ (list) | ||||
|    ╭─[5:1] | ||||
|  4 │  | ||||
|  5 │ map(f, f = [0, 1]) | ||||
|    ·     ┬ | ||||
|    ·     ╰── tests/argument_error/input.kcl | ||||
|    · ─────────┬────────┬ | ||||
|    ·          │        ╰── tests/argument_error/input.kcl | ||||
|    ·          ╰── tests/argument_error/input.kcl | ||||
|    ╰──── | ||||
|   ╰─▶ KCL Semantic error | ||||
|        | ||||
|         × semantic: f requires a value with type `fn(any): any`, but found | ||||
|         │ array (list) | ||||
|          ╭─[5:12] | ||||
|        4 │ | ||||
|        5 │ map(f, f = [0, 1]) | ||||
|          ·            ───┬── | ||||
|          ·               ╰── tests/argument_error/input.kcl | ||||
|          ╰──── | ||||
|  | ||||
| @ -4,10 +4,22 @@ description: Error from executing array_elem_pop_empty_fail.kcl | ||||
| --- | ||||
| KCL Semantic error | ||||
|  | ||||
|   × semantic: Cannot pop from an empty array | ||||
|   × semantic: The input argument of `std::array::pop` requires a value with | ||||
|   │ type `[any; 1+]`, but found array (list) | ||||
|    ╭─[2:8] | ||||
|  1 │ arr = [] | ||||
|  2 │ fail = pop(arr) | ||||
|    ·        ────┬─── | ||||
|    ·        ────┬───┬ | ||||
|    ·            │   ╰── tests/array_elem_pop_empty_fail/input.kcl | ||||
|    ·            ╰── tests/array_elem_pop_empty_fail/input.kcl | ||||
|    ╰──── | ||||
|   ╰─▶ KCL Semantic error | ||||
|        | ||||
|         × semantic: The input argument of `std::array::pop` requires a value | ||||
|         │ with type `[any; 1+]`, but found array (list) | ||||
|          ╭─[2:12] | ||||
|        1 │ arr = [] | ||||
|        2 │ fail = pop(arr) | ||||
|          ·            ─┬─ | ||||
|          ·             ╰── tests/array_elem_pop_empty_fail/input.kcl | ||||
|          ╰──── | ||||
|  | ||||
| @ -105,10 +105,8 @@ description: Operations executed clone_w_fillets.kcl | ||||
|     "sourceRange": [] | ||||
|   }, | ||||
|   { | ||||
|     "labeledArgs": {}, | ||||
|     "type": "KclStdLibCall", | ||||
|     "name": "clone", | ||||
|     "sourceRange": [], | ||||
|     "type": "StdLibCall", | ||||
|     "unlabeledArg": { | ||||
|       "value": { | ||||
|         "type": "Solid", | ||||
| @ -117,6 +115,8 @@ description: Operations executed clone_w_fillets.kcl | ||||
|         } | ||||
|       }, | ||||
|       "sourceRange": [] | ||||
|     } | ||||
|     }, | ||||
|     "labeledArgs": {}, | ||||
|     "sourceRange": [] | ||||
|   } | ||||
| ] | ||||
|  | ||||
| @ -98,10 +98,8 @@ description: Operations executed clone_w_shell.kcl | ||||
|     "sourceRange": [] | ||||
|   }, | ||||
|   { | ||||
|     "labeledArgs": {}, | ||||
|     "type": "KclStdLibCall", | ||||
|     "name": "clone", | ||||
|     "sourceRange": [], | ||||
|     "type": "StdLibCall", | ||||
|     "unlabeledArg": { | ||||
|       "value": { | ||||
|         "type": "Solid", | ||||
| @ -110,6 +108,8 @@ description: Operations executed clone_w_shell.kcl | ||||
|         } | ||||
|       }, | ||||
|       "sourceRange": [] | ||||
|     } | ||||
|     }, | ||||
|     "labeledArgs": {}, | ||||
|     "sourceRange": [] | ||||
|   } | ||||
| ] | ||||
|  | ||||
| @ -678,7 +678,7 @@ flowchart LR | ||||
|   99 --- 245 | ||||
|   99 --- 293 | ||||
|   106 --- 193 | ||||
|   106 x--> 206 | ||||
|   106 x--> 207 | ||||
|   106 --- 272 | ||||
|   106 --- 321 | ||||
|   126 --- 194 | ||||
| @ -1012,7 +1012,7 @@ flowchart LR | ||||
|   233 <--x 205 | ||||
|   234 <--x 205 | ||||
|   235 <--x 205 | ||||
|   272 <--x 207 | ||||
|   272 <--x 206 | ||||
|   254 <--x 213 | ||||
|   255 <--x 213 | ||||
|   256 <--x 213 | ||||
|  | ||||
| Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB | 
| Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB | 
| Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB | 
| Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB | 
| Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB | 
| Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB | 
| Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB | 
| Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB | 
| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 85 KiB | 
| Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB | 
| Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB | 
| Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB | 
| Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 174 KiB | 
| Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB | 
