Merge branch 'main' into achalmers/grid-scale2
| @ -9,6 +9,7 @@ VITE_KC_SITE_BASE_URL=https://dev.zoo.dev | ||||
| VITE_KC_SITE_APP_URL=https://app.dev.zoo.dev | ||||
| VITE_KC_SKIP_AUTH=false | ||||
| VITE_KC_CONNECTION_TIMEOUT_MS=5000 | ||||
| #VITE_WASM_URL="optional way of overriding the wasm url, particular for unit tests which need this if you running not on the default 3000 port" | ||||
| #VITE_KC_DEV_TOKEN="optional token to skip auth in the app" | ||||
| #token="required token for playwright. TODO: clean up env vars in #3973" | ||||
|  | ||||
|  | ||||
							
								
								
									
										3
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,3 @@ | ||||
| * @KittyCAD/frontend | ||||
| /src/ @KittyCAD/frontend | ||||
| /rust/ @KittyCAD/kcl | ||||
							
								
								
									
										11
									
								
								.github/ISSUE_TEMPLATE/release.md
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -2,7 +2,7 @@ | ||||
| name: Release | ||||
| about: Create a new release for the Zoo Design Studio | ||||
| title: "Cut release v1.?.?" | ||||
| labels: [release] | ||||
| labels: [meta/release] | ||||
| --- | ||||
|  | ||||
| > Instructions: https://github.com/KittyCAD/modeling-app/blob/main/CONTRIBUTING.md#shipping-releases | ||||
| @ -19,7 +19,8 @@ Release builds URL: ??? | ||||
| * [ ] Confirm the application opens (dismiss the updater) | ||||
| * [ ] Create a project with a basic Text-to-CAD prompt | ||||
| * [ ] Confirm the result is viewable in an engine stream | ||||
| * [ ] Open the application again and confirm the updater can downgrade | ||||
| * [ ] Use 'Check for updates' to bring back the updater toast | ||||
| * [ ] Confirm the app can update to the previous release | ||||
|  | ||||
| ## macOS via ??? | ||||
|  | ||||
| @ -27,7 +28,8 @@ Release builds URL: ??? | ||||
| * [ ] Confirm the application opens (dismiss the updater) | ||||
| * [ ] Create a project with a basic Text-to-CAD prompt | ||||
| * [ ] Confirm the result is viewable in an engine stream | ||||
| * [ ] Open the application again and confirm the updater can downgrade | ||||
| * [ ] Use 'Check for updates' to bring back the updater toast | ||||
| * [ ] Confirm the app can update to the previous release | ||||
|  | ||||
| ## Linux via ??? | ||||
|  | ||||
| @ -35,4 +37,5 @@ Release builds URL: ??? | ||||
| * [ ] Confirm the application opens (dismiss the updater) | ||||
| * [ ] Create a project with a basic Text-to-CAD prompt | ||||
| * [ ] Confirm the result is viewable in an engine stream | ||||
| * [ ] Open the application again and confirm the updater can downgrade | ||||
| * [ ] Use 'Check for updates' to bring back the updater toast | ||||
| * [ ] Confirm the app can update to the previous release | ||||
|  | ||||
							
								
								
									
										6
									
								
								.github/workflows/cargo-fmt.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -31,15 +31,15 @@ jobs: | ||||
|       - name: Use correct Rust toolchain | ||||
|         shell: bash | ||||
|         run: | | ||||
|           [ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./ | ||||
|           cp .github/workflows/nightly-rust-toolchain.toml rust-toolchain.toml | ||||
|       - name: Install rust | ||||
|         uses: actions-rust-lang/setup-rust-toolchain@v1 | ||||
|         with: | ||||
|           cache-workspaces: rust | ||||
|           components: rustfmt | ||||
|  | ||||
|       - name: Run cargo fmt | ||||
|       - name: Run nightly cargo fmt | ||||
|         run: | | ||||
|           cd rust | ||||
|           cargo fmt -- --check | ||||
|           cargo +nightly fmt -- --check | ||||
|         shell: bash | ||||
|  | ||||
							
								
								
									
										15
									
								
								.github/workflows/e2e-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -95,7 +95,8 @@ jobs: | ||||
|         shell: bash | ||||
|         run: npm run build:wasm | ||||
|  | ||||
|       - uses: actions/upload-artifact@v4 | ||||
|       - name: Upload compiled wasm artifacts | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: prepared-wasm | ||||
|           path: | | ||||
| @ -176,7 +177,8 @@ jobs: | ||||
|           CI_SUITE: e2e:snapshots | ||||
|           TARGET: web | ||||
|  | ||||
|       - uses: actions/upload-artifact@v4 | ||||
|       - name: Upload playwright report | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         if: ${{ !cancelled() }} | ||||
|         with: | ||||
|           name: playwright-report-snapshot-${{ github.sha }} | ||||
| @ -290,7 +292,8 @@ jobs: | ||||
|           CI_SUITE: e2e:web | ||||
|           TARGET: web | ||||
|  | ||||
|       - uses: actions/upload-artifact@v4 | ||||
|       - name: Upload playwright report | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         if: ${{ !cancelled() && (success() || failure()) }} | ||||
|         with: | ||||
|           name: playwright-report-web-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} | ||||
| @ -415,7 +418,8 @@ jobs: | ||||
|           CI_SUITE: e2e:desktop | ||||
|           TARGET: desktop | ||||
|  | ||||
|       - uses: actions/upload-artifact@v4 | ||||
|       - name: Upload test report | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         if: always() | ||||
|         with: | ||||
|           name: test-results-desktop-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} | ||||
| @ -424,7 +428,8 @@ jobs: | ||||
|           retention-days: 30 | ||||
|           overwrite: true | ||||
|  | ||||
|       - uses: actions/upload-artifact@v4 | ||||
|       - name: Upload playwright report | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         if: always() | ||||
|         with: | ||||
|           name: playwright-report-desktop-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }} | ||||
|  | ||||
							
								
								
									
										3
									
								
								.github/workflows/nightly-rust-toolchain.toml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,3 @@ | ||||
| [toolchain] | ||||
| channel = "nightly" | ||||
| components = ["rustfmt"] | ||||
| @ -10,9 +10,11 @@ Extend the current sketch with a new involute circular curve. | ||||
| ```kcl | ||||
| involuteCircular( | ||||
|   @sketch: Sketch, | ||||
|   startRadius: number(Length), | ||||
|   endRadius: number(Length), | ||||
|   angle: number(Angle), | ||||
|   startRadius?: number(Length), | ||||
|   endRadius?: number(Length), | ||||
|   startDiameter?: number(Length), | ||||
|   endDiameter?: number(Length), | ||||
|   reverse?: bool, | ||||
|   tag?: TagDecl, | ||||
| ): Sketch | ||||
| @ -25,9 +27,11 @@ involuteCircular( | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | Which sketch should this path be added to? | Yes | | ||||
| | `startRadius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, start_radius is the radius of the inner circle. | Yes | | ||||
| | `endRadius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, end_radius is the radius of the outer circle. | Yes | | ||||
| | `angle` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | The angle to rotate the involute by. A value of zero will produce a curve with a tangent along the x-axis at the start point of the curve. | Yes | | ||||
| | `startRadius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, startRadius is the radius of the inner circle. Either `startRadius` or `startDiameter` must be given (but not both). | No | | ||||
| | `endRadius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, endRadius is the radius of the outer circle. Either `endRadius` or `endDiameter` must be given (but not both). | No | | ||||
| | `startDiameter` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, startDiameter describes the inner circle. Either `startRadius` or `startDiameter` must be given (but not both). | No | | ||||
| | `endDiameter` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The involute is described between two circles, endDiameter describes the outer circle. Either `endRadius` or `endDiameter` must be given (but not both). | No | | ||||
| | `reverse` | [`bool`](/docs/kcl-std/types/std-types-bool) | If reverse is true, the segment will start from the end of the involute, otherwise it will start from that start. | No | | ||||
| | `tag` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | Create a new tag which refers to this line. | No | | ||||
|  | ||||
|  | ||||
| @ -29,7 +29,7 @@ The sketches need to be closed and on different planes that are parallel. | ||||
| | `vDegree` | [`number(_)`](/docs/kcl-std/types/std-types-number) | Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. | No | | ||||
| | `bezApproximateRational` | [`bool`](/docs/kcl-std/types/std-types-bool) | Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios. Over time, this field won't be necessary. | No | | ||||
| | `baseCurveIndex` | [`number(_)`](/docs/kcl-std/types/std-types-number) | This can be set to override the automatically determined topological base curve, which is usually the first section encountered. | No | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Tolerance for the loft operation. | No | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No | | ||||
| | `tagStart` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the start of the loft, i.e. the original sketch. | No | | ||||
| | `tagEnd` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the end of the loft. | No | | ||||
|  | ||||
|  | ||||
| @ -38,7 +38,7 @@ revolved around the same axis. | ||||
| | `sketches` | [`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) | The sketch or set of sketches that should be revolved | Yes | | ||||
| | `axis` | [`Axis2d`](/docs/kcl-std/types/std-types-Axis2d) or [`Edge`](/docs/kcl-std/types/std-types-Edge) | Axis of revolution. | Yes | | ||||
| | `angle` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | Angle to revolve (in degrees). Default is 360. | No | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Tolerance for the revolve operation. | No | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No | | ||||
| | `symmetric` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the extrusion will happen symmetrically around the sketch. Otherwise, the extrusion will happen on only one side of the sketch. | No | | ||||
| | `bidirectionalAngle` | [`number(Angle)`](/docs/kcl-std/types/std-types-number) | If specified, will also revolve in the opposite direction to 'angle' to the specified angle. If 'symmetric' is true, this value is ignored. | No | | ||||
| | `tagStart` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the start of the revolve, i.e. the original sketch. | No | | ||||
|  | ||||
| @ -35,7 +35,7 @@ swept along the same path. | ||||
| | `sketches` | [`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) | The sketch or set of sketches that should be swept in space. | Yes | | ||||
| | `path` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) or [`Helix`](/docs/kcl-std/types/std-types-Helix) | The path to sweep the sketch along. | Yes | | ||||
| | `sectional` | [`bool`](/docs/kcl-std/types/std-types-bool) | If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Tolerance for this operation. | No | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No | | ||||
| | `relativeTo` | [`string`](/docs/kcl-std/types/std-types-string) | What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. | No | | ||||
| | `tagStart` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the start of the sweep, i.e. the original sketch. | No | | ||||
| | `tagEnd` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | A named tag for the face at the end of the sweep. | No | | ||||
|  | ||||
| @ -28,7 +28,7 @@ will smoothly blend the transition. | ||||
| | `solid` | [`Solid`](/docs/kcl-std/types/std-types-Solid) | The solid whose edges should be filletted | Yes | | ||||
| | `radius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The radius of the fillet | Yes | | ||||
| | `tags` | [`[Edge; 1+]`](/docs/kcl-std/types/std-types-Edge) | The paths you want to fillet | Yes | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The tolerance for this fillet | No | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No | | ||||
| | `tag` | [`TagDecl`](/docs/kcl-std/types/std-types-TagDecl) | Create a new tag which refers to this fillet | No | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
| @ -24,7 +24,7 @@ verifying fit, and analyzing overlapping geometries in assemblies. | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `solids` | `[Solid; 2+]` | The solids to intersect. | Yes | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the intersection operation. | No | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
|  | ||||
| @ -27,7 +27,7 @@ and complex multi-body part modeling. | ||||
| |----------|------|-------------|----------| | ||||
| | `solids` | [`[Solid; 1+]`](/docs/kcl-std/types/std-types-Solid) | The solids to use as the base to subtract from. | Yes | | ||||
| | `tools` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) | The solids to subtract. | Yes | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the subtraction operation. | No | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
|  | ||||
| @ -21,7 +21,7 @@ union( | ||||
| | Name | Type | Description | Required | | ||||
| |----------|------|-------------|----------| | ||||
| | `solids` | `[Solid; 2+]` | The solids to union. | Yes | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the union operation. | No | | ||||
| | `tolerance` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | No | | ||||
|  | ||||
| ### Returns | ||||
|  | ||||
|  | ||||
| @ -4,7 +4,6 @@ import * as fsp from 'fs/promises' | ||||
|  | ||||
| import { executorInputPath, getUtils } from '@e2e/playwright/test-utils' | ||||
| import { expect, test } from '@e2e/playwright/zoo-test' | ||||
| import { expectPixelColor } from '@e2e/playwright/fixtures/sceneFixture' | ||||
|  | ||||
| test.describe('Command bar tests', () => { | ||||
|   test('Extrude from command bar selects extrude line after', async ({ | ||||
| @ -308,7 +307,7 @@ test.describe('Command bar tests', () => { | ||||
|     ) | ||||
|  | ||||
|     const continueButton = page.getByRole('button', { name: 'Continue' }) | ||||
|     const submitButton = page.getByRole('button', { name: 'Submit command' }) | ||||
|     const submitButton = page.getByTestId('command-bar-submit') | ||||
|     await continueButton.click() | ||||
|  | ||||
|     // Review step and argument hotkeys | ||||
| @ -515,47 +514,6 @@ test.describe('Command bar tests', () => { | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   test( | ||||
|     `Zoom to fit to shared model on web`, | ||||
|     { tag: ['@web'] }, | ||||
|     async ({ page, scene }) => { | ||||
|       if (process.env.TARGET !== 'web') { | ||||
|         // This test is web-only | ||||
|         // TODO: re-enable on CI as part of a new @web test suite | ||||
|         return | ||||
|       } | ||||
|       await test.step(`Prepare and navigate to home page with query params`, async () => { | ||||
|         // a quad in the top left corner of the XZ plane (which is out of the current view) | ||||
|         const code = `sketch001 = startSketchOn(XZ) | ||||
| profile001 = startProfile(sketch001, at = [-484.34, 484.95]) | ||||
|   |> yLine(length = -69.1) | ||||
|   |> xLine(length = 66.84) | ||||
|   |> yLine(length = 71.37) | ||||
|   |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) | ||||
|   |> close() | ||||
| ` | ||||
|         const targetURL = `?create-file=true&name=test&units=mm&code=${encodeURIComponent(btoa(code))}&ask-open-desktop=true` | ||||
|         await page.goto(page.url() + targetURL) | ||||
|         expect(page.url()).toContain(targetURL) | ||||
|       }) | ||||
|  | ||||
|       await test.step(`Submit the command`, async () => { | ||||
|         await page.getByTestId('continue-to-web-app-button').click() | ||||
|  | ||||
|         await scene.connectionEstablished() | ||||
|  | ||||
|         // This makes SystemIOMachineActors.createKCLFile run after EngineStream/firstPlay | ||||
|         await page.waitForTimeout(3000) | ||||
|  | ||||
|         await page.getByTestId('command-bar-submit').click() | ||||
|       }) | ||||
|  | ||||
|       await test.step(`Ensure we created the project and are in the modeling scene`, async () => { | ||||
|         await expectPixelColor(page, [252, 252, 252], { x: 600, y: 260 }, 8) | ||||
|       }) | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test(`Can add and edit a named parameter or constant`, async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|  | ||||
| @ -54,9 +54,7 @@ test( | ||||
|       await page.keyboard.press('Enter') | ||||
|  | ||||
|       // Click the checkbox | ||||
|       const submitButton = page.getByText('Confirm Export') | ||||
|       await expect(submitButton).toBeVisible() | ||||
|       await page.keyboard.press('Enter') | ||||
|       await cmdBar.submit() | ||||
|  | ||||
|       // Expect it to succeed | ||||
|       const errorToastMessage = page.getByText(`Error while exporting`) | ||||
| @ -119,9 +117,7 @@ test( | ||||
|       await page.keyboard.press('Enter') | ||||
|  | ||||
|       // Click the checkbox | ||||
|       const submitButton = page.getByText('Confirm Export') | ||||
|       await expect(submitButton).toBeVisible() | ||||
|       await page.keyboard.press('Enter') | ||||
|       await cmdBar.submit() | ||||
|  | ||||
|       // Look out for the toast message | ||||
|       const exportingToastMessage = page.getByText(`Exporting...`) | ||||
|  | ||||
| @ -229,11 +229,12 @@ test.describe('Feature Tree pane', () => { | ||||
|     const initialCode = `sketch001 = startSketchOn(XZ) | ||||
|       |> circle(center = [0, 0], radius = 5) | ||||
|       renamedExtrude = extrude(sketch001, length = ${initialInput})` | ||||
|     const newConstantName = 'length001' | ||||
|     const expectedCode = `${newConstantName} = 23 | ||||
|     const newParameterName = 'length001' | ||||
|     const expectedCode = `${newParameterName} = 23 | ||||
|     sketch001 = startSketchOn(XZ) | ||||
|       |> circle(center = [0, 0], radius = 5) | ||||
|             renamedExtrude = extrude(sketch001, length = ${newConstantName})` | ||||
|             renamedExtrude = extrude(sketch001, length = ${newParameterName})` | ||||
|     const editedParameterValue = '23 * 2' | ||||
|  | ||||
|     await context.folderSetupFn(async (dir) => { | ||||
|       const testDir = join(dir, 'test-sample') | ||||
| @ -279,7 +280,7 @@ test.describe('Feature Tree pane', () => { | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     await test.step('Add a named constant for distance argument and submit', async () => { | ||||
|     await test.step('Add a parameter for distance argument and submit', async () => { | ||||
|       await expect(cmdBar.currentArgumentInput).toBeVisible() | ||||
|       await cmdBar.variableCheckbox.click() | ||||
|       await cmdBar.progressCmdBar() | ||||
| @ -296,13 +297,43 @@ test.describe('Feature Tree pane', () => { | ||||
|         highlightedCode: '', | ||||
|         diagnostics: [], | ||||
|         activeLines: [ | ||||
|           `renamedExtrude = extrude(sketch001, length = ${newConstantName})`, | ||||
|           `renamedExtrude = extrude(sketch001, length = ${newParameterName})`, | ||||
|         ], | ||||
|       }) | ||||
|       await editor.expectEditor.toContain(expectedCode, { | ||||
|         shouldNormalise: true, | ||||
|       }) | ||||
|     }) | ||||
|  | ||||
|     await test.step('Edit the parameter via the feature tree', async () => { | ||||
|       const parameter = await toolbar.getFeatureTreeOperation('Parameter', 0) | ||||
|       await parameter.dblclick() | ||||
|       await cmdBar.expectState({ | ||||
|         commandName: 'Edit parameter', | ||||
|         currentArgKey: 'value', | ||||
|         currentArgValue: '23', | ||||
|         headerArguments: { | ||||
|           Name: newParameterName, | ||||
|           Value: '23', | ||||
|         }, | ||||
|         stage: 'arguments', | ||||
|         highlightedHeaderArg: 'value', | ||||
|       }) | ||||
|       await cmdBar.argumentInput | ||||
|         .locator('[contenteditable]') | ||||
|         .fill(editedParameterValue) | ||||
|       await cmdBar.progressCmdBar() | ||||
|       await cmdBar.expectState({ | ||||
|         stage: 'review', | ||||
|         commandName: 'Edit parameter', | ||||
|         headerArguments: { | ||||
|           Name: newParameterName, | ||||
|           Value: '46', // Shows calculated result | ||||
|         }, | ||||
|       }) | ||||
|       await cmdBar.progressCmdBar() | ||||
|       await editor.expectEditor.toContain(editedParameterValue) | ||||
|     }) | ||||
|   }) | ||||
|   test(`User can edit an offset plane operation from the feature tree`, async ({ | ||||
|     context, | ||||
|  | ||||
| @ -118,15 +118,11 @@ export class CmdBarFixture { | ||||
|       return | ||||
|     } | ||||
|  | ||||
|     const arrowButton = this.page.getByRole('button', { | ||||
|       name: 'arrow right Continue', | ||||
|     }) | ||||
|     const arrowButton = this.page.getByTestId('command-bar-continue') | ||||
|     if (await arrowButton.isVisible()) { | ||||
|       await arrowButton.click() | ||||
|       await this.continue() | ||||
|     } else { | ||||
|       await this.page | ||||
|         .getByRole('button', { name: 'checkmark Submit command' }) | ||||
|         .click() | ||||
|       await this.submit() | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
| @ -183,14 +183,15 @@ export class EditorFixture { | ||||
|   scrollToText(text: string, placeCursor?: boolean) { | ||||
|     return this.page.evaluate( | ||||
|       (args: { text: string; placeCursor?: boolean }) => { | ||||
|         const editorView = window.editorManager.getEditorView() | ||||
|         // error TS2339: Property 'docView' does not exist on type 'EditorView'. | ||||
|         // Except it does so :shrug: | ||||
|         // @ts-ignore | ||||
|         let index = window.editorManager._editorView?.docView.view.state.doc | ||||
|         const index = editorView?.docView.view.state.doc | ||||
|           .toString() | ||||
|           .indexOf(args.text) | ||||
|         window.editorManager._editorView?.focus() | ||||
|         window.editorManager._editorView?.dispatch({ | ||||
|         editorView?.focus() | ||||
|         editorView?.dispatch({ | ||||
|           selection: window.EditorSelection.create([ | ||||
|             window.EditorSelection.cursor(index), | ||||
|           ]), | ||||
|  | ||||
| @ -5,7 +5,7 @@ import type { | ||||
|   FullResult, | ||||
| } from '@playwright/test/reporter' | ||||
|  | ||||
| class MyAPIReporter implements Reporter { | ||||
| class APIReporter implements Reporter { | ||||
|   private pendingRequests: Promise<void>[] = [] | ||||
|   private allResults: Record<string, any>[] = [] | ||||
|   private blockingResults: Record<string, any>[] = [] | ||||
| @ -32,7 +32,7 @@ class MyAPIReporter implements Reporter { | ||||
|           'X-API-Key': process.env.TAB_API_KEY || '', | ||||
|         }), | ||||
|         body: JSON.stringify({ | ||||
|           project: 'https://github.com/KittyCAD/modeling-app', | ||||
|           project: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}`, | ||||
|           branch: | ||||
|             process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '', | ||||
|           commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '', | ||||
| @ -60,7 +60,7 @@ class MyAPIReporter implements Reporter { | ||||
|  | ||||
|     const payload = { | ||||
|       // Required information | ||||
|       project: 'https://github.com/KittyCAD/modeling-app', | ||||
|       project: `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}`, | ||||
|       suite: process.env.CI_SUITE || 'e2e', | ||||
|       branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '', | ||||
|       commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '', | ||||
| @ -124,4 +124,4 @@ class MyAPIReporter implements Reporter { | ||||
|   } | ||||
| } | ||||
|  | ||||
| export default MyAPIReporter | ||||
| export default APIReporter | ||||
|  | ||||
| @ -1083,14 +1083,13 @@ openSketch = startSketchOn(XY) | ||||
|     cmdBar, | ||||
|   }) => { | ||||
|     // One dumb hardcoded screen pixel value | ||||
|     const testPoint = { x: 700, y: 150 } | ||||
|     const testPoint = { x: 700, y: 200 } | ||||
|     // TODO: replace the testPoint selection with a feature tree click once that's supported #7544 | ||||
|     const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y) | ||||
|     const expectedOutput = `plane001 = offsetPlane(XZ, offset = 5)` | ||||
|  | ||||
|     await homePage.goToModelingScene() | ||||
|     // FIXME: Since there is no KCL code loaded. We need to wait for the scene to load before we continue. | ||||
|     // The engine may not be connected | ||||
|     await page.waitForTimeout(15000) | ||||
|     await scene.settled(cmdBar) | ||||
|  | ||||
|     await test.step(`Look for the blue of the XZ plane`, async () => { | ||||
|       //await scene.expectPixelColor([50, 51, 96], testPoint, 15) // FIXME | ||||
| @ -1829,7 +1828,6 @@ profile002 = startProfile(sketch002, at = [0, 0]) | ||||
|           currentArgKey: 'sketches', | ||||
|           currentArgValue: '', | ||||
|           headerArguments: { | ||||
|             Sectional: '', | ||||
|             Profiles: '', | ||||
|             Path: '', | ||||
|           }, | ||||
| @ -1843,7 +1841,6 @@ profile002 = startProfile(sketch002, at = [0, 0]) | ||||
|           currentArgKey: 'path', | ||||
|           currentArgValue: '', | ||||
|           headerArguments: { | ||||
|             Sectional: '', | ||||
|             Profiles: '1 profile', | ||||
|             Path: '', | ||||
|           }, | ||||
| @ -1856,7 +1853,6 @@ profile002 = startProfile(sketch002, at = [0, 0]) | ||||
|           currentArgKey: 'path', | ||||
|           currentArgValue: '', | ||||
|           headerArguments: { | ||||
|             Sectional: '', | ||||
|             Profiles: '1 profile', | ||||
|             Path: '', | ||||
|           }, | ||||
| @ -1869,7 +1865,6 @@ profile002 = startProfile(sketch002, at = [0, 0]) | ||||
|           headerArguments: { | ||||
|             Profiles: '1 profile', | ||||
|             Path: '1 segment', | ||||
|             Sectional: '', | ||||
|           }, | ||||
|           stage: 'review', | ||||
|         }) | ||||
| @ -1894,6 +1889,9 @@ profile002 = startProfile(sketch002, at = [0, 0]) | ||||
|           0 | ||||
|         ) | ||||
|         await operationButton.dblclick({ button: 'left' }) | ||||
|         await page | ||||
|           .getByRole('button', { name: 'sectional', exact: false }) | ||||
|           .click() | ||||
|         await cmdBar.expectState({ | ||||
|           commandName: 'Sweep', | ||||
|           currentArgKey: 'sectional', | ||||
| @ -1971,7 +1969,6 @@ profile001 = ${circleCode}` | ||||
|         currentArgKey: 'sketches', | ||||
|         currentArgValue: '', | ||||
|         headerArguments: { | ||||
|           Sectional: '', | ||||
|           Profiles: '', | ||||
|           Path: '', | ||||
|         }, | ||||
| @ -1986,7 +1983,6 @@ profile001 = ${circleCode}` | ||||
|         currentArgKey: 'path', | ||||
|         currentArgValue: '', | ||||
|         headerArguments: { | ||||
|           Sectional: '', | ||||
|           Profiles: '1 profile', | ||||
|           Path: '', | ||||
|         }, | ||||
| @ -2000,7 +1996,6 @@ profile001 = ${circleCode}` | ||||
|         currentArgKey: 'path', | ||||
|         currentArgValue: '', | ||||
|         headerArguments: { | ||||
|           Sectional: '', | ||||
|           Profiles: '1 profile', | ||||
|           Path: '', | ||||
|         }, | ||||
| @ -2013,7 +2008,6 @@ profile001 = ${circleCode}` | ||||
|         headerArguments: { | ||||
|           Profiles: '1 profile', | ||||
|           Path: '1 helix', | ||||
|           Sectional: '', | ||||
|         }, | ||||
|         stage: 'review', | ||||
|       }) | ||||
| @ -4734,7 +4728,6 @@ path001 = startProfile(sketch001, at = [0, 0]) | ||||
|         headerArguments: { | ||||
|           Profiles: '', | ||||
|           Path: '', | ||||
|           Sectional: '', | ||||
|         }, | ||||
|         highlightedHeaderArg: 'Profiles', | ||||
|         commandName: 'Sweep', | ||||
| @ -4747,7 +4740,6 @@ path001 = startProfile(sketch001, at = [0, 0]) | ||||
|         headerArguments: { | ||||
|           Profiles: '2 profiles', | ||||
|           Path: '', | ||||
|           Sectional: '', | ||||
|         }, | ||||
|         highlightedHeaderArg: 'path', | ||||
|         commandName: 'Sweep', | ||||
| @ -4760,7 +4752,6 @@ path001 = startProfile(sketch001, at = [0, 0]) | ||||
|         headerArguments: { | ||||
|           Profiles: '2 profiles', | ||||
|           Path: '1 segment', | ||||
|           Sectional: '', | ||||
|         }, | ||||
|         commandName: 'Sweep', | ||||
|       }) | ||||
|  | ||||
| @ -475,6 +475,7 @@ test.describe('Can export from electron app', () => { | ||||
|               }, | ||||
|               tronApp.projectDirName, | ||||
|               page, | ||||
|               cmdBar, | ||||
|               method | ||||
|             ) | ||||
|           ) | ||||
| @ -779,9 +780,6 @@ test.describe(`Project management commands`, () => { | ||||
|       const commandContinueButton = page.getByRole('button', { | ||||
|         name: 'Continue', | ||||
|       }) | ||||
|       const commandSubmitButton = page.getByRole('button', { | ||||
|         name: 'Submit command', | ||||
|       }) | ||||
|       const toastMessage = page.getByText(`Successfully renamed`) | ||||
|  | ||||
|       await test.step(`Setup`, async () => { | ||||
| @ -800,8 +798,7 @@ test.describe(`Project management commands`, () => { | ||||
|         await expect(commandContinueButton).toBeVisible() | ||||
|         await commandContinueButton.click() | ||||
|  | ||||
|         await expect(commandSubmitButton).toBeVisible() | ||||
|         await commandSubmitButton.click() | ||||
|         await cmdBar.submit() | ||||
|  | ||||
|         await expect(toastMessage).toBeVisible() | ||||
|       }) | ||||
| @ -837,9 +834,6 @@ test.describe(`Project management commands`, () => { | ||||
|       }) | ||||
|       const projectNameOption = page.getByRole('option', { name: projectName }) | ||||
|       const commandWarning = page.getByText('Are you sure you want to delete?') | ||||
|       const commandSubmitButton = page.getByRole('button', { | ||||
|         name: 'Submit command', | ||||
|       }) | ||||
|       const toastMessage = page.getByText(`Successfully deleted`) | ||||
|       const noProjectsMessage = page.getByText('No projects found') | ||||
|  | ||||
| @ -859,8 +853,7 @@ test.describe(`Project management commands`, () => { | ||||
|         await projectNameOption.click() | ||||
|  | ||||
|         await expect(commandWarning).toBeVisible() | ||||
|         await expect(commandSubmitButton).toBeVisible() | ||||
|         await commandSubmitButton.click() | ||||
|         await cmdBar.submit() | ||||
|  | ||||
|         await expect(toastMessage).toBeVisible() | ||||
|       }) | ||||
| @ -894,9 +887,6 @@ test.describe(`Project management commands`, () => { | ||||
|       const commandContinueButton = page.getByRole('button', { | ||||
|         name: 'Continue', | ||||
|       }) | ||||
|       const commandSubmitButton = page.getByRole('button', { | ||||
|         name: 'Submit command', | ||||
|       }) | ||||
|       const toastMessage = page.getByText(`Successfully renamed`) | ||||
|  | ||||
|       await test.step(`Setup`, async () => { | ||||
| @ -914,8 +904,7 @@ test.describe(`Project management commands`, () => { | ||||
|         await expect(commandContinueButton).toBeVisible() | ||||
|         await commandContinueButton.click() | ||||
|  | ||||
|         await expect(commandSubmitButton).toBeVisible() | ||||
|         await commandSubmitButton.click() | ||||
|         await cmdBar.submit() | ||||
|  | ||||
|         await expect(toastMessage).toBeVisible() | ||||
|       }) | ||||
| @ -949,9 +938,6 @@ test.describe(`Project management commands`, () => { | ||||
|       }) | ||||
|       const projectNameOption = page.getByRole('option', { name: projectName }) | ||||
|       const commandWarning = page.getByText('Are you sure you want to delete?') | ||||
|       const commandSubmitButton = page.getByRole('button', { | ||||
|         name: 'Submit command', | ||||
|       }) | ||||
|       const toastMessage = page.getByText(`Successfully deleted`) | ||||
|       const noProjectsMessage = page.getByText('No projects found') | ||||
|  | ||||
| @ -967,8 +953,7 @@ test.describe(`Project management commands`, () => { | ||||
|         await projectNameOption.click() | ||||
|  | ||||
|         await expect(commandWarning).toBeVisible() | ||||
|         await expect(commandSubmitButton).toBeVisible() | ||||
|         await commandSubmitButton.click() | ||||
|         await cmdBar.submit() | ||||
|  | ||||
|         await expect(toastMessage).toBeVisible() | ||||
|       }) | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| import path from 'path' | ||||
| import { bracket } from '@e2e/playwright/fixtures/bracket' | ||||
| import type { Page } from '@playwright/test' | ||||
| import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture' | ||||
| import { reportRejection } from '@src/lib/trap' | ||||
| import * as fsp from 'fs/promises' | ||||
|  | ||||
| @ -421,10 +422,7 @@ extrude002 = extrude(profile002, length = 150) | ||||
|       await page.keyboard.press('Enter') | ||||
|  | ||||
|       // Click the checkbox | ||||
|       const submitButton = page.getByText('Confirm Export') | ||||
|       await expect(submitButton).toBeVisible() | ||||
|  | ||||
|       await page.keyboard.press('Enter') | ||||
|       await cmdBar.submit() | ||||
|  | ||||
|       // Find the toast. | ||||
|       // Look out for the toast message | ||||
| @ -461,8 +459,7 @@ extrude002 = extrude(profile002, length = 150) | ||||
|       await page.keyboard.press('Enter') | ||||
|  | ||||
|       // Click the checkbox | ||||
|       await expect(submitButton).toBeVisible() | ||||
|       await page.keyboard.press('Enter') | ||||
|       await cmdBar.submit() | ||||
|  | ||||
|       // Find the toast. | ||||
|       // Look out for the toast message | ||||
| @ -482,6 +479,7 @@ extrude002 = extrude(profile002, length = 150) | ||||
|   test('ensure you CAN export while an export is already going', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|     cmdBar, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|     await test.step('Set up the code and durations', async () => { | ||||
| @ -516,11 +514,11 @@ extrude002 = extrude(profile002, length = 150) | ||||
|     const successToastMessage = page.getByText(`Exported successfully`) | ||||
|  | ||||
|     await test.step('second export', async () => { | ||||
|       await clickExportButton(page) | ||||
|       await clickExportButton(page, cmdBar) | ||||
|  | ||||
|       await expect(exportingToastMessage).toBeVisible() | ||||
|  | ||||
|       await clickExportButton(page) | ||||
|       await clickExportButton(page, cmdBar) | ||||
|  | ||||
|       await test.step('The first export still succeeds', async () => { | ||||
|         await Promise.all([ | ||||
| @ -537,7 +535,7 @@ extrude002 = extrude(profile002, length = 150) | ||||
|  | ||||
|     await test.step('Successful, unblocked export', async () => { | ||||
|       // Try exporting again. | ||||
|       await clickExportButton(page) | ||||
|       await clickExportButton(page, cmdBar) | ||||
|  | ||||
|       // Find the toast. | ||||
|       // Look out for the toast message | ||||
| @ -575,7 +573,7 @@ extrude002 = extrude(profile002, length = 150) | ||||
|         name: 'Projects', | ||||
|       }) | ||||
|       const projectLink = page.getByRole('link', { name: 'bracket' }) | ||||
|       const networkHealthIndicator = page.getByTestId('network-toggle') | ||||
|       const networkHealthIndicator = page.getByTestId(/network-toggle/) | ||||
|  | ||||
|       await test.step('Check the home page', async () => { | ||||
|         await expect(projectsHeading).toBeVisible() | ||||
| @ -880,7 +878,7 @@ s2 = startSketchOn(XY) | ||||
|   }) | ||||
| }) | ||||
|  | ||||
| async function clickExportButton(page: Page) { | ||||
| async function clickExportButton(page: Page, cmdBar: CmdBarFixture) { | ||||
|   await test.step('Running export flow', async () => { | ||||
|     // export the model | ||||
|     const exportButton = page.getByTestId('export-pane-button') | ||||
| @ -896,9 +894,6 @@ async function clickExportButton(page: Page) { | ||||
|     await page.keyboard.press('Enter') | ||||
|  | ||||
|     // Click the checkbox | ||||
|     const submitButton = page.getByText('Confirm Export') | ||||
|     await expect(submitButton).toBeVisible() | ||||
|  | ||||
|     await page.keyboard.press('Enter') | ||||
|     await cmdBar.submit() | ||||
|   }) | ||||
| } | ||||
|  | ||||
| @ -1445,6 +1445,103 @@ solid001 = subtract([extrude001], tools = [extrude002]) | ||||
|     await u.closeDebugPanel() | ||||
|   }) | ||||
|  | ||||
|   test('Can edit a tangentialArc defined by angle and radius', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|     editor, | ||||
|     toolbar, | ||||
|     scene, | ||||
|     cmdBar, | ||||
|   }) => { | ||||
|     const viewportSize = { width: 1500, height: 750 } | ||||
|     await page.setBodyDimensions(viewportSize) | ||||
|  | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `@settings(defaultLengthUnit=in) | ||||
| sketch001 = startSketchOn(XZ) | ||||
|   |> startProfile(at = [-10, -10]) | ||||
|   |> line(end = [20.0, 10.0]) | ||||
|   |> tangentialArc(angle = 60deg, radius=10.0)` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await homePage.goToModelingScene() | ||||
|     await toolbar.waitForFeatureTreeToBeBuilt() | ||||
|     await scene.settled(cmdBar) | ||||
|  | ||||
|     await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick() | ||||
|  | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
|     await page.mouse.move(1200, 139) | ||||
|     await page.mouse.down() | ||||
|     await page.mouse.move(870, 250) | ||||
|     await page.mouse.up() | ||||
|  | ||||
|     await page.waitForTimeout(200) | ||||
|  | ||||
|     await editor.expectEditor.toContain( | ||||
|       `tangentialArc(angle = 234.01deg, radius = 4.08)`, | ||||
|       { shouldNormalise: true } | ||||
|     ) | ||||
|   }) | ||||
|  | ||||
|   test('Can undo with closed code pane', async ({ | ||||
|     page, | ||||
|     homePage, | ||||
|     editor, | ||||
|     toolbar, | ||||
|     scene, | ||||
|     cmdBar, | ||||
|   }) => { | ||||
|     const u = await getUtils(page) | ||||
|  | ||||
|     const viewportSize = { width: 1500, height: 750 } | ||||
|     await page.setBodyDimensions(viewportSize) | ||||
|  | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
|         `@settings(defaultLengthUnit=in) | ||||
| sketch001 = startSketchOn(XZ) | ||||
|   |> startProfile(at = [-10, -10]) | ||||
|   |> line(end = [20.0, 10.0]) | ||||
|   |> tangentialArc(end = [5.49, 8.37])` | ||||
|       ) | ||||
|     }) | ||||
|  | ||||
|     await homePage.goToModelingScene() | ||||
|     await toolbar.waitForFeatureTreeToBeBuilt() | ||||
|     await scene.settled(cmdBar) | ||||
|  | ||||
|     await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick() | ||||
|  | ||||
|     await page.waitForTimeout(1000) | ||||
|  | ||||
|     await page.mouse.move(1200, 139) | ||||
|     await page.mouse.down() | ||||
|     await page.mouse.move(870, 250) | ||||
|     await page.mouse.up() | ||||
|  | ||||
|     await editor.expectEditor.toContain(`tangentialArc(end=[-5.85,4.32])`, { | ||||
|       shouldNormalise: true, | ||||
|     }) | ||||
|  | ||||
|     await u.closeKclCodePanel() | ||||
|  | ||||
|     // Undo the last change | ||||
|     await page.keyboard.down('Control') | ||||
|     await page.keyboard.press('KeyZ') | ||||
|     await page.keyboard.up('Control') | ||||
|  | ||||
|     await u.openKclCodePanel() | ||||
|     await editor.expectEditor.toContain(`tangentialArc(end = [5.49, 8.37])`, { | ||||
|       shouldNormalise: true, | ||||
|     }) | ||||
|   }) | ||||
|  | ||||
|   test('Can delete a single segment line with keyboard', async ({ | ||||
|     page, | ||||
|     scene, | ||||
|  | ||||
| @ -798,7 +798,7 @@ test('theme persists', async ({ page, context, homePage }) => { | ||||
|  | ||||
|   await page.getByTestId('settings-close-button').click() | ||||
|  | ||||
|   const networkToggle = page.getByTestId('network-toggle') | ||||
|   const networkToggle = page.getByTestId(/network-toggle/) | ||||
|  | ||||
|   // simulate network down | ||||
|   await u.emulateNetworkConditions({ | ||||
|  | ||||
| Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 51 KiB | 
| Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 58 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 51 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 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: 48 KiB After Width: | Height: | Size: 48 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: 31 KiB After Width: | Height: | Size: 33 KiB | 
| Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 53 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 48 KiB | 
| Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 61 KiB | 
| Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 135 KiB | 
| Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 118 KiB | 
| Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 135 KiB | 
| Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 68 KiB | 
| Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 71 KiB | 
| Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 67 KiB | 
| Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 69 KiB | 
| Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 69 KiB | 
| Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB | 
							
								
								
									
										117
									
								
								e2e/playwright/temporary-workspace.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,117 @@ | ||||
| import { expect, test } from '@e2e/playwright/zoo-test' | ||||
| import { stringToBase64 } from '@src/lib/base64' | ||||
|  | ||||
| test.describe('Temporary workspace', () => { | ||||
|   test( | ||||
|     'Opening a share link creates a temporary environment that is not saved', | ||||
|     { tag: ['@web'] }, | ||||
|     async ({ page, editor, scene, cmdBar, homePage }) => { | ||||
|       await test.step('Pre-condition: editor is empty', async () => { | ||||
|         await homePage.goToModelingScene() | ||||
|         await scene.settled(cmdBar) | ||||
|         await editor.expectEditor.toContain('') | ||||
|       }) | ||||
|  | ||||
|       await test.step('Go to share link, check new content present, make a change', async () => { | ||||
|         const code = `sketch001 = startSketchOn(XY) | ||||
|   profile001 = startProfile(sketch001, at = [-124.89, -186.4]) | ||||
|     |> line(end = [391.31, 444.04]) | ||||
|     |> line(end = [96.21, -493.07]) | ||||
|     |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) | ||||
|     |> close() | ||||
|   extrude001 = extrude(profile001, length = 5) | ||||
| ` | ||||
|  | ||||
|         const codeQueryParam = encodeURIComponent(stringToBase64(code)) | ||||
|         const targetURL = `?create-file=true&browser=test&code=${codeQueryParam}&ask-open-desktop=true` | ||||
|         await page.goto(page.url() + targetURL) | ||||
|         await expect.poll(() => page.url()).toContain(targetURL) | ||||
|         const button = page.getByRole('button', { name: 'Continue to web app' }) | ||||
|         await button.click() | ||||
|  | ||||
|         await editor.expectEditor.toContain(code, { shouldNormalise: true }) | ||||
|         await editor.scrollToText('-124.89', true) | ||||
|         await page.keyboard.press('9') | ||||
|         await page.keyboard.press('9') | ||||
|       }) | ||||
|  | ||||
|       await test.step('Post-condition: empty editor once again (original state)', async () => { | ||||
|         await homePage.goToModelingScene() | ||||
|         await scene.settled(cmdBar) | ||||
|         const code = await page.evaluate(() => | ||||
|           window.localStorage.getItem('persistCode') | ||||
|         ) | ||||
|         await expect(code).toContain('') | ||||
|       }) | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test( | ||||
|     'Opening a sample link creates a temporary environment that is not saved', | ||||
|     { tag: ['@web'] }, | ||||
|     async ({ page, editor, scene, cmdBar, homePage }) => { | ||||
|       await test.step('Pre-condition: editor is empty', async () => { | ||||
|         await homePage.goToModelingScene() | ||||
|         await scene.settled(cmdBar) | ||||
|         await editor.expectEditor.toContain('') | ||||
|       }) | ||||
|  | ||||
|       await test.step('Load sample, make an edit', async () => { | ||||
|         await page.goto( | ||||
|           `${page.url()}/?cmd=add-kcl-file-to-project&groupId=application&projectName=browser&source=kcl-samples&sample=brake-rotor/main.kcl` | ||||
|         ) | ||||
|  | ||||
|         await editor.scrollToText('114.3', true) | ||||
|         await page.keyboard.press('9') | ||||
|         await page.keyboard.press('9') | ||||
|       }) | ||||
|  | ||||
|       await test.step('Post-condition: empty editor once again (original state)', async () => { | ||||
|         await homePage.goToModelingScene() | ||||
|         await scene.settled(cmdBar) | ||||
|         const code = await page.evaluate(() => | ||||
|           window.localStorage.getItem('persistCode') | ||||
|         ) | ||||
|         await expect(code).toContain('') | ||||
|       }) | ||||
|     } | ||||
|   ) | ||||
|  | ||||
|   test( | ||||
|     'Hitting save will save the temporary workspace', | ||||
|     { tag: ['@web'] }, | ||||
|     async ({ page, editor, scene, cmdBar, homePage }) => { | ||||
|       const buttonSaveTemporaryWorkspace = page.getByTestId('tws-save') | ||||
|  | ||||
|       await test.step('Pre-condition: editor is empty', async () => { | ||||
|         await homePage.goToModelingScene() | ||||
|         await scene.settled(cmdBar) | ||||
|         await editor.expectEditor.toContain('') | ||||
|       }) | ||||
|  | ||||
|       await test.step('Load sample, make an edit, *save*', async () => { | ||||
|         await page.goto( | ||||
|           `${page.url()}/?cmd=add-kcl-file-to-project&groupId=application&projectName=browser&source=kcl-samples&sample=brake-rotor/main.kcl` | ||||
|         ) | ||||
|         await homePage.goToModelingScene() | ||||
|         await scene.settled(cmdBar) | ||||
|  | ||||
|         await editor.scrollToText('114.3') | ||||
|         await editor.replaceCode('114.3', '999.9133') | ||||
|         await editor.expectEditor.toContain('999.9133') | ||||
|  | ||||
|         await buttonSaveTemporaryWorkspace.click() | ||||
|         await expect(buttonSaveTemporaryWorkspace).not.toBeVisible() | ||||
|  | ||||
|         await editor.expectEditor.toContain('999.9133') | ||||
|       }) | ||||
|  | ||||
|       await test.step('Post-condition: has the edits in localStorage', async () => { | ||||
|         const code = await page.evaluate(() => | ||||
|           window.localStorage.getItem('persistCode') | ||||
|         ) | ||||
|         await expect(code).toContain('999.9133') | ||||
|       }) | ||||
|     } | ||||
|   ) | ||||
| }) | ||||
| @ -24,16 +24,15 @@ test.describe('Test network related behaviors', () => { | ||||
|  | ||||
|       await homePage.goToModelingScene() | ||||
|  | ||||
|       const networkToggle = page.getByTestId('network-toggle') | ||||
|       const networkToggle = page.getByTestId(/network-toggle/) | ||||
|  | ||||
|       // This is how we wait until the stream is online | ||||
|       await expect( | ||||
|         page.getByRole('button', { name: 'Start Sketch' }) | ||||
|       ).not.toBeDisabled({ timeout: 15000 }) | ||||
|  | ||||
|       const networkWidget = page.locator('[data-testid="network-toggle"]') | ||||
|       await expect(networkWidget).toBeVisible() | ||||
|       await networkWidget.hover() | ||||
|       await expect(networkToggle).toBeVisible() | ||||
|       await networkToggle.hover() | ||||
|  | ||||
|       const networkPopover = page.locator('[data-testid="network-popover"]') | ||||
|       await expect(networkPopover).not.toBeVisible() | ||||
| @ -44,7 +43,7 @@ test.describe('Test network related behaviors', () => { | ||||
|       ).toBeVisible() | ||||
|  | ||||
|       // Click the network widget | ||||
|       await networkWidget.click() | ||||
|       await networkToggle.click() | ||||
|  | ||||
|       // Check the modal opened. | ||||
|       await expect(networkPopover).toBeVisible() | ||||
| @ -65,8 +64,8 @@ test.describe('Test network related behaviors', () => { | ||||
|       // Expect the network to be down | ||||
|       await expect(networkToggle).toContainText('Network health (Offline)') | ||||
|  | ||||
|       // Click the network widget | ||||
|       await networkWidget.click() | ||||
|       // Click the network toggle | ||||
|       await networkToggle.click() | ||||
|  | ||||
|       // Check the modal opened. | ||||
|       await expect(networkPopover).toBeVisible() | ||||
| @ -99,7 +98,7 @@ test.describe('Test network related behaviors', () => { | ||||
|     'Engine disconnect & reconnect in sketch mode', | ||||
|     { tag: '@skipLocalEngine' }, | ||||
|     async ({ page, homePage, toolbar, scene, cmdBar }) => { | ||||
|       const networkToggle = page.getByTestId('network-toggle') | ||||
|       const networkToggle = page.getByTestId(/network-toggle/) | ||||
|       const networkToggleConnectedText = page.getByText( | ||||
|         'Network health (Strong)' | ||||
|       ) | ||||
| @ -286,7 +285,7 @@ profile001 = startProfile(sketch001, at = [12.34, -12.34]) | ||||
|     'Paused stream freezes view frame, unpause reconnect is seamless to user', | ||||
|     { tag: ['@desktop', '@skipLocalEngine'] }, | ||||
|     async ({ page, homePage, scene, cmdBar, toolbar, tronApp }) => { | ||||
|       const networkToggle = page.getByTestId('network-toggle') | ||||
|       const networkToggle = page.getByTestId(/network-toggle/) | ||||
|       const networkToggleConnectedText = page.getByText( | ||||
|         'Network health (Strong)' | ||||
|       ) | ||||
|  | ||||
| @ -22,6 +22,7 @@ export const token = process.env.token || '' | ||||
| import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration' | ||||
|  | ||||
| import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup' | ||||
| import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture' | ||||
| import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist' | ||||
| import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates' | ||||
| import { test } from '@e2e/playwright/zoo-test' | ||||
| @ -37,7 +38,7 @@ export const headerMasks = (page: Page) => [ | ||||
| ] | ||||
|  | ||||
| export const lowerRightMasks = (page: Page) => [ | ||||
|   page.getByTestId('network-toggle'), | ||||
|   page.getByTestId(/network-toggle/), | ||||
|   page.getByTestId('billing-remaining-bar'), | ||||
| ] | ||||
|  | ||||
| @ -158,10 +159,10 @@ async function openKclCodePanel(page: Page) { | ||||
|   await page.evaluate(() => { | ||||
|     // editorManager is available on the window object. | ||||
|     //@ts-ignore this is in an entirely different context that tsc can't see. | ||||
|     editorManager._editorView.dispatch({ | ||||
|     editorManager.getEditorView().dispatch({ | ||||
|       selection: { | ||||
|         //@ts-ignore this is in an entirely different context that tsc can't see. | ||||
|         anchor: editorManager._editorView.docView.length, | ||||
|         anchor: editorManager.getEditorView().docView.length, | ||||
|       }, | ||||
|       scrollIntoView: true, | ||||
|     }) | ||||
| @ -737,6 +738,7 @@ export const doExport = async ( | ||||
|   output: Models['OutputFormat3d_type'], | ||||
|   rootDir: string, | ||||
|   page: Page, | ||||
|   cmdBar: CmdBarFixture, | ||||
|   exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown' | ||||
| ): Promise<Paths> => { | ||||
|   if (exportFrom === 'dropdown') { | ||||
| @ -780,9 +782,7 @@ export const doExport = async ( | ||||
|       .click() | ||||
|     await page.locator('#arg-form').waitFor({ state: 'detached' }) | ||||
|   } | ||||
|   await expect(page.getByText('Confirm Export')).toBeVisible() | ||||
|  | ||||
|   await page.getByRole('button', { name: 'Submit command' }).click() | ||||
|   await cmdBar.submit() | ||||
|  | ||||
|   await expect(page.getByText('Exported successfully')).toBeVisible() | ||||
|  | ||||
|  | ||||
| @ -10,7 +10,7 @@ import { | ||||
| import { expect, test } from '@e2e/playwright/zoo-test' | ||||
|  | ||||
| test.describe('Testing constraints', () => { | ||||
|   test('Can constrain line length', async ({ page, homePage }) => { | ||||
|   test('Can constrain line length', async ({ page, homePage, cmdBar }) => { | ||||
|     await page.addInitScript(async () => { | ||||
|       localStorage.setItem( | ||||
|         'persistCode', | ||||
| @ -50,11 +50,7 @@ test.describe('Testing constraints', () => { | ||||
|     await page.waitForTimeout(100) | ||||
|     await page.getByTestId('constraint-length').click() | ||||
|     await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('20') | ||||
|     await page | ||||
|       .getByRole('button', { | ||||
|         name: 'arrow right Continue', | ||||
|       }) | ||||
|       .click() | ||||
|     await cmdBar.continue() | ||||
|  | ||||
|     await expect(page.locator('.cm-content')).toHaveText( | ||||
|       `length001 = 20sketch001 = startSketchOn(XY)  |> startProfile(at = [-10, -10])  |> line(end = [20, 0])  |> angledLine(angle = 90, length = length001)  |> xLine(length = -20)` | ||||
| @ -681,9 +677,6 @@ test.describe('Testing constraints', () => { | ||||
|           .getByRole('textbox') | ||||
|         const cmdBarKclVariableNameInput = | ||||
|           page.getByPlaceholder('Variable name') | ||||
|         const cmdBarSubmitButton = page.getByRole('button', { | ||||
|           name: 'arrow right Continue', | ||||
|         }) | ||||
|  | ||||
|         await page.addInitScript(async () => { | ||||
|           localStorage.setItem( | ||||
| @ -736,7 +729,7 @@ part002 = startSketchOn(XZ) | ||||
|         await page.waitForTimeout(500) | ||||
|         const [ang, len] = value.split(', ') | ||||
|         const changedCode = `|> angledLine(angle = ${ang}, length = ${len})` | ||||
|         await cmdBarSubmitButton.click() | ||||
|         await cmdBar.continue() | ||||
|         await expect(page.locator('.cm-content')).toContainText(changedCode) | ||||
|  | ||||
|         // checking active assures the cursor is where it should be | ||||
| @ -1101,11 +1094,7 @@ part002 = startSketchOn(XZ) | ||||
|     await page.waitForTimeout(500) | ||||
|  | ||||
|     await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('10') | ||||
|     await page | ||||
|       .getByRole('button', { | ||||
|         name: 'arrow right Continue', | ||||
|       }) | ||||
|       .click() | ||||
|     await cmdBar.continue() | ||||
|  | ||||
|     await pollEditorLinesSelectedLength(page, 1) | ||||
|     activeLinesContent = await page.locator('.cm-activeLine').all() | ||||
|  | ||||
| @ -8,37 +8,37 @@ test.describe('Testing Gizmo', () => { | ||||
|   const cases = [ | ||||
|     { | ||||
|       testDescription: 'top view', | ||||
|       clickPosition: { x: 951, y: 385 }, | ||||
|       clickPosition: { x: 951, y: 402 }, | ||||
|       expectedCameraPosition: { x: 800, y: -152, z: 4886.02 }, | ||||
|       expectedCameraTarget: { x: 800, y: -152, z: 26 }, | ||||
|     }, | ||||
|     { | ||||
|       testDescription: 'bottom view', | ||||
|       clickPosition: { x: 951, y: 429 }, | ||||
|       clickPosition: { x: 951, y: 449 }, | ||||
|       expectedCameraPosition: { x: 800, y: -152, z: -4834.02 }, | ||||
|       expectedCameraTarget: { x: 800, y: -152, z: 26 }, | ||||
|     }, | ||||
|     { | ||||
|       testDescription: 'right view', | ||||
|       clickPosition: { x: 929, y: 417 }, | ||||
|       clickPosition: { x: 929, y: 435 }, | ||||
|       expectedCameraPosition: { x: 5660.02, y: -152, z: 26 }, | ||||
|       expectedCameraTarget: { x: 800, y: -152, z: 26 }, | ||||
|     }, | ||||
|     { | ||||
|       testDescription: 'left view', | ||||
|       clickPosition: { x: 974, y: 397 }, | ||||
|       clickPosition: { x: 974, y: 417 }, | ||||
|       expectedCameraPosition: { x: -4060.02, y: -152, z: 26 }, | ||||
|       expectedCameraTarget: { x: 800, y: -152, z: 26 }, | ||||
|     }, | ||||
|     { | ||||
|       testDescription: 'back view', | ||||
|       clickPosition: { x: 967, y: 421 }, | ||||
|       clickPosition: { x: 967, y: 441 }, | ||||
|       expectedCameraPosition: { x: 800, y: 4708.02, z: 26 }, | ||||
|       expectedCameraTarget: { x: 800, y: -152, z: 26 }, | ||||
|     }, | ||||
|     { | ||||
|       testDescription: 'front view', | ||||
|       clickPosition: { x: 935, y: 393 }, | ||||
|       clickPosition: { x: 935, y: 413 }, | ||||
|       expectedCameraPosition: { x: 800, y: -5012.02, z: 26 }, | ||||
|       expectedCameraTarget: { x: 800, y: -152, z: 26 }, | ||||
|     }, | ||||
|  | ||||
| @ -21,7 +21,7 @@ test.describe('Testing loading external models', () => { | ||||
|   // We have no more web tests | ||||
|   test.fail( | ||||
|     'Web: should overwrite current code, cannot create new file', | ||||
|     async ({ editor, context, page, homePage }) => { | ||||
|     async ({ editor, context, page, homePage, cmdBar }) => { | ||||
|       const u = await getUtils(page) | ||||
|       await test.step(`Test setup`, async () => { | ||||
|         await context.addInitScript((code) => { | ||||
| @ -52,9 +52,6 @@ test.describe('Testing loading external models', () => { | ||||
|           name, | ||||
|         }) | ||||
|       const warningText = page.getByText('Overwrite current file with sample?') | ||||
|       const confirmButton = page.getByRole('button', { | ||||
|         name: 'Submit command', | ||||
|       }) | ||||
|  | ||||
|       await test.step(`Precondition: check the initial code`, async () => { | ||||
|         await u.openKclCodePanel() | ||||
| @ -70,7 +67,7 @@ test.describe('Testing loading external models', () => { | ||||
|         await expect(commandMethodOption('Create new file')).not.toBeVisible() | ||||
|         await commandMethodOption('Overwrite').click() | ||||
|         await expect(warningText).toBeVisible() | ||||
|         await confirmButton.click() | ||||
|         await cmdBar.submit() | ||||
|  | ||||
|         await editor.expectEditor.toContain('// ' + newSample.title) | ||||
|       }) | ||||
|  | ||||
| @ -3,6 +3,7 @@ import type { LineInputsType } from '@src/lang/std/sketchcombos' | ||||
| import { uuidv4 } from '@src/lib/utils' | ||||
|  | ||||
| import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture' | ||||
| import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture' | ||||
| import { deg, getUtils, wiggleMove } from '@e2e/playwright/test-utils' | ||||
| import { expect, test } from '@e2e/playwright/zoo-test' | ||||
|  | ||||
| @ -18,7 +19,7 @@ test.describe('Testing segment overlays', () => { | ||||
|      * @param {number} options.steps - The number of steps to perform | ||||
|      */ | ||||
|     const _clickConstrained = | ||||
|       (page: Page, editor: EditorFixture) => | ||||
|       (page: Page, editor: EditorFixture, cmdBar: CmdBarFixture) => | ||||
|       async ({ | ||||
|         hoverPos, | ||||
|         constraintType, | ||||
| @ -93,11 +94,7 @@ test.describe('Testing segment overlays', () => { | ||||
|           page.getByTestId('cmd-bar-arg-value').getByRole('textbox') | ||||
|         ).toBeFocused() | ||||
|         await page.waitForTimeout(500) | ||||
|         await page | ||||
|           .getByRole('button', { | ||||
|             name: 'arrow right Continue', | ||||
|           }) | ||||
|           .click() | ||||
|         await cmdBar.continue() | ||||
|         await editor.expectEditor.toContain(expectFinal, { | ||||
|           shouldNormalise: true, | ||||
|         }) | ||||
| @ -113,7 +110,7 @@ test.describe('Testing segment overlays', () => { | ||||
|      * @param {number} options.steps - The number of steps to perform | ||||
|      */ | ||||
|     const _clickUnconstrained = | ||||
|       (page: Page, editor: EditorFixture) => | ||||
|       (page: Page, editor: EditorFixture, cmdBar: CmdBarFixture) => | ||||
|       async ({ | ||||
|         hoverPos, | ||||
|         constraintType, | ||||
| @ -163,11 +160,7 @@ test.describe('Testing segment overlays', () => { | ||||
|           page.getByTestId('cmd-bar-arg-value').getByRole('textbox') | ||||
|         ).toBeFocused() | ||||
|         await page.waitForTimeout(500) | ||||
|         await page | ||||
|           .getByRole('button', { | ||||
|             name: 'arrow right Continue', | ||||
|           }) | ||||
|           .click() | ||||
|         await cmdBar.continue() | ||||
|         await editor.expectEditor.toContain(expectAfterUnconstrained, { | ||||
|           shouldNormalise: true, | ||||
|         }) | ||||
| @ -239,8 +232,8 @@ test.describe('Testing segment overlays', () => { | ||||
|  | ||||
|       await expect(page.getByTestId('segment-overlay')).toHaveCount(14) | ||||
|  | ||||
|       const clickUnconstrained = _clickUnconstrained(page, editor) | ||||
|       const clickConstrained = _clickConstrained(page, editor) | ||||
|       const clickUnconstrained = _clickUnconstrained(page, editor, cmdBar) | ||||
|       const clickConstrained = _clickConstrained(page, editor, cmdBar) | ||||
|  | ||||
|       await u.openAndClearDebugPanel() | ||||
|       await u.sendCustomCmd({ | ||||
| @ -664,7 +657,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38) | ||||
|       await expect( | ||||
|         page.getByTestId('cmd-bar-arg-value').getByRole('textbox') | ||||
|       ).toBeFocused() | ||||
|       await page.getByRole('button', { name: 'arrow right Continue' }).click() | ||||
|       await cmdBar.continue() | ||||
|  | ||||
|       // Verify the X constraint was added | ||||
|       await editor.expectEditor.toContain('center = [xAbs001, 0]', { | ||||
| @ -682,7 +675,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38) | ||||
|       await expect( | ||||
|         page.getByTestId('cmd-bar-arg-value').getByRole('textbox') | ||||
|       ).toBeFocused() | ||||
|       await page.getByRole('button', { name: 'arrow right Continue' }).click() | ||||
|       await cmdBar.continue() | ||||
|  | ||||
|       // Verify the Y constraint was added | ||||
|       await editor.expectEditor.toContain('center = [xAbs001, yAbs001]', { | ||||
| @ -700,7 +693,7 @@ profile002 = circle(sketch001, center = [345, 0], radius = 238.38) | ||||
|       await expect( | ||||
|         page.getByTestId('cmd-bar-arg-value').getByRole('textbox') | ||||
|       ).toBeFocused() | ||||
|       await page.getByRole('button', { name: 'arrow right Continue' }).click() | ||||
|       await cmdBar.continue() | ||||
|  | ||||
|       // Verify all constraints were added | ||||
|       await editor.expectEditor.toContain( | ||||
| @ -887,7 +880,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16]) | ||||
|     await expect( | ||||
|       page.getByTestId('cmd-bar-arg-value').getByRole('textbox') | ||||
|     ).toBeFocused() | ||||
|     await page.getByRole('button', { name: 'arrow right Continue' }).click() | ||||
|     await cmdBar.continue() | ||||
|  | ||||
|     // Verify the constraint was added | ||||
|     await editor.expectEditor.toContain( | ||||
| @ -910,7 +903,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16]) | ||||
|     await expect( | ||||
|       page.getByTestId('cmd-bar-arg-value').getByRole('textbox') | ||||
|     ).toBeFocused() | ||||
|     await page.getByRole('button', { name: 'arrow right Continue' }).click() | ||||
|     await cmdBar.continue() | ||||
|  | ||||
|     // Verify both constraints were added | ||||
|     await editor.expectEditor.toContain( | ||||
| @ -935,7 +928,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16]) | ||||
|     await expect( | ||||
|       page.getByTestId('cmd-bar-arg-value').getByRole('textbox') | ||||
|     ).toBeFocused() | ||||
|     await page.getByRole('button', { name: 'arrow right Continue' }).click() | ||||
|     await cmdBar.continue() | ||||
|  | ||||
|     // Verify the constraint was added | ||||
|     await editor.expectEditor.toContain('endAbsolute = [xAbs002, 84.07]', { | ||||
| @ -955,7 +948,7 @@ profile003 = startProfile(sketch001, at = [64.39, 35.16]) | ||||
|     await expect( | ||||
|       page.getByTestId('cmd-bar-arg-value').getByRole('textbox') | ||||
|     ).toBeFocused() | ||||
|     await page.getByRole('button', { name: 'arrow right Continue' }).click() | ||||
|     await cmdBar.continue() | ||||
|  | ||||
|     // Verify all constraints were added | ||||
|     await editor.expectEditor.toContain( | ||||
|  | ||||
| @ -32,7 +32,7 @@ test('Units menu', async ({ page, homePage }) => { | ||||
| test( | ||||
|   'Successful export shows a success toast', | ||||
|   { tag: '@skipLocalEngine' }, | ||||
|   async ({ page, homePage, tronApp }) => { | ||||
|   async ({ page, homePage, cmdBar, tronApp }) => { | ||||
|     // FYI this test doesn't work with only engine running locally | ||||
|     // And you will need to have the KittyCAD CLI installed | ||||
|     const u = await getUtils(page) | ||||
| @ -94,7 +94,8 @@ part001 = startSketchOn(-XZ) | ||||
|         presentation: 'pretty', | ||||
|       }, | ||||
|       tronApp?.projectDirName, | ||||
|       page | ||||
|       page, | ||||
|       cmdBar | ||||
|     ) | ||||
|   } | ||||
| ) | ||||
| @ -254,6 +255,7 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn | ||||
| test('Basic default modeling and sketch hotkeys work', async ({ | ||||
|   page, | ||||
|   homePage, | ||||
|   cmdBar, | ||||
| }) => { | ||||
|   const u = await getUtils(page) | ||||
|   await test.step(`Set up test`, async () => { | ||||
| @ -397,11 +399,8 @@ test('Basic default modeling and sketch hotkeys work', async ({ | ||||
|     await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible({ | ||||
|       timeout: 20_000, | ||||
|     }) | ||||
|     await page.getByRole('button', { name: 'Continue' }).click() | ||||
|     await expect( | ||||
|       page.getByRole('button', { name: 'Submit command' }) | ||||
|     ).toBeVisible() | ||||
|     await page.getByRole('button', { name: 'Submit command' }).click() | ||||
|     await cmdBar.continue() | ||||
|     await cmdBar.submit() | ||||
|     await expect(page.locator('.cm-content')).toContainText('extrude(') | ||||
|   }) | ||||
|  | ||||
| @ -575,8 +574,7 @@ profile001 = startProfile(sketch002, at = [-12.34, 12.34]) | ||||
|  | ||||
|   await cmdBar.progressCmdBar() | ||||
|   await cmdBar.progressCmdBar() | ||||
|   await expect(page.getByText('Confirm Extrude')).toBeVisible() | ||||
|   await cmdBar.progressCmdBar() | ||||
|   await cmdBar.submit() | ||||
|  | ||||
|   const result2 = result.genNext` | ||||
| const sketch002 = extrude(sketch002, length = ${[5, 5]} + 7)` | ||||
|  | ||||
| @ -111,7 +111,8 @@ commaSep1NoTrailingComma<term> { term ("," term)* } | ||||
|  | ||||
|   PipeSubstitution { "%" } | ||||
|  | ||||
|   identifier { (@asciiLetter | "_") (@asciiLetter | @digit | "_")* } | ||||
|   // Includes non-whitespace unicode characters. | ||||
|   identifier { $[a-zA-Z_\u{a1}-\u{167f}\u{1681}-\u{1fff}\u{200e}-\u{2027}\u{202a}-\u{202e}\u{2030}-\u{205e}\u{2061}-\u{2fff}\u{3001}-\u{fefe}\u{ff00}-\u{10ffff}] $[a-zA-Z0-9_\u{a1}-\u{167f}\u{1681}-\u{1fff}\u{200e}-\u{2027}\u{202a}-\u{202e}\u{2030}-\u{205e}\u{2061}-\u{2fff}\u{3001}-\u{fefe}\u{ff00}-\u{10ffff}]* } | ||||
|   AnnotationName { "@" identifier? } | ||||
|   PropertyName { identifier } | ||||
|   TagDeclarator { "$" identifier } | ||||
|  | ||||
| @ -42,15 +42,15 @@ fn helicalGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) { | ||||
|     helicalGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight)) | ||||
|       |> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2)) | ||||
|       |> involuteCircular( | ||||
|            startRadius = baseDiameter / 2, | ||||
|            endRadius = tipDiameter / 2, | ||||
|            startDiameter = baseDiameter, | ||||
|            endDiameter = tipDiameter, | ||||
|            angle = helixCalc, | ||||
|            tag = $seg01, | ||||
|          ) | ||||
|       |> line(endAbsolute = polar(angle = 160 / nTeeth + helixCalc, length = tipDiameter / 2)) | ||||
|       |> involuteCircular( | ||||
|            startRadius = baseDiameter / 2, | ||||
|            endRadius = tipDiameter / 2, | ||||
|            startDiameter = baseDiameter, | ||||
|            endDiameter = tipDiameter, | ||||
|            angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)), | ||||
|            reverse = true, | ||||
|          ) | ||||
|  | ||||
							
								
								
									
										553
									
								
								rust/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						| @ -29,8 +29,8 @@ debug = "line-tables-only" | ||||
| [workspace.dependencies] | ||||
| async-trait = "0.1.88" | ||||
| anyhow = { version = "1" } | ||||
| bson = { version = "2.13.0", features = ["uuid-1", "chrono"] } | ||||
| clap = { version = "4.5.36", features = ["derive"] } | ||||
| bson = { version = "2.15.0", features = ["uuid-1", "chrono"] } | ||||
| clap = { version = "4.5.40", features = ["derive"] } | ||||
| console_error_panic_hook = "0.1.7" | ||||
| dashmap = { version = "6.1.0" } | ||||
| http = "1" | ||||
| @ -38,8 +38,8 @@ indexmap = "2.9.0" | ||||
| kittycad = { version = "0.3.37", default-features = false, features = ["js", "requests"] } | ||||
| kittycad-modeling-cmds = { version = "0.2.123", features = ["ts-rs", "websocket"] } | ||||
| lazy_static = "1.5.0" | ||||
| miette = "7.5.0" | ||||
| pyo3 = { version = "0.24.1" } | ||||
| miette = "7.6.0" | ||||
| pyo3 = { version = "0.24.2" } | ||||
| serde = { version = "1", features = ["derive"] } | ||||
| serde_json = { version = "1" } | ||||
| slog = "2.7.0" | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
|  | ||||
| [package] | ||||
| name = "kcl-bumper" | ||||
| version = "0.1.81" | ||||
| version = "0.1.82" | ||||
| edition = "2021" | ||||
| repository = "https://github.com/KittyCAD/modeling-api" | ||||
| rust-version = "1.76" | ||||
| @ -19,7 +19,7 @@ anyhow = { workspace = true } | ||||
| clap = { workspace = true, features = ["derive"] } | ||||
| semver = "1.0.25" | ||||
| serde = { workspace = true } | ||||
| toml_edit = "0.22.16" | ||||
| toml_edit = "0.22.26" | ||||
|  | ||||
| [lints] | ||||
| workspace = true | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| [package] | ||||
| name = "kcl-derive-docs" | ||||
| description = "A tool for generating documentation from Rust derive macros" | ||||
| version = "0.1.81" | ||||
| version = "0.1.82" | ||||
| edition = "2021" | ||||
| license = "MIT" | ||||
| repository = "https://github.com/KittyCAD/modeling-app" | ||||
| @ -14,7 +14,7 @@ bench = false | ||||
| [dependencies] | ||||
| proc-macro2 = "1" | ||||
| quote = "1" | ||||
| syn = { version = "2.0.96", features = ["full"] } | ||||
| syn = { version = "2.0.103", features = ["full"] } | ||||
|  | ||||
| [lints] | ||||
| workspace = true | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| [package] | ||||
| name = "kcl-directory-test-macro" | ||||
| description = "A tool for generating tests from a directory of kcl files" | ||||
| version = "0.1.81" | ||||
| version = "0.1.82" | ||||
| edition = "2021" | ||||
| license = "MIT" | ||||
| repository = "https://github.com/KittyCAD/modeling-app" | ||||
| @ -14,7 +14,7 @@ bench = false | ||||
| convert_case = "0.8.0" | ||||
| proc-macro2 = "1" | ||||
| quote = "1" | ||||
| syn = { version = "2.0.96", features = ["full"] } | ||||
| syn = { version = "2.0.103", features = ["full"] } | ||||
|  | ||||
| [lints] | ||||
| workspace = true | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| [package] | ||||
| name = "kcl-language-server-release" | ||||
| version = "0.1.81" | ||||
| version = "0.1.82" | ||||
| edition = "2021" | ||||
| authors = ["KittyCAD Inc <kcl@kittycad.io>"] | ||||
| publish = false | ||||
| @ -14,7 +14,7 @@ bench = false | ||||
| [dependencies] | ||||
| anyhow = { workspace = true } | ||||
| clap = { workspace = true, features = ["cargo", "derive", "env", "unicode"] } | ||||
| flate2 = "1.1.1" | ||||
| flate2 = "1.1.2" | ||||
| lazy_static = { workspace = true } | ||||
| log = { version = "0.4.27", features = ["serde"] } | ||||
| slog = { workspace = true } | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
| name = "kcl-language-server" | ||||
| description = "A language server for KCL." | ||||
| authors = ["KittyCAD Inc <kcl@kittycad.io>"] | ||||
| version = "0.2.81" | ||||
| version = "0.2.82" | ||||
| edition = "2021" | ||||
| license = "MIT" | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
| @ -31,8 +31,8 @@ slog-term = { workspace = true } | ||||
| tracing-subscriber = { workspace = true } | ||||
|  | ||||
| [target.'cfg(not(target_arch = "wasm32"))'.dependencies] | ||||
| signal-hook = "0.3.17" | ||||
| tokio = { version = "1.44.2", features = ["full"] } | ||||
| signal-hook = "0.3.18" | ||||
| tokio = { version = "1.45.1", features = ["full"] } | ||||
| tower-lsp = { version = "0.20.0", features = ["proposed"] } | ||||
|  | ||||
| [target.'cfg(target_arch = "wasm32")'.dependencies] | ||||
|  | ||||
| @ -1,7 +1,7 @@ | ||||
| [package] | ||||
| name = "kcl-lib" | ||||
| description = "KittyCAD Language implementation and tools" | ||||
| version = "0.2.81" | ||||
| version = "0.2.82" | ||||
| edition = "2021" | ||||
| license = "MIT" | ||||
| repository = "https://github.com/KittyCAD/modeling-app" | ||||
| @ -25,8 +25,8 @@ async-recursion = "1.1.1" | ||||
| async-trait = { workspace = true } | ||||
| base64 = "0.22.1" | ||||
| bson = { workspace = true } | ||||
| chrono = "0.4.38" | ||||
| clap = { version = "4.5.36", default-features = false, optional = true, features = [ | ||||
| chrono = "0.4.41" | ||||
| clap = { version = "4.5.40", default-features = false, optional = true, features = [ | ||||
|     "std", | ||||
|     "derive", | ||||
| ] } | ||||
| @ -42,11 +42,12 @@ gltf-json = "1.4.1" | ||||
| http = { workspace = true } | ||||
| image = { version = "0.25.6", default-features = false, features = ["png"] } | ||||
| indexmap = { workspace = true, features = ["serde", "rayon"] } | ||||
| itertools = "0.13.0" | ||||
| itertools = "0.14.0" | ||||
| kcl-derive-docs = { version = "0.1", path = "../kcl-derive-docs" } | ||||
| kittycad = { workspace = true } | ||||
| kittycad-modeling-cmds = { workspace = true } | ||||
| lazy_static = { workspace = true } | ||||
| libm = "0.2.15" | ||||
| measurements = "0.11.0" | ||||
| miette = { workspace = true } | ||||
| mime_guess = "2.0.5" | ||||
| @ -69,11 +70,11 @@ schemars = { version = "0.8.17", features = [ | ||||
| ] } | ||||
| serde = { workspace = true } | ||||
| serde_json = { workspace = true } | ||||
| sha2 = "0.10.8" | ||||
| tabled = { version = "0.18.0", optional = true } | ||||
| tempfile = "3.19" | ||||
| sha2 = "0.10.9" | ||||
| tabled = { version = "0.20.0", optional = true } | ||||
| tempfile = "3.20" | ||||
| thiserror = "2.0.0" | ||||
| toml = "0.8.19" | ||||
| toml = "0.8.22" | ||||
| ts-rs = { version = "10.1.0", features = [ | ||||
|     "uuid-impl", | ||||
|     "url-impl", | ||||
| @ -82,7 +83,7 @@ ts-rs = { version = "10.1.0", features = [ | ||||
|     "no-serde-warnings", | ||||
|     "serde-json-impl", | ||||
| ] } | ||||
| tynm = "0.1.10" | ||||
| tynm = "0.2.0" | ||||
| url = { version = "2.5.4", features = ["serde"] } | ||||
| uuid = { workspace = true, features = ["v4", "v5", "js", "serde"] } | ||||
| validator = { version = "0.20.0", features = ["derive"] } | ||||
| @ -94,7 +95,6 @@ zip = { workspace = true } | ||||
| [target.'cfg(target_arch = "wasm32")'.dependencies] | ||||
| console_error_panic_hook = { workspace = true } | ||||
| futures-lite = "2.6.0" | ||||
| instant = { version = "0.1.13", features = ["wasm-bindgen", "inaccurate"] } | ||||
| js-sys = { version = "0.3.72" } | ||||
| tokio = { workspace = true, features = ["sync", "time"] } | ||||
| tower-lsp = { workspace = true, features = ["runtime-agnostic"] } | ||||
| @ -105,9 +105,8 @@ wasm-timer = { package = "zduny-wasm-timer", version = "0.2.5" } | ||||
| web-sys = { version = "0.3.76", features = ["console"] } | ||||
|  | ||||
| [target.'cfg(not(target_arch = "wasm32"))'.dependencies] | ||||
| instant = "0.1.13" | ||||
| tokio = { workspace = true, features = ["full"] } | ||||
| tokio-tungstenite = { version = "0.26.2", features = [ | ||||
| tokio-tungstenite = { version = "0.27.0", features = [ | ||||
|     "rustls-tls-native-roots", | ||||
| ] } | ||||
| tower-lsp = { workspace = true, features = ["proposed", "default"] } | ||||
| @ -131,15 +130,15 @@ tabled = ["dep:tabled"] | ||||
| approx = "0.5" | ||||
| base64 = "0.22.1" | ||||
| criterion = { version = "0.6.0", features = ["async_tokio"] } | ||||
| expectorate = "1.1.0" | ||||
| expectorate = "1.2.0" | ||||
| handlebars = "6.3.2" | ||||
| image = { version = "0.25.6", default-features = false, features = ["png"] } | ||||
| insta = { version = "1.42.2", features = ["json", "filters", "redactions"] } | ||||
| insta = { version = "1.43.1", features = ["json", "filters", "redactions"] } | ||||
| kcl-directory-test-macro = { version = "0.1", path = "../kcl-directory-test-macro" } | ||||
| miette = { version = "7.5.0", features = ["fancy"] } | ||||
| miette = { version = "7.6.0", features = ["fancy"] } | ||||
| pretty_assertions = "1.4.1" | ||||
| tokio = { version = "1.44.2", features = ["rt-multi-thread", "macros", "time"] } | ||||
| twenty-twenty = "0.8.0" | ||||
| tokio = { version = "1.45.1", features = ["rt-multi-thread", "macros", "time"] } | ||||
| twenty-twenty = "0.8.2" | ||||
|  | ||||
| [lints] | ||||
| workspace = true | ||||
|  | ||||
| @ -1 +1,19 @@ | ||||
| enum-variant-size-threshold = 48 | ||||
|  | ||||
| disallowed-methods = [ | ||||
|   { path = "f64::sin",   reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f64::cos",   reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f64::tan",   reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f64::asin",  reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f64::acos",  reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f64::atan",  reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f64::atan2", reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f32::sin",   reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f32::cos",   reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f32::tan",   reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f32::asin",  reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f32::acos",  reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f32::atan",  reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
|   { path = "f32::atan2", reason = "Use trig functions from libm crate instead, to ensure FP math works the same across OSs and platforms."}, | ||||
| ] | ||||
|  | ||||
|  | ||||
| @ -36,6 +36,7 @@ use schemars::JsonSchema; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use tokio::sync::RwLock; | ||||
| use uuid::Uuid; | ||||
| use web_time::Instant; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
| @ -241,7 +242,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { | ||||
|                 .unwrap_or_default() | ||||
|         }; | ||||
|  | ||||
|         let current_time = instant::Instant::now(); | ||||
|         let current_time = Instant::now(); | ||||
|         while current_time.elapsed().as_secs() < 60 { | ||||
|             let responses = self.responses().read().await.clone(); | ||||
|             let Some(resp) = responses.get(&id) else { | ||||
| @ -249,7 +250,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static { | ||||
|                 // No seriously WE DO NOT WANT TO PAUSE THE WHOLE APP ON THE JS SIDE. | ||||
|                 #[cfg(target_arch = "wasm32")] | ||||
|                 { | ||||
|                     let duration = instant::Duration::from_millis(1); | ||||
|                     let duration = web_time::Duration::from_millis(1); | ||||
|                     wasm_timer::Delay::new(duration).await.map_err(|err| { | ||||
|                         KclError::new_internal(KclErrorDetails::new( | ||||
|                             format!("Failed to sleep: {:?}", err), | ||||
|  | ||||
| @ -668,9 +668,8 @@ impl From<GeometryWithImportedGeometry> for KclValue { | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use crate::exec::UnitType; | ||||
|  | ||||
|     use super::*; | ||||
|     use crate::exec::UnitType; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_human_friendly_type() { | ||||
|  | ||||
| @ -3005,6 +3005,8 @@ impl BinaryOperator { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// The operator associativity of the operator (as in the parsing sense, not the mathematical sense of associativity). | ||||
|     /// | ||||
|     /// Follow JS definitions of each operator. | ||||
|     /// Taken from <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table> | ||||
|     pub fn associativity(&self) -> Associativity { | ||||
| @ -3015,6 +3017,12 @@ impl BinaryOperator { | ||||
|             Self::And | Self::Or => Associativity::Left, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Whether an operator is mathematically associative. If it is, then the operator associativity (given by the | ||||
|     /// `associativity` method) is mostly irrelevant. | ||||
|     pub fn associative(&self) -> bool { | ||||
|         matches!(self, Self::Add | Self::Mul | Self::And | Self::Or) | ||||
|     } | ||||
| } | ||||
| #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] | ||||
| #[ts(export)] | ||||
|  | ||||
| @ -3334,7 +3334,7 @@ mod tests { | ||||
|     use super::*; | ||||
|     use crate::{ | ||||
|         parsing::ast::types::{BodyItem, Expr, VariableKind}, | ||||
|         KclError, ModuleId, | ||||
|         ModuleId, | ||||
|     }; | ||||
|  | ||||
|     fn assert_reserved(word: &str) { | ||||
| @ -4398,14 +4398,10 @@ secondExtrude = startSketchOn(XY) | ||||
|     #[test] | ||||
|     fn test_parse_parens_unicode() { | ||||
|         let result = crate::parsing::top_level_parse("(ޜ"); | ||||
|         let KclError::Lexical { details } = result.0.unwrap_err() else { | ||||
|             panic!(); | ||||
|         }; | ||||
|         // TODO: Better errors when program cannot tokenize. | ||||
|         let details = result.0.unwrap().1.pop().unwrap(); | ||||
|         // TODO: Highlight where the unmatched open parenthesis is. | ||||
|         // https://github.com/KittyCAD/modeling-app/issues/696 | ||||
|         assert_eq!(details.message, "found unknown token 'ޜ'"); | ||||
|         assert_eq!(details.source_ranges[0].start(), 1); | ||||
|         assert_eq!(details.source_ranges[0].end(), 2); | ||||
|         assert_eq!(details.message, "Unexpected end of file. The compiler expected )"); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|  | ||||
| @ -6,7 +6,7 @@ use winnow::{ | ||||
|     error::{ContextError, ParseError}, | ||||
|     prelude::*, | ||||
|     stream::{Location, Stream}, | ||||
|     token::{any, none_of, one_of, take_till, take_until}, | ||||
|     token::{any, none_of, take_till, take_until, take_while}, | ||||
|     LocatingSlice, Stateful, | ||||
| }; | ||||
|  | ||||
| @ -163,8 +163,8 @@ fn whitespace(i: &mut Input<'_>) -> ModalResult<Token> { | ||||
| } | ||||
|  | ||||
| fn inner_word(i: &mut Input<'_>) -> ModalResult<()> { | ||||
|     one_of(('a'..='z', 'A'..='Z', '_')).parse_next(i)?; | ||||
|     repeat::<_, _, (), _, _>(0.., one_of(('a'..='z', 'A'..='Z', '0'..='9', '_'))).parse_next(i)?; | ||||
|     take_while(1.., |c: char| c.is_alphabetic() || c == '_').parse_next(i)?; | ||||
|     take_while(0.., |c: char| c.is_alphabetic() || c.is_ascii_digit() || c == '_').parse_next(i)?; | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| @ -786,6 +786,7 @@ const things = "things" | ||||
|         }; | ||||
|         assert_eq!(actual.tokens[0], expected); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_word_starting_with_keyword() { | ||||
|         let module_id = ModuleId::default(); | ||||
| @ -799,4 +800,18 @@ const things = "things" | ||||
|         }; | ||||
|         assert_eq!(actual.tokens[0], expected); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn non_english_identifiers() { | ||||
|         let module_id = ModuleId::default(); | ||||
|         let actual = lex("亞當", module_id).unwrap(); | ||||
|         let expected = Token { | ||||
|             token_type: TokenType::Word, | ||||
|             value: "亞當".to_owned(), | ||||
|             start: 0, | ||||
|             end: 6, | ||||
|             module_id, | ||||
|         }; | ||||
|         assert_eq!(actual.tokens[0], expected); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -4,7 +4,6 @@ use std::{ | ||||
| }; | ||||
|  | ||||
| use indexmap::IndexMap; | ||||
| use insta::rounded_redaction; | ||||
|  | ||||
| use crate::{ | ||||
|     errors::KclError, | ||||
| @ -262,17 +261,6 @@ async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) { | ||||
|             let mem_result = catch_unwind(AssertUnwindSafe(|| { | ||||
|                 assert_snapshot(test, "Variables in memory after executing", || { | ||||
|                     insta::assert_json_snapshot!("program_memory", outcome.variables, { | ||||
|                         ".**.value" => rounded_redaction(3), | ||||
|                         ".**[].value" => rounded_redaction(3), | ||||
|                         ".**.from[]" => rounded_redaction(3), | ||||
|                         ".**.to[]" => rounded_redaction(3), | ||||
|                         ".**.center[]" => rounded_redaction(3), | ||||
|                         ".**[].x[]" => rounded_redaction(3), | ||||
|                         ".**[].y[]" => rounded_redaction(3), | ||||
|                         ".**[].z[]" => rounded_redaction(3), | ||||
|                         ".**.x" => rounded_redaction(3), | ||||
|                         ".**.y" => rounded_redaction(3), | ||||
|                         ".**.z" => rounded_redaction(3), | ||||
|                          ".**.sourceRange" => Vec::new(), | ||||
|                     }) | ||||
|                 }) | ||||
| @ -346,11 +334,6 @@ fn assert_artifact_snapshots( | ||||
|     let result1 = catch_unwind(AssertUnwindSafe(|| { | ||||
|         assert_snapshot(test, "Operations executed", || { | ||||
|             insta::assert_json_snapshot!("ops", module_operations, { | ||||
|                 ".*[].*.unlabeledArg.*.value.**[].from[]" => rounded_redaction(3), | ||||
|                 ".*[].*.unlabeledArg.*.value.**[].to[]" => rounded_redaction(3), | ||||
|                 ".*[].**.value.value" => rounded_redaction(3), | ||||
|                 ".*[].*.labeledArgs.*.value.**[].from[]" => rounded_redaction(3), | ||||
|                 ".*[].*.labeledArgs.*.value.**[].to[]" => rounded_redaction(3), | ||||
|                 ".**.sourceRange" => Vec::new(), | ||||
|                 ".**.functionSourceRange" => Vec::new(), | ||||
|                 ".**.moduleId" => 0, | ||||
| @ -364,10 +347,6 @@ fn assert_artifact_snapshots( | ||||
|     let result2 = catch_unwind(AssertUnwindSafe(|| { | ||||
|         assert_snapshot(test, "Artifact commands", || { | ||||
|             insta::assert_json_snapshot!("artifact_commands", module_commands, { | ||||
|                 ".*[].command.**.value" => rounded_redaction(3), | ||||
|                 ".*[].command.**.x" => rounded_redaction(3), | ||||
|                 ".*[].command.**.y" => rounded_redaction(3), | ||||
|                 ".*[].command.**.z" => rounded_redaction(3), | ||||
|                 ".**.range" => Vec::new(), | ||||
|             }); | ||||
|         }) | ||||
| @ -3626,3 +3605,24 @@ mod user_reported_union_2_bug { | ||||
|         super::execute(TEST_NAME, false).await | ||||
|     } | ||||
| } | ||||
| mod non_english_identifiers { | ||||
|     const TEST_NAME: &str = "non_english_identifiers"; | ||||
|  | ||||
|     /// Test parsing KCL. | ||||
|     #[test] | ||||
|     fn parse() { | ||||
|         super::parse(TEST_NAME) | ||||
|     } | ||||
|  | ||||
|     /// Test that parsing and unparsing KCL produces the original KCL input. | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
|     async fn unparse() { | ||||
|         super::unparse(TEST_NAME).await | ||||
|     } | ||||
|  | ||||
|     /// Test that KCL is executed correctly. | ||||
|     #[tokio::test(flavor = "multi_thread")] | ||||
|     async fn kcl_test_execute() { | ||||
|         super::execute(TEST_NAME, true).await | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -9,7 +9,7 @@ use kittycad_modeling_cmds::{ | ||||
|     websocket::OkWebSocketResponseData, | ||||
| }; | ||||
|  | ||||
| use super::{args::TyF64, DEFAULT_TOLERANCE}; | ||||
| use super::{args::TyF64, DEFAULT_TOLERANCE_MM}; | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{types::RuntimeType, ExecState, KclValue, ModelingCmdMeta, Solid}, | ||||
| @ -57,7 +57,7 @@ pub(crate) async fn inner_union( | ||||
|             ModelingCmdMeta::from_args_id(&args, solid_out_id), | ||||
|             ModelingCmd::from(mcmd::BooleanUnion { | ||||
|                 solid_ids: solids.iter().map(|s| s.id).collect(), | ||||
|                 tolerance: LengthUnit(tolerance.map(|t| t.n).unwrap_or(DEFAULT_TOLERANCE)), | ||||
|                 tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)), | ||||
|             }), | ||||
|         ) | ||||
|         .await?; | ||||
| @ -122,7 +122,7 @@ pub(crate) async fn inner_intersect( | ||||
|             ModelingCmdMeta::from_args_id(&args, solid_out_id), | ||||
|             ModelingCmd::from(mcmd::BooleanIntersection { | ||||
|                 solid_ids: solids.iter().map(|s| s.id).collect(), | ||||
|                 tolerance: LengthUnit(tolerance.map(|t| t.n).unwrap_or(DEFAULT_TOLERANCE)), | ||||
|                 tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)), | ||||
|             }), | ||||
|         ) | ||||
|         .await?; | ||||
| @ -186,7 +186,7 @@ pub(crate) async fn inner_subtract( | ||||
|             ModelingCmd::from(mcmd::BooleanSubtract { | ||||
|                 target_ids: solids.iter().map(|s| s.id).collect(), | ||||
|                 tool_ids: tools.iter().map(|s| s.id).collect(), | ||||
|                 tolerance: LengthUnit(tolerance.map(|t| t.n).unwrap_or(DEFAULT_TOLERANCE)), | ||||
|                 tolerance: LengthUnit(tolerance.map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)), | ||||
|             }), | ||||
|         ) | ||||
|         .await?; | ||||
|  | ||||
| @ -18,7 +18,7 @@ use kittycad_modeling_cmds::{ | ||||
| }; | ||||
| use uuid::Uuid; | ||||
|  | ||||
| use super::{args::TyF64, utils::point_to_mm, DEFAULT_TOLERANCE}; | ||||
| use super::{args::TyF64, utils::point_to_mm, DEFAULT_TOLERANCE_MM}; | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ | ||||
| @ -79,7 +79,7 @@ async fn inner_extrude( | ||||
| ) -> Result<Vec<Solid>, KclError> { | ||||
|     // Extrude the element(s). | ||||
|     let mut solids = Vec::new(); | ||||
|     let tolerance = LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)); | ||||
|     let tolerance = LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)); | ||||
|  | ||||
|     if symmetric.unwrap_or(false) && bidirectional_length.is_some() { | ||||
|         return Err(KclError::new_semantic(KclErrorDetails::new( | ||||
|  | ||||
| @ -6,7 +6,7 @@ use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::CutType, ModelingC | ||||
| use kittycad_modeling_cmds as kcmc; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use super::{args::TyF64, DEFAULT_TOLERANCE}; | ||||
| use super::{args::TyF64, DEFAULT_TOLERANCE_MM}; | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ | ||||
| @ -122,7 +122,7 @@ async fn inner_fillet( | ||||
|                 strategy: Default::default(), | ||||
|                 object_id: solid.id, | ||||
|                 radius: LengthUnit(radius.to_mm()), | ||||
|                 tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)), | ||||
|                 tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)), | ||||
|                 cut_type: CutType::Fillet, | ||||
|             }), | ||||
|         ) | ||||
|  | ||||
| @ -6,7 +6,7 @@ use anyhow::Result; | ||||
| use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, ModelingCmd}; | ||||
| use kittycad_modeling_cmds as kcmc; | ||||
|  | ||||
| use super::{args::TyF64, DEFAULT_TOLERANCE}; | ||||
| use super::{args::TyF64, DEFAULT_TOLERANCE_MM}; | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ | ||||
| @ -84,7 +84,7 @@ async fn inner_loft( | ||||
|                 section_ids: sketches.iter().map(|group| group.id).collect(), | ||||
|                 base_curve_index, | ||||
|                 bez_approximate_rational, | ||||
|                 tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)), | ||||
|                 tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)), | ||||
|                 v_degree, | ||||
|             }), | ||||
|         ) | ||||
|  | ||||
| @ -34,21 +34,21 @@ pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl | ||||
| pub async fn cos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?; | ||||
|     let num = num.to_radians(); | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(num.cos(), exec_state.current_default_units()))) | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::cos(num), exec_state.current_default_units()))) | ||||
| } | ||||
|  | ||||
| /// Compute the sine of a number (in radians). | ||||
| pub async fn sin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?; | ||||
|     let num = num.to_radians(); | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(num.sin(), exec_state.current_default_units()))) | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::sin(num), exec_state.current_default_units()))) | ||||
| } | ||||
|  | ||||
| /// Compute the tangent of a number (in radians). | ||||
| pub async fn tan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let num: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::angle(), exec_state)?; | ||||
|     let num = num.to_radians(); | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(num.tan(), exec_state.current_default_units()))) | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(libm::tan(num), exec_state.current_default_units()))) | ||||
| } | ||||
|  | ||||
| /// Compute the square root of a number. | ||||
| @ -164,7 +164,7 @@ pub async fn pow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl | ||||
| /// Compute the arccosine of a number (in radians). | ||||
| pub async fn acos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?; | ||||
|     let result = input.n.acos(); | ||||
|     let result = libm::acos(input.n); | ||||
|  | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians()))) | ||||
| } | ||||
| @ -172,7 +172,7 @@ pub async fn acos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc | ||||
| /// Compute the arcsine of a number (in radians). | ||||
| pub async fn asin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?; | ||||
|     let result = input.n.asin(); | ||||
|     let result = libm::asin(input.n); | ||||
|  | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians()))) | ||||
| } | ||||
| @ -180,7 +180,7 @@ pub async fn asin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc | ||||
| /// Compute the arctangent of a number (in radians). | ||||
| pub async fn atan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let input: TyF64 = args.get_unlabeled_kw_arg("input", &RuntimeType::count(), exec_state)?; | ||||
|     let result = input.n.atan(); | ||||
|     let result = libm::atan(input.n); | ||||
|  | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians()))) | ||||
| } | ||||
| @ -190,7 +190,7 @@ pub async fn atan2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K | ||||
|     let y = args.get_kw_arg("y", &RuntimeType::length(), exec_state)?; | ||||
|     let x = args.get_kw_arg("x", &RuntimeType::length(), exec_state)?; | ||||
|     let (y, x, _) = NumericType::combine_eq_coerce(y, x); | ||||
|     let result = y.atan2(x); | ||||
|     let result = libm::atan2(y, x); | ||||
|  | ||||
|     Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians()))) | ||||
| } | ||||
| @ -246,7 +246,7 @@ pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclVa | ||||
|     let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?; | ||||
|     let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?; | ||||
|     let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg); | ||||
|     let result = (leg.min(hypotenuse) / hypotenuse).acos().to_degrees(); | ||||
|     let result = libm::acos(leg.min(hypotenuse) / hypotenuse).to_degrees(); | ||||
|     Ok(KclValue::from_number_with_type( | ||||
|         result, | ||||
|         NumericType::degrees(), | ||||
| @ -259,7 +259,7 @@ pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclVa | ||||
|     let hypotenuse: TyF64 = args.get_kw_arg("hypotenuse", &RuntimeType::length(), exec_state)?; | ||||
|     let leg: TyF64 = args.get_kw_arg("leg", &RuntimeType::length(), exec_state)?; | ||||
|     let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg); | ||||
|     let result = (leg.min(hypotenuse) / hypotenuse).asin().to_degrees(); | ||||
|     let result = libm::asin(leg.min(hypotenuse) / hypotenuse).to_degrees(); | ||||
|     Ok(KclValue::from_number_with_type( | ||||
|         result, | ||||
|         NumericType::degrees(), | ||||
|  | ||||
| @ -442,5 +442,5 @@ pub(crate) fn std_ty(path: &str, fn_name: &str) -> (PrimitiveType, StdFnProps) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// The default tolerance for modeling commands in [`kittycad_modeling_cmds::length_unit::LengthUnit`]. | ||||
| const DEFAULT_TOLERANCE: f64 = 0.0000001; | ||||
| /// The default tolerance for modeling commands in millimeters. | ||||
| const DEFAULT_TOLERANCE_MM: f64 = 0.0000001; | ||||
|  | ||||
| @ -9,7 +9,7 @@ use kcmc::{ | ||||
| }; | ||||
| use kittycad_modeling_cmds::{self as kcmc, shared::Point3d}; | ||||
|  | ||||
| use super::{args::TyF64, DEFAULT_TOLERANCE}; | ||||
| use super::{args::TyF64, DEFAULT_TOLERANCE_MM}; | ||||
| use crate::{ | ||||
|     errors::{KclError, KclErrorDetails}, | ||||
|     execution::{ | ||||
| @ -133,7 +133,7 @@ async fn inner_revolve( | ||||
|     let mut solids = Vec::new(); | ||||
|     for sketch in &sketches { | ||||
|         let id = exec_state.next_uuid(); | ||||
|         let tolerance = tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE); | ||||
|         let tolerance = tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM); | ||||
|  | ||||
|         let direction = match &axis { | ||||
|             Axis2dOrEdgeReference::Axis { direction, origin } => { | ||||
|  | ||||
| @ -250,7 +250,7 @@ async fn inner_tangent_to_end(tag: &TagIdentifier, exec_state: &mut ExecState, a | ||||
|  | ||||
|     // Calculate the end point from the angle and radius. | ||||
|     // atan2 outputs radians. | ||||
|     let previous_end_tangent = Angle::from_radians(f64::atan2( | ||||
|     let previous_end_tangent = Angle::from_radians(libm::atan2( | ||||
|         from[1] - tan_previous_point[1], | ||||
|         from[0] - tan_previous_point[0], | ||||
|     )); | ||||
|  | ||||
| @ -305,7 +305,7 @@ async fn inner_polygon( | ||||
|         radius.n | ||||
|     } else { | ||||
|         // circumscribed | ||||
|         radius.n / half_angle.cos() | ||||
|         radius.n / libm::cos(half_angle) | ||||
|     }; | ||||
|  | ||||
|     let angle_step = std::f64::consts::TAU / num_sides as f64; | ||||
| @ -316,8 +316,8 @@ async fn inner_polygon( | ||||
|         .map(|i| { | ||||
|             let angle = angle_step * i as f64; | ||||
|             [ | ||||
|                 center_u[0] + radius_to_vertices * angle.cos(), | ||||
|                 center_u[1] + radius_to_vertices * angle.sin(), | ||||
|                 center_u[0] + radius_to_vertices * libm::cos(angle), | ||||
|                 center_u[1] + radius_to_vertices * libm::sin(angle), | ||||
|             ] | ||||
|         }) | ||||
|         .collect(); | ||||
| @ -415,16 +415,26 @@ pub(crate) fn get_radius( | ||||
|     radius: Option<TyF64>, | ||||
|     diameter: Option<TyF64>, | ||||
|     source_range: SourceRange, | ||||
| ) -> Result<TyF64, KclError> { | ||||
|     get_radius_labelled(radius, diameter, source_range, "radius", "diameter") | ||||
| } | ||||
|  | ||||
| pub(crate) fn get_radius_labelled( | ||||
|     radius: Option<TyF64>, | ||||
|     diameter: Option<TyF64>, | ||||
|     source_range: SourceRange, | ||||
|     label_radius: &'static str, | ||||
|     label_diameter: &'static str, | ||||
| ) -> Result<TyF64, KclError> { | ||||
|     match (radius, diameter) { | ||||
|         (Some(radius), None) => Ok(radius), | ||||
|         (None, Some(diameter)) => Ok(TyF64::new(diameter.n / 2.0, diameter.ty)), | ||||
|         (None, None) => Err(KclError::new_type(KclErrorDetails::new( | ||||
|             "This function needs either `diameter` or `radius`".to_string(), | ||||
|             format!("This function needs either `{label_diameter}` or `{label_radius}`"), | ||||
|             vec![source_range], | ||||
|         ))), | ||||
|         (Some(_), Some(_)) => Err(KclError::new_type(KclErrorDetails::new( | ||||
|             "You cannot specify both `diameter` and `radius`, please remove one".to_string(), | ||||
|             format!("You cannot specify both `{label_diameter}` and `{label_radius}`, please remove one"), | ||||
|             vec![source_range], | ||||
|         ))), | ||||
|     } | ||||
|  | ||||
| @ -11,7 +11,7 @@ use parse_display::{Display, FromStr}; | ||||
| use schemars::JsonSchema; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use super::shapes::get_radius; | ||||
| use super::shapes::{get_radius, get_radius_labelled}; | ||||
| #[cfg(feature = "artifact-graph")] | ||||
| use crate::execution::{Artifact, ArtifactId, CodeRef, StartSketchOnFace, StartSketchOnPlane}; | ||||
| use crate::{ | ||||
| @ -101,13 +101,26 @@ pub const NEW_TAG_KW: &str = "tag"; | ||||
| pub async fn involute_circular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { | ||||
|     let sketch = args.get_unlabeled_kw_arg("sketch", &RuntimeType::sketch(), exec_state)?; | ||||
|  | ||||
|     let start_radius: TyF64 = args.get_kw_arg("startRadius", &RuntimeType::length(), exec_state)?; | ||||
|     let end_radius: TyF64 = args.get_kw_arg("endRadius", &RuntimeType::length(), exec_state)?; | ||||
|     let start_radius: Option<TyF64> = args.get_kw_arg_opt("startRadius", &RuntimeType::length(), exec_state)?; | ||||
|     let end_radius: Option<TyF64> = args.get_kw_arg_opt("endRadius", &RuntimeType::length(), exec_state)?; | ||||
|     let start_diameter: Option<TyF64> = args.get_kw_arg_opt("startDiameter", &RuntimeType::length(), exec_state)?; | ||||
|     let end_diameter: Option<TyF64> = args.get_kw_arg_opt("endDiameter", &RuntimeType::length(), exec_state)?; | ||||
|     let angle: TyF64 = args.get_kw_arg("angle", &RuntimeType::angle(), exec_state)?; | ||||
|     let reverse = args.get_kw_arg_opt("reverse", &RuntimeType::bool(), exec_state)?; | ||||
|     let tag = args.get_kw_arg_opt("tag", &RuntimeType::tag_decl(), exec_state)?; | ||||
|     let new_sketch = | ||||
|         inner_involute_circular(sketch, start_radius, end_radius, angle, reverse, tag, exec_state, args).await?; | ||||
|     let new_sketch = inner_involute_circular( | ||||
|         sketch, | ||||
|         start_radius, | ||||
|         end_radius, | ||||
|         start_diameter, | ||||
|         end_diameter, | ||||
|         angle, | ||||
|         reverse, | ||||
|         tag, | ||||
|         exec_state, | ||||
|         args, | ||||
|     ) | ||||
|     .await?; | ||||
|     Ok(KclValue::Sketch { | ||||
|         value: Box::new(new_sketch), | ||||
|     }) | ||||
| @ -115,16 +128,18 @@ pub async fn involute_circular(exec_state: &mut ExecState, args: Args) -> Result | ||||
|  | ||||
| fn involute_curve(radius: f64, angle: f64) -> (f64, f64) { | ||||
|     ( | ||||
|         radius * (angle.cos() + angle * angle.sin()), | ||||
|         radius * (angle.sin() - angle * angle.cos()), | ||||
|         radius * (libm::cos(angle) + angle * libm::sin(angle)), | ||||
|         radius * (libm::sin(angle) - angle * libm::cos(angle)), | ||||
|     ) | ||||
| } | ||||
|  | ||||
| #[allow(clippy::too_many_arguments)] | ||||
| async fn inner_involute_circular( | ||||
|     sketch: Sketch, | ||||
|     start_radius: TyF64, | ||||
|     end_radius: TyF64, | ||||
|     start_radius: Option<TyF64>, | ||||
|     end_radius: Option<TyF64>, | ||||
|     start_diameter: Option<TyF64>, | ||||
|     end_diameter: Option<TyF64>, | ||||
|     angle: TyF64, | ||||
|     reverse: Option<bool>, | ||||
|     tag: Option<TagNode>, | ||||
| @ -133,6 +148,22 @@ async fn inner_involute_circular( | ||||
| ) -> Result<Sketch, KclError> { | ||||
|     let id = exec_state.next_uuid(); | ||||
|  | ||||
|     let longer_args_dot_source_range = args.source_range; | ||||
|     let start_radius = get_radius_labelled( | ||||
|         start_radius, | ||||
|         start_diameter, | ||||
|         args.source_range, | ||||
|         "startRadius", | ||||
|         "startDiameter", | ||||
|     )?; | ||||
|     let end_radius = get_radius_labelled( | ||||
|         end_radius, | ||||
|         end_diameter, | ||||
|         longer_args_dot_source_range, | ||||
|         "endRadius", | ||||
|         "endDiameter", | ||||
|     )?; | ||||
|  | ||||
|     exec_state | ||||
|         .batch_modeling_cmd( | ||||
|             ModelingCmdMeta::from_args_id(&args, id), | ||||
| @ -157,11 +188,11 @@ async fn inner_involute_circular( | ||||
|     let theta = f64::sqrt(end_radius * end_radius - start_radius * start_radius) / start_radius; | ||||
|     let (x, y) = involute_curve(start_radius, theta); | ||||
|  | ||||
|     end.x = x * angle.to_radians().cos() - y * angle.to_radians().sin(); | ||||
|     end.y = x * angle.to_radians().sin() + y * angle.to_radians().cos(); | ||||
|     end.x = x * libm::cos(angle.to_radians()) - y * libm::sin(angle.to_radians()); | ||||
|     end.y = x * libm::sin(angle.to_radians()) + y * libm::cos(angle.to_radians()); | ||||
|  | ||||
|     end.x -= start_radius * angle.to_radians().cos(); | ||||
|     end.y -= start_radius * angle.to_radians().sin(); | ||||
|     end.x -= start_radius * libm::cos(angle.to_radians()); | ||||
|     end.y -= start_radius * libm::sin(angle.to_radians()); | ||||
|  | ||||
|     if reverse.unwrap_or_default() { | ||||
|         end.x = -end.x; | ||||
| @ -500,8 +531,8 @@ async fn inner_angled_line_length( | ||||
|  | ||||
|     //double check me on this one - mike | ||||
|     let delta: [f64; 2] = [ | ||||
|         length * f64::cos(angle_degrees.to_radians()), | ||||
|         length * f64::sin(angle_degrees.to_radians()), | ||||
|         length * libm::cos(angle_degrees.to_radians()), | ||||
|         length * libm::sin(angle_degrees.to_radians()), | ||||
|     ]; | ||||
|     let relative = true; | ||||
|  | ||||
| @ -601,7 +632,7 @@ async fn inner_angled_line_to_x( | ||||
|     } | ||||
|  | ||||
|     let x_component = x_to.to_length_units(from.units) - from.x; | ||||
|     let y_component = x_component * f64::tan(angle_degrees.to_radians()); | ||||
|     let y_component = x_component * libm::tan(angle_degrees.to_radians()); | ||||
|     let y_to = from.y + y_component; | ||||
|  | ||||
|     let new_sketch = straight_line( | ||||
| @ -668,7 +699,7 @@ async fn inner_angled_line_to_y( | ||||
|     } | ||||
|  | ||||
|     let y_component = y_to.to_length_units(from.units) - from.y; | ||||
|     let x_component = y_component / f64::tan(angle_degrees.to_radians()); | ||||
|     let x_component = y_component / libm::tan(angle_degrees.to_radians()); | ||||
|     let x_to = from.x + x_component; | ||||
|  | ||||
|     let new_sketch = straight_line( | ||||
| @ -1413,7 +1444,7 @@ async fn inner_tangential_arc_radius_angle( | ||||
|  | ||||
|             // Calculate the end point from the angle and radius. | ||||
|             // atan2 outputs radians. | ||||
|             let previous_end_tangent = Angle::from_radians(f64::atan2( | ||||
|             let previous_end_tangent = Angle::from_radians(libm::atan2( | ||||
|                 from.y - tan_previous_point[1], | ||||
|                 from.x - tan_previous_point[0], | ||||
|             )); | ||||
|  | ||||
| @ -6,7 +6,7 @@ use kittycad_modeling_cmds::{self as kcmc, shared::RelativeTo}; | ||||
| use schemars::JsonSchema; | ||||
| use serde::Serialize; | ||||
|  | ||||
| use super::{args::TyF64, DEFAULT_TOLERANCE}; | ||||
| use super::{args::TyF64, DEFAULT_TOLERANCE_MM}; | ||||
| use crate::{ | ||||
|     errors::KclError, | ||||
|     execution::{ | ||||
| @ -93,7 +93,7 @@ async fn inner_sweep( | ||||
|                     target: sketch.id.into(), | ||||
|                     trajectory, | ||||
|                     sectional: sectional.unwrap_or(false), | ||||
|                     tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)), | ||||
|                     tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE_MM)), | ||||
|                     relative_to, | ||||
|                 }), | ||||
|             ) | ||||
|  | ||||
| @ -44,7 +44,7 @@ pub(crate) fn distance(a: Coords2d, b: Coords2d) -> f64 { | ||||
| pub(crate) fn between(a: Coords2d, b: Coords2d) -> Angle { | ||||
|     let x = b[0] - a[0]; | ||||
|     let y = b[1] - a[1]; | ||||
|     normalize(Angle::from_radians(y.atan2(x))) | ||||
|     normalize(Angle::from_radians(libm::atan2(y, x))) | ||||
| } | ||||
|  | ||||
| /// Normalize the angle | ||||
| @ -97,8 +97,8 @@ pub(crate) fn normalize_rad(angle: f64) -> f64 { | ||||
|  | ||||
| fn calculate_intersection_of_two_lines(line1: &[Coords2d; 2], line2_angle: f64, line2_point: Coords2d) -> Coords2d { | ||||
|     let line2_point_b = [ | ||||
|         line2_point[0] + f64::cos(line2_angle.to_radians()) * 10.0, | ||||
|         line2_point[1] + f64::sin(line2_angle.to_radians()) * 10.0, | ||||
|         line2_point[0] + libm::cos(line2_angle.to_radians()) * 10.0, | ||||
|         line2_point[1] + libm::sin(line2_angle.to_radians()) * 10.0, | ||||
|     ]; | ||||
|     intersect(line1[0], line1[1], line2_point, line2_point_b) | ||||
| } | ||||
| @ -138,13 +138,13 @@ fn offset_line(offset: f64, p1: Coords2d, p2: Coords2d) -> [Coords2d; 2] { | ||||
|         let direction = (p2[0] - p1[0]).signum(); | ||||
|         return [[p1[0], p1[1] + offset * direction], [p2[0], p2[1] + offset * direction]]; | ||||
|     } | ||||
|     let x_offset = offset / f64::sin(f64::atan2(p1[1] - p2[1], p1[0] - p2[0])); | ||||
|     let x_offset = offset / libm::sin(libm::atan2(p1[1] - p2[1], p1[0] - p2[0])); | ||||
|     [[p1[0] + x_offset, p1[1]], [p2[0] + x_offset, p2[1]]] | ||||
| } | ||||
|  | ||||
| pub(crate) fn get_y_component(angle: Angle, x: f64) -> Coords2d { | ||||
|     let normalised_angle = ((angle.to_degrees() % 360.0) + 360.0) % 360.0; // between 0 and 360 | ||||
|     let y = x * f64::tan(normalised_angle.to_radians()); | ||||
|     let y = x * libm::tan(normalised_angle.to_radians()); | ||||
|     let sign = if normalised_angle > 90.0 && normalised_angle <= 270.0 { | ||||
|         -1.0 | ||||
|     } else { | ||||
| @ -155,7 +155,7 @@ pub(crate) fn get_y_component(angle: Angle, x: f64) -> Coords2d { | ||||
|  | ||||
| pub(crate) fn get_x_component(angle: Angle, y: f64) -> Coords2d { | ||||
|     let normalised_angle = ((angle.to_degrees() % 360.0) + 360.0) % 360.0; // between 0 and 360 | ||||
|     let x = y / f64::tan(normalised_angle.to_radians()); | ||||
|     let x = y / libm::tan(normalised_angle.to_radians()); | ||||
|     let sign = if normalised_angle > 180.0 && normalised_angle <= 360.0 { | ||||
|         -1.0 | ||||
|     } else { | ||||
| @ -174,13 +174,13 @@ pub(crate) fn arc_center_and_end( | ||||
|     let end_angle = end_angle.to_radians(); | ||||
|  | ||||
|     let center = [ | ||||
|         -1.0 * (radius * start_angle.cos() - from[0]), | ||||
|         -1.0 * (radius * start_angle.sin() - from[1]), | ||||
|         -1.0 * (radius * libm::cos(start_angle) - from[0]), | ||||
|         -1.0 * (radius * libm::sin(start_angle) - from[1]), | ||||
|     ]; | ||||
|  | ||||
|     let end = [ | ||||
|         center[0] + radius * end_angle.cos(), | ||||
|         center[1] + radius * end_angle.sin(), | ||||
|         center[0] + radius * libm::cos(end_angle), | ||||
|         center[1] + radius * libm::sin(end_angle), | ||||
|     ]; | ||||
|  | ||||
|     (center, end) | ||||
| @ -357,7 +357,10 @@ mod tests { | ||||
|  | ||||
|         let get_point = |radius: f64, t: f64| { | ||||
|             let angle = t * TAU; | ||||
|             [center[0] + radius * angle.cos(), center[1] + radius * angle.sin()] | ||||
|             [ | ||||
|                 center[0] + radius * libm::cos(angle), | ||||
|                 center[1] + radius * libm::sin(angle), | ||||
|             ] | ||||
|         }; | ||||
|  | ||||
|         for radius in radius_array { | ||||
| @ -442,7 +445,7 @@ fn get_slope(start: Coords2d, end: Coords2d) -> (f64, f64) { | ||||
| fn get_angle(point1: Coords2d, point2: Coords2d) -> f64 { | ||||
|     let delta_x = point2[0] - point1[0]; | ||||
|     let delta_y = point2[1] - point1[1]; | ||||
|     let angle = delta_y.atan2(delta_x); | ||||
|     let angle = libm::atan2(delta_y, delta_x); | ||||
|  | ||||
|     let result = if angle < 0.0 { angle + 2.0 * PI } else { angle }; | ||||
|     result * (180.0 / PI) | ||||
| @ -484,13 +487,13 @@ fn get_mid_point( | ||||
|     ); | ||||
|     let delta_ang = delta_ang / 2.0 + deg2rad(angle_from_center_to_arc_start); | ||||
|     let shortest_arc_mid_point: Coords2d = [ | ||||
|         delta_ang.cos() * radius + center[0], | ||||
|         delta_ang.sin() * radius + center[1], | ||||
|         libm::cos(delta_ang) * radius + center[0], | ||||
|         libm::sin(delta_ang) * radius + center[1], | ||||
|     ]; | ||||
|     let opposite_delta = delta_ang + PI; | ||||
|     let longest_arc_mid_point: Coords2d = [ | ||||
|         opposite_delta.cos() * radius + center[0], | ||||
|         opposite_delta.sin() * radius + center[1], | ||||
|         libm::cos(opposite_delta) * radius + center[0], | ||||
|         libm::sin(opposite_delta) * radius + center[1], | ||||
|     ]; | ||||
|  | ||||
|     let rotation_direction_original_points = is_points_ccw(&[tan_previous_point, arc_start_point, arc_end_point]); | ||||
| @ -592,11 +595,14 @@ pub fn get_tangential_arc_to_info(input: TangentialArcInfoInput) -> TangentialAr | ||||
|         input.obtuse, | ||||
|     ); | ||||
|  | ||||
|     let start_angle = (input.arc_start_point[1] - center[1]).atan2(input.arc_start_point[0] - center[0]); | ||||
|     let end_angle = (input.arc_end_point[1] - center[1]).atan2(input.arc_end_point[0] - center[0]); | ||||
|     let start_angle = libm::atan2( | ||||
|         input.arc_start_point[1] - center[1], | ||||
|         input.arc_start_point[0] - center[0], | ||||
|     ); | ||||
|     let end_angle = libm::atan2(input.arc_end_point[1] - center[1], input.arc_end_point[0] - center[0]); | ||||
|     let ccw = is_points_ccw(&[input.arc_start_point, arc_mid_point, input.arc_end_point]); | ||||
|  | ||||
|     let arc_mid_angle = (arc_mid_point[1] - center[1]).atan2(arc_mid_point[0] - center[0]); | ||||
|     let arc_mid_angle = libm::atan2(arc_mid_point[1] - center[1], arc_mid_point[0] - center[0]); | ||||
|     let start_to_mid_arc_length = radius | ||||
|         * delta(Angle::from_radians(start_angle), Angle::from_radians(arc_mid_angle)) | ||||
|             .to_radians() | ||||
| @ -724,7 +730,7 @@ mod get_tangential_arc_to_info_tests { | ||||
|  | ||||
|     #[test] | ||||
|     fn test_get_tangential_arc_to_info_obtuse_with_wrap_around() { | ||||
|         let arc_end = (std::f64::consts::PI / 4.0).cos() * 2.0; | ||||
|         let arc_end = libm::cos(std::f64::consts::PI / 4.0) * 2.0; | ||||
|         let result = get_tangential_arc_to_info(TangentialArcInfoInput { | ||||
|             tan_previous_point: [2.0, -4.0], | ||||
|             arc_start_point: [2.0, 0.0], | ||||
| @ -803,7 +809,7 @@ pub(crate) fn get_tangent_point_from_previous_arc( | ||||
|     let tangential_angle = angle_from_old_center_to_arc_start + if last_arc_ccw { -90.0 } else { 90.0 }; | ||||
|     // What is the 10.0 constant doing??? | ||||
|     [ | ||||
|         tangential_angle.to_radians().cos() * 10.0 + last_arc_end[0], | ||||
|         tangential_angle.to_radians().sin() * 10.0 + last_arc_end[1], | ||||
|         libm::cos(tangential_angle.to_radians()) * 10.0 + last_arc_end[0], | ||||
|         libm::sin(tangential_angle.to_radians()) * 10.0 + last_arc_end[1], | ||||
|     ] | ||||
| } | ||||
|  | ||||
| @ -3,8 +3,8 @@ use std::fmt::Write; | ||||
| use crate::{ | ||||
|     parsing::{ | ||||
|         ast::types::{ | ||||
|             Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator, | ||||
|             BinaryPart, BodyItem, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions, | ||||
|             Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, Associativity, BinaryExpression, | ||||
|             BinaryOperator, BinaryPart, BodyItem, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions, | ||||
|             FunctionExpression, IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, | ||||
|             LiteralIdentifier, LiteralValue, MemberExpression, Node, NonCodeNode, NonCodeValue, ObjectExpression, | ||||
|             Parameter, PipeExpression, Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration, | ||||
| @ -710,17 +710,28 @@ impl BinaryExpression { | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         let should_wrap_right = match &self.right { | ||||
|         // It would be better to always preserve the user's parentheses but since we've dropped that | ||||
|         // info from the AST, we bracket expressions as necessary. | ||||
|         let should_wrap_left = match &self.left { | ||||
|             BinaryPart::BinaryExpression(bin_exp) => { | ||||
|                 self.precedence() > bin_exp.precedence() | ||||
|                     || self.operator == BinaryOperator::Sub | ||||
|                     || self.operator == BinaryOperator::Div | ||||
|                     || ((self.precedence() == bin_exp.precedence()) | ||||
|                         && (!(self.operator.associative() && self.operator == bin_exp.operator) | ||||
|                             && self.operator.associativity() == Associativity::Right)) | ||||
|             } | ||||
|             _ => false, | ||||
|         }; | ||||
|  | ||||
|         let should_wrap_left = match &self.left { | ||||
|             BinaryPart::BinaryExpression(bin_exp) => self.precedence() > bin_exp.precedence(), | ||||
|         let should_wrap_right = match &self.right { | ||||
|             BinaryPart::BinaryExpression(bin_exp) => { | ||||
|                 self.precedence() > bin_exp.precedence() | ||||
|                     // These two lines preserve previous reformatting behaviour. | ||||
|                     || self.operator == BinaryOperator::Sub | ||||
|                     || self.operator == BinaryOperator::Div | ||||
|                     || ((self.precedence() == bin_exp.precedence()) | ||||
|                         && (!(self.operator.associative() && self.operator == bin_exp.operator) | ||||
|                             && self.operator.associativity() == Associativity::Left)) | ||||
|             } | ||||
|             _ => false, | ||||
|         }; | ||||
|  | ||||
| @ -2820,4 +2831,36 @@ yo = 'bing' | ||||
|         let recasted = ast.recast(&FormatOptions::new(), 0); | ||||
|         assert_eq!(recasted, code); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn paren_precedence() { | ||||
|         let code = r#"x = 1 - 2 - 3 | ||||
| x = (1 - 2) - 3 | ||||
| x = 1 - (2 - 3) | ||||
| x = 1 + 2 + 3 | ||||
| x = (1 + 2) + 3 | ||||
| x = 1 + (2 + 3) | ||||
| x = 2 * (y % 2) | ||||
| x = (2 * y) % 2 | ||||
| x = 2 % (y * 2) | ||||
| x = (2 % y) * 2 | ||||
| x = 2 * y % 2 | ||||
| "#; | ||||
|  | ||||
|         let expected = r#"x = 1 - 2 - 3 | ||||
| x = 1 - 2 - 3 | ||||
| x = 1 - (2 - 3) | ||||
| x = 1 + 2 + 3 | ||||
| x = 1 + 2 + 3 | ||||
| x = 1 + 2 + 3 | ||||
| x = 2 * (y % 2) | ||||
| x = 2 * y % 2 | ||||
| x = 2 % (y * 2) | ||||
| x = 2 % y * 2 | ||||
| x = 2 * y % 2 | ||||
| "#; | ||||
|         let ast = crate::parsing::top_level_parse(code).unwrap(); | ||||
|         let recasted = ast.recast(&FormatOptions::new(), 0); | ||||
|         assert_eq!(recasted, expected); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -622,7 +622,7 @@ export fn revolve( | ||||
|   axis: Axis2d | Edge, | ||||
|   /// Angle to revolve (in degrees). Default is 360. | ||||
|   angle?: number(Angle), | ||||
|   /// Tolerance for the revolve operation. | ||||
|   /// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | ||||
|   tolerance?: number(Length), | ||||
|   /// If true, the extrusion will happen symmetrically around the sketch. Otherwise, the extrusion will happen on only one side of the sketch. | ||||
|   symmetric?: bool, | ||||
| @ -961,7 +961,7 @@ export fn sweep( | ||||
|   path: Sketch | Helix, | ||||
|   /// If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | ||||
|   sectional?: bool, | ||||
|   /// Tolerance for this operation. | ||||
|   /// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | ||||
|   tolerance?: number(Length), | ||||
|   /// What is the sweep relative to? Can be either 'sketchPlane' or 'trajectoryCurve'. | ||||
|   relativeTo?: string = 'trajectoryCurve', | ||||
| @ -1047,7 +1047,7 @@ export fn loft( | ||||
|   bezApproximateRational?: bool = false, | ||||
|   /// This can be set to override the automatically determined topological base curve, which is usually the first section encountered. | ||||
|   baseCurveIndex?: number(Count), | ||||
|   /// Tolerance for the loft operation. | ||||
|   /// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | ||||
|   tolerance?: number(Length), | ||||
|   /// A named tag for the face at the start of the loft, i.e. the original sketch. | ||||
|   tagStart?: TagDecl, | ||||
| @ -1499,12 +1499,20 @@ export fn profileStartY( | ||||
| export fn involuteCircular( | ||||
|   /// Which sketch should this path be added to? | ||||
|   @sketch: Sketch, | ||||
|   /// The involute is described between two circles, start_radius is the radius of the inner circle. | ||||
|   startRadius: number(Length), | ||||
|   /// The involute is described between two circles, end_radius is the radius of the outer circle. | ||||
|   endRadius: number(Length), | ||||
|   /// The angle to rotate the involute by. A value of zero will produce a curve with a tangent along the x-axis at the start point of the curve. | ||||
|   angle: number(Angle), | ||||
|   /// The involute is described between two circles, startRadius is the radius of the inner circle. | ||||
|   /// Either `startRadius` or `startDiameter` must be given (but not both). | ||||
|   startRadius?: number(Length), | ||||
|   /// The involute is described between two circles, endRadius is the radius of the outer circle. | ||||
|   /// Either `endRadius` or `endDiameter` must be given (but not both). | ||||
|   endRadius?: number(Length), | ||||
|   /// The involute is described between two circles, startDiameter describes the inner circle. | ||||
|   /// Either `startRadius` or `startDiameter` must be given (but not both). | ||||
|   startDiameter?: number(Length), | ||||
|   /// The involute is described between two circles, endDiameter describes the outer circle. | ||||
|   /// Either `endRadius` or `endDiameter` must be given (but not both). | ||||
|   endDiameter?: number(Length), | ||||
|   /// If reverse is true, the segment will start from the end of the involute, otherwise it will start from that start. | ||||
|   reverse?: bool = false, | ||||
|   /// Create a new tag which refers to this line. | ||||
|  | ||||
| @ -70,7 +70,7 @@ export fn fillet( | ||||
|   radius: number(Length), | ||||
|   /// The paths you want to fillet | ||||
|   tags: [Edge; 1+], | ||||
|   /// The tolerance for this fillet | ||||
|   /// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | ||||
|   tolerance?: number(Length), | ||||
|   /// Create a new tag which refers to this fillet | ||||
|   tag?: TagDecl, | ||||
| @ -799,7 +799,7 @@ export fn patternCircular3d( | ||||
| export fn union( | ||||
|   /// The solids to union. | ||||
|   @solids: [Solid; 2+], | ||||
|   /// The tolerance to use for the union operation. | ||||
|   /// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | ||||
|   tolerance?: number(Length), | ||||
| ): [Solid; 1+] {} | ||||
|  | ||||
| @ -857,7 +857,7 @@ export fn union( | ||||
| export fn intersect( | ||||
|   /// The solids to intersect. | ||||
|   @solids: [Solid; 2+], | ||||
|   /// The tolerance to use for the intersection operation. | ||||
|   /// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | ||||
|   tolerance?: number(Length), | ||||
| ): [Solid; 1+] {} | ||||
|  | ||||
| @ -917,7 +917,7 @@ export fn subtract( | ||||
|   @solids: [Solid; 1+], | ||||
|   /// The solids to subtract. | ||||
|   tools: [Solid], | ||||
|   /// The tolerance to use for the subtraction operation. | ||||
|   /// Defines the smallest distance below which two entities are considered coincident, intersecting, coplanar, or similar. For most use cases, it should not be changed from its default value of 10^-7 millimeters. | ||||
|   tolerance?: number(Length), | ||||
| ): [Solid; 1+] {} | ||||
|  | ||||
|  | ||||
| @ -7239,7 +7239,7 @@ description: Operations executed add_lots.kcl | ||||
|       "name": "PI", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 3.142, | ||||
|         "value": 3.141592653589793, | ||||
|         "ty": { | ||||
|           "type": "Unknown" | ||||
|         } | ||||
| @ -7255,7 +7255,7 @@ description: Operations executed add_lots.kcl | ||||
|       "name": "E", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 2.718, | ||||
|         "value": 2.718281828459045, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
| @ -7272,7 +7272,7 @@ description: Operations executed add_lots.kcl | ||||
|       "name": "TAU", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 6.283, | ||||
|         "value": 6.283185307179586, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
|  | ||||
| @ -132,8 +132,8 @@ description: Artifact commands angled_line.kcl | ||||
|         "segment": { | ||||
|           "type": "line", | ||||
|           "end": { | ||||
|             "x": 3.762, | ||||
|             "y": -11.763, | ||||
|             "x": 3.761813572028026, | ||||
|             "y": -11.763131328405109, | ||||
|             "z": 0.0 | ||||
|           }, | ||||
|           "relative": true | ||||
|  | ||||
| @ -94,7 +94,7 @@ description: Operations executed angled_line.kcl | ||||
|       "name": "PI", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 3.142, | ||||
|         "value": 3.141592653589793, | ||||
|         "ty": { | ||||
|           "type": "Unknown" | ||||
|         } | ||||
| @ -110,7 +110,7 @@ description: Operations executed angled_line.kcl | ||||
|       "name": "E", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 2.718, | ||||
|         "value": 2.718281828459045, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
| @ -127,7 +127,7 @@ description: Operations executed angled_line.kcl | ||||
|       "name": "TAU", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 6.283, | ||||
|         "value": 6.283185307179586, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
|  | ||||
| @ -75,7 +75,7 @@ description: Variables in memory after executing angled_line.kcl | ||||
|             "tag": null, | ||||
|             "to": [ | ||||
|               19.93, | ||||
|               15.04 | ||||
|               15.040000000000001 | ||||
|             ], | ||||
|             "type": "ToPoint", | ||||
|             "units": { | ||||
| @ -89,7 +89,7 @@ description: Variables in memory after executing angled_line.kcl | ||||
|             }, | ||||
|             "from": [ | ||||
|               19.93, | ||||
|               15.04 | ||||
|               15.040000000000001 | ||||
|             ], | ||||
|             "tag": { | ||||
|               "commentStart": 133, | ||||
| @ -100,7 +100,7 @@ description: Variables in memory after executing angled_line.kcl | ||||
|             }, | ||||
|             "to": [ | ||||
|               23.08, | ||||
|               5.19 | ||||
|               5.190000000000001 | ||||
|             ], | ||||
|             "type": "ToPoint", | ||||
|             "units": { | ||||
| @ -114,12 +114,12 @@ description: Variables in memory after executing angled_line.kcl | ||||
|             }, | ||||
|             "from": [ | ||||
|               23.08, | ||||
|               5.19 | ||||
|               5.190000000000001 | ||||
|             ], | ||||
|             "tag": null, | ||||
|             "to": [ | ||||
|               7.91, | ||||
|               1.09 | ||||
|               7.909999999999998, | ||||
|               1.0900000000000016 | ||||
|             ], | ||||
|             "type": "ToPoint", | ||||
|             "units": { | ||||
| @ -132,13 +132,13 @@ description: Variables in memory after executing angled_line.kcl | ||||
|               "sourceRange": [] | ||||
|             }, | ||||
|             "from": [ | ||||
|               7.91, | ||||
|               1.09 | ||||
|               7.909999999999998, | ||||
|               1.0900000000000016 | ||||
|             ], | ||||
|             "tag": null, | ||||
|             "to": [ | ||||
|               11.672, | ||||
|               -10.673 | ||||
|               11.671813572028025, | ||||
|               -10.673131328405107 | ||||
|             ], | ||||
|             "type": "ToPoint", | ||||
|             "units": { | ||||
| @ -151,13 +151,13 @@ description: Variables in memory after executing angled_line.kcl | ||||
|               "sourceRange": [] | ||||
|             }, | ||||
|             "from": [ | ||||
|               11.672, | ||||
|               -10.673 | ||||
|               11.671813572028025, | ||||
|               -10.673131328405107 | ||||
|             ], | ||||
|             "tag": null, | ||||
|             "to": [ | ||||
|               -1.348, | ||||
|               -0.643 | ||||
|               -1.3481864279719744, | ||||
|               -0.6431313284051079 | ||||
|             ], | ||||
|             "type": "ToPoint", | ||||
|             "units": { | ||||
| @ -170,8 +170,8 @@ description: Variables in memory after executing angled_line.kcl | ||||
|               "sourceRange": [] | ||||
|             }, | ||||
|             "from": [ | ||||
|               -1.348, | ||||
|               -0.643 | ||||
|               -1.3481864279719744, | ||||
|               -0.6431313284051079 | ||||
|             ], | ||||
|             "tag": null, | ||||
|             "to": [ | ||||
|  | ||||
| @ -398,7 +398,7 @@ description: Operations executed any_type.kcl | ||||
|       "name": "PI", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 3.142, | ||||
|         "value": 3.141592653589793, | ||||
|         "ty": { | ||||
|           "type": "Unknown" | ||||
|         } | ||||
| @ -414,7 +414,7 @@ description: Operations executed any_type.kcl | ||||
|       "name": "E", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 2.718, | ||||
|         "value": 2.718281828459045, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
| @ -431,7 +431,7 @@ description: Operations executed any_type.kcl | ||||
|       "name": "TAU", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 6.283, | ||||
|         "value": 6.283185307179586, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
|  | ||||
| @ -12,7 +12,7 @@ description: Operations executed argument_error.kcl | ||||
|       "name": "PI", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 3.142, | ||||
|         "value": 3.141592653589793, | ||||
|         "ty": { | ||||
|           "type": "Unknown" | ||||
|         } | ||||
| @ -28,7 +28,7 @@ description: Operations executed argument_error.kcl | ||||
|       "name": "E", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 2.718, | ||||
|         "value": 2.718281828459045, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
| @ -45,7 +45,7 @@ description: Operations executed argument_error.kcl | ||||
|       "name": "TAU", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 6.283, | ||||
|         "value": 6.283185307179586, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
|  | ||||
| @ -12,7 +12,7 @@ description: Operations executed array_elem_pop.kcl | ||||
|       "name": "PI", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 3.142, | ||||
|         "value": 3.141592653589793, | ||||
|         "ty": { | ||||
|           "type": "Unknown" | ||||
|         } | ||||
| @ -28,7 +28,7 @@ description: Operations executed array_elem_pop.kcl | ||||
|       "name": "E", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 2.718, | ||||
|         "value": 2.718281828459045, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
| @ -45,7 +45,7 @@ description: Operations executed array_elem_pop.kcl | ||||
|       "name": "TAU", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 6.283, | ||||
|         "value": 6.283185307179586, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
|  | ||||
| @ -12,7 +12,7 @@ description: Operations executed array_elem_pop_empty_fail.kcl | ||||
|       "name": "PI", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 3.142, | ||||
|         "value": 3.141592653589793, | ||||
|         "ty": { | ||||
|           "type": "Unknown" | ||||
|         } | ||||
| @ -28,7 +28,7 @@ description: Operations executed array_elem_pop_empty_fail.kcl | ||||
|       "name": "E", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 2.718, | ||||
|         "value": 2.718281828459045, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
| @ -45,7 +45,7 @@ description: Operations executed array_elem_pop_empty_fail.kcl | ||||
|       "name": "TAU", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 6.283, | ||||
|         "value": 6.283185307179586, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
|  | ||||
| @ -12,7 +12,7 @@ description: Operations executed array_elem_pop_fail.kcl | ||||
|       "name": "PI", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 3.142, | ||||
|         "value": 3.141592653589793, | ||||
|         "ty": { | ||||
|           "type": "Unknown" | ||||
|         } | ||||
| @ -28,7 +28,7 @@ description: Operations executed array_elem_pop_fail.kcl | ||||
|       "name": "E", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 2.718, | ||||
|         "value": 2.718281828459045, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
| @ -45,7 +45,7 @@ description: Operations executed array_elem_pop_fail.kcl | ||||
|       "name": "TAU", | ||||
|       "value": { | ||||
|         "type": "Number", | ||||
|         "value": 6.283, | ||||
|         "value": 6.283185307179586, | ||||
|         "ty": { | ||||
|           "type": "Known", | ||||
|           "type": "Count" | ||||
|  | ||||