Compare commits

..

1 Commits

Author SHA1 Message Date
083103144a add test
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-20 11:04:53 -07:00
663 changed files with 123305 additions and 204989 deletions

View File

@ -7,11 +7,11 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
# If no last run artifact, than run Playwright normally
echo "run playwright normally"
if [[ "$3" == *ubuntu* ]]; then
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:e2e:desktop -- --shard=$1/$2 || true
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:playwright:electron -- --shard=$1/$2 || true
elif [[ "$3" == *windows* ]]; then
npm run test:e2e:desktop -- --grep=@windows --shard=$1/$2 || true
npm run test:playwright:electron -- --grep=@windows --shard=$1/$2 || true
elif [[ "$3" == *macos* ]]; then
npm run test:e2e:desktop -- --grep=@macos --shard=$1/$2 || true
npm run test:playwright:electron -- --grep=@macos --shard=$1/$2 || true
else
echo "Do not run Playwright. Unable to detect os runtime."
exit 1
@ -31,11 +31,11 @@ while [[ $retry -le $max_retries ]]; do
echo "retried=true" >>$GITHUB_OUTPUT
echo "run playwright with last failed tests and retry $retry"
if [[ "$3" == *ubuntu* ]]; then
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:e2e:desktop -- --last-failed || true
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- npm run test:playwright:electron -- --last-failed || true
elif [[ "$3" == *windows* ]]; then
npm run test:e2e:desktop -- --grep=@windows --last-failed || true
npm run test:playwright:electron -- --grep=@windows --last-failed || true
elif [[ "$3" == *macos* ]]; then
npm run test:e2e:desktop -- --grep=@macos --last-failed || true
npm run test:playwright:electron -- --grep=@macos --last-failed || true
else
echo "Do not run playwright. Unable to detect os runtime."
exit 1

View File

@ -1,22 +1,16 @@
name: cargo test
on:
push:
branches:
- main
pull_request:
workflow_dispatch:
schedule:
- cron: 0 * * * * # hourly
permissions:
contents: read
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo test
jobs:
build-test-artifacts:
name: Build test artifacts
@ -199,7 +193,7 @@ jobs:
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: e2e:kcl
CI_SUITE: unit:kcl
run-internal-kcl-samples:
name: cargo test (internal-kcl-samples)
runs-on:

View File

@ -1,5 +1,4 @@
name: E2E Tests
on:
push:
branches:
@ -20,11 +19,9 @@ permissions:
jobs:
prepare-wasm:
# separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
steps:
- uses: actions/checkout@v4
- id: filter
@ -102,13 +99,10 @@ jobs:
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
snapshots:
needs: [prepare-wasm]
name: playwright:snapshots:ubuntu
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
name: e2e:snapshots
needs: [prepare-wasm]
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
@ -136,9 +130,10 @@ jobs:
cache: 'npm'
- name: Install dependencies
id: deps-install
run: npm install
- name: Download browser cache
- name: Cache browsers
uses: actions/cache@v4
with:
path: |
@ -148,7 +143,7 @@ jobs:
- name: Install browsers
run: npm run playwright install --with-deps
- name: npm run test:snapshots
- name: Test snapshots
uses: nick-fields/retry@v3.0.2
with:
shell: bash
@ -161,7 +156,7 @@ jobs:
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: e2e:snapshots
CI_SUITE: snapshots
TARGET: web
- name: Update snapshots
@ -173,7 +168,7 @@ jobs:
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: e2e:snapshots
CI_SUITE: snapshots
TARGET: web
- uses: actions/upload-artifact@v4
@ -211,93 +206,12 @@ jobs:
git push
git push origin ${{ github.head_ref }}
web:
electron:
needs: [prepare-wasm]
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
name: e2e:web (${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }})
timeout-minutes: 60
env:
OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- uses: actions/download-artifact@v4
name: prepared-wasm
- name: Copy prepared Wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install dependencies
run: npm install
- name: Download browser cache
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright/
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- name: Install browsers
run: npm run playwright install --with-deps
- name: Start Vector
if: ${{ !contains(matrix.os, 'windows') }}
run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh
env:
GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }}
OS_NAME: ${{ env.OS_NAME }}
- name: npm run test:e2e:web
uses: nick-fields/retry@v3.0.2
with:
shell: bash
command: npm run test:e2e:web
timeout_minutes: 5
max_attempts: 5
env:
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
TAB_API_URL: ${{ secrets.TAB_API_URL }}
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: e2e:web
TARGET: web
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }}
with:
path: playwright-report/
include-hidden-files: true
retention-days: 30
overwrite: true
desktop:
needs: [prepare-wasm]
name: playwright:electron:${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }} (shard ${{ matrix.shardIndex }})
strategy:
fail-fast: false
matrix:
@ -340,17 +254,13 @@ jobs:
shardIndex: 2
shardTotal: 2
runs-on: ${{ matrix.os }}
name: e2e:desktop (${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}, shard ${{ matrix.shardIndex }})
env:
OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
name: prepared-wasm
- name: Copy prepared Wasm
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
@ -366,16 +276,19 @@ jobs:
id: deps-install
run: npm install
- name: Download browser cache
- name: Cache Playwright Browsers
uses: actions/cache@v4
with:
path: |
~/.cache/ms-playwright/
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- name: Install browsers
- name: Install Playwright Browsers
run: npm run playwright install --with-deps
- name: Build web
run: npm run tronb:vite:dev
- name: Start Vector
if: ${{ !contains(matrix.os, 'windows') }}
run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh
@ -383,9 +296,6 @@ jobs:
GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }}
OS_NAME: ${{ env.OS_NAME }}
- name: Build app
run: npm run tronb:vite:dev
- uses: actions/download-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }}
continue-on-error: true
@ -393,7 +303,7 @@ jobs:
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
- name: npm run test:e2e:desktop
- name: Run playwright/electron flow (with retries)
id: retry
if: ${{ !cancelled() && steps.deps-install.outcome == 'success' }}
uses: nick-fields/retry@v3.0.2
@ -409,7 +319,6 @@ jobs:
TAB_API_KEY: ${{ secrets.TAB_API_KEY }}
CI_COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
CI_PR_NUMBER: ${{ github.event.pull_request.number }}
CI_SUITE: e2e:desktop
TARGET: desktop
- uses: actions/upload-artifact@v4

View File

@ -122,24 +122,25 @@ https://github.com/KittyCAD/modeling-app/issues/new
#### 2. Push a new tag
Decide on a `v`-prefixed semver `VERSION` (e.g. `v1.2.3`) with the team and tag the repo on the latest main:
Create a new tag and push it to the repo. The `semantic-release.sh` script will automatically bump the minor part, which we use the most. For instance going from `v0.27.0` to `v0.28.0`.
```
git tag $VERSION --message=""
git push origin $VERSION
VERSION=$(./scripts/semantic-release.sh)
git tag $VERSION
git push origin --tags
```
This will trigger the `build-apps` workflow to set the version, build & sign the apps, and generate release files.
This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files.
The workflow should be listed right away [in this list](https://github.com/KittyCAD/modeling-app/actions/workflows/build-apps.yml?query=event%3Apush).
The workflow should be listed right away [in this list](https://github.com/KittyCAD/modeling-app/actions/workflows/build-apps.yml?query=event%3Apush)).
#### 3. Manually test artifacts
##### Release builds
The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in step 2).
The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in 2.).
Manually test against [this list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue.
Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue.
A prompt should show up asking for a downgrade to the last release version. Running through that at the end of testing
and making sure the current release candidate has the ability to be updated to what electron-updater points to is critical,
@ -157,20 +158,15 @@ If the prompt doesn't show up, start the app in command line to grab the electro
./Zoo Design Studio-{version}-{arch}-linux.AppImage
```
#### 4. Bump the KCL version
#### 4. Publish the release
Follow the instructions [here](./rust/README.md) to publish new crates.
This ensures that the KCL accepted by the app is also accepted by the CLI.
Head over to https://github.com/KittyCAD/modeling-app/releases/new, pick the newly created tag and type it in the _Release title_ field as well.
#### 5. Publish the release
Hit _Generate release notes_ as a starting point to discuss the changelog in the issue. Once done, make sure _Set as the latest release_ is checked, and hit _Publish release_.
Head over to https://github.com/KittyCAD/modeling-app/releases/new, pick the newly created tag and type it in the **Release title** field as well.
A new `publish-apps-release` will kick in and you should be able to find it [here](https://github.com/KittyCAD/modeling-app/actions?query=event%3Arelease). On success, the files will be uploaded to the public bucket as well as to the GitHub release, and the announcement on Discord will be sent.
Click **Generate release notes** as a starting point to discuss the changelog in the issue. Once done, make sure **Set as the latest release** is checked, and click **Publish release**.
A new `publish-apps-release` workflow will start and you should be able to find it [here](https://github.com/KittyCAD/modeling-app/actions?query=event%3Arelease). On success, the files will be uploaded to the public bucket as well as to the GitHub release, and the announcement on Discord will be sent.
#### 6. Close the issue
#### 5. Close the issue
If everything is well and the release is out to the public, the issue tracking the release shall be closed.
@ -205,7 +201,7 @@ Prepare these system dependencies:
#### Snapshot tests (Google Chrome on Ubuntu only)
Only Ubuntu and Google Chrome is supported for the set of tests evaluating screenshot snapshots.
Only Ubunu and Google Chrome is supported for the set of tests evaluating screenshot snapshots.
If you don't run Ubuntu locally or in a VM, you may use a GitHub Codespace.
```
npm run playwright -- install chrome
@ -213,21 +209,14 @@ npm run test:snapshots
```
You may use `-- --update-snapshots` as needed.
#### Desktop tests (Electron on all platforms)
#### Electron flow tests (Chromium on Ubuntu, macOS, Windows)
```
npm run playwright -- install chromium
npm run test:e2e:desktop:local
npm run test:playwright:electron:local
```
You may use `-- -g "my test"` to match specific test titles, or `-- path/to/file.spec.ts` for a test file.
#### Web tests (Google Chrome on all platforms)
```
npm run test:e2e:web
```
#### Debugger
However, if you want a debugger I recommend using VSCode and the `playwright` extension, as the above command is a cruder debugger that steps into every function call which is annoying.

View File

@ -4,7 +4,7 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
## Windows
1. Download the [Zoo Design Studio installer](https://zoo.dev/design-studio/download) for Windows and for your processor type.
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
2. Once downloaded, run the installer `Zoo Design Studio-{version}-{arch}-win.exe` which should take a few seconds.
@ -12,7 +12,7 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
## macOS
1. Download the [Zoo Design Studio installer](https://zoo.dev/design-studio/download) for macOS and for your processor type.
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
2. Once downloaded, open the disk image `Zoo Design Studio-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
@ -21,7 +21,7 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
## Linux
1. Download the [Zoo Design Studio installer](https://zoo.dev/design-studio/download) for Linux and for your processor type.
1. Download the [Zoo Design Studio installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
- On Ubuntu, install the FUSE library with these commands in a terminal.

View File

@ -120,18 +120,19 @@ test-e2e: test-e2e-$(TARGET)
.PHONY: test-e2e-web
test-e2e-web: install build ## Run the web e2e tests
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
ifdef E2E_GREP
npm run test:e2e:web -- --headed --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
npm run chrome:test -- --headed --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
else
npm run test:e2e:web -- --headed --workers='100%'
npm run chrome:test -- --headed --workers='100%'
endif
.PHONY: test-e2e-desktop
test-e2e-desktop: install build ## Run the desktop e2e tests
ifdef E2E_GREP
npm run test:e2e:desktop -- --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
npm run test:playwright:electron -- --grep="$(E2E_GREP)" --max-failures=$(E2E_FAILURES)
else
npm run test:e2e:desktop -- --workers='100%'
npm run test:playwright:electron -- --workers='100%'
endif
###############################################################################

View File

@ -2,7 +2,7 @@
# Zoo Design Studio
[zoo.dev/design-studio](https://zoo.dev/design-studio)
[zoo.dev/modeling-app](https://zoo.dev/modeling-app)
A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev).
@ -40,8 +40,12 @@ The 3D view in Design Studio is just a video stream from our hosted geometry eng
## Get Started
We recommend downloading the latest application binary from our [website](https://zoo.dev/design-studio/download). If you don't see your platform or architecture supported there, please file an issue. See the [installation guide](INSTALL.md) for additional instructions.
We recommend downloading the latest application binary from our [releases](https://github.com/KittyCAD/modeling-app/releases) page. If you don't see your platform or architecture supported there, please file an issue.
## Developing
Finally, if you'd like to run a development build or contribute to the project, please visit our [contributor guide](CONTRIBUTING.md) to get started. To contribute to the KittyCAD Language, see the dedicated [readme](rust/kcl-lib/README.md) for KCL.
Finally, if you'd like to run a development build or contribute to the project, please visit our [contributor guide](CONTRIBUTING.md) to get started.
## KCL
To contribute to the KittyCAD Language, see the [README](https://github.com/KittyCAD/modeling-app/tree/main/rust/kcl-lib) for KCL.

View File

@ -16,16 +16,15 @@ There are some useful functions for working with arrays in the standard library,
Arrays have their own types: `[T]` where `T` is the type of the elements of the array, for example, `[string]` means an array of `string`s and `[any]` means an array of any values.
Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; n+]` denotes an array whose length is at least `n`. The common case for that is `[T; 1+]`, i.e., a non-empty array. E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types.
Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; 1+]` denotes an array whose length is at least one (i.e., a non-empty array). E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types.
## Ranges
Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. A range which is exclusive of its end is written with `<end`. Examples:
Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. Examples:
```kcl,norun
[0..3] // [0, 1, 2, 3]
[3..10] // [3, 4, 5, 6, 7, 8, 9, 10]
[3..<10] // [3, 4, 5, 6, 7, 8, 9]
x = 2
[x..x+1] // [2, 3]
```

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,6 @@ arc(
angleStart?: number,
angleEnd?: number,
radius?: number,
diameter?: number,
interiorAbsolute?: Point2d,
endAbsolute?: Point2d,
tag?: TagDeclarator,
@ -31,8 +30,7 @@ Unless this makes a lot of sense and feels like what you're looking for to const
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | Which sketch should this path be added to? | Yes |
| `angleStart` | [`number`](/docs/kcl-std/types/std-types-number) | Where along the circle should this arc start? | No |
| `angleEnd` | [`number`](/docs/kcl-std/types/std-types-number) | Where along the circle should this arc end? | No |
| `radius` | [`number`](/docs/kcl-std/types/std-types-number) | How large should the circle be? Incompatible with `diameter`. | No |
| `diameter` | [`number`](/docs/kcl-std/types/std-types-number) | How large should the circle be? Incompatible with `radius`. | No |
| `radius` | [`number`](/docs/kcl-std/types/std-types-number) | How large should the circle be? | No |
| `interiorAbsolute` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | Any point between the arc's start and end? Requires `endAbsolute`. Incompatible with `angleStart` or `angleEnd` | No |
| `endAbsolute` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | Where should this arc end? Requires `interiorAbsolute`. Incompatible with `angleStart` or `angleEnd` | No |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | Create a new tag which refers to this line | No |

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -11,8 +11,7 @@ layout: manual
circle(
@sketch_or_surface: Sketch | Plane | Face,
center: Point2d,
radius?: number(Length),
diameter?: number(Length),
radius: number(Length),
tag?: tag,
): Sketch
```
@ -26,8 +25,7 @@ the provided (x, y) origin point.
|----------|------|-------------|----------|
| `sketch_or_surface` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) or [`Plane`](/docs/kcl-std/types/std-types-Plane) or [`Face`](/docs/kcl-std/types/std-types-Face) | Sketch to extend, or plane or surface to sketch on. | Yes |
| `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center of the circle. | Yes |
| `radius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The radius of the circle. Incompatible with `diameter`. | No |
| `diameter` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The diameter of the circle. Incompatible with `radius`. | No |
| `radius` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The radius of the circle. | Yes |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | Create a new tag which refers to this circle. | No |
### Returns
@ -53,7 +51,7 @@ exampleSketch = startSketchOn(XZ)
|> line(end = [0, 30])
|> line(end = [-30, 0])
|> close()
|> subtract2d(tool = circle(center = [0, 15], diameter = 10))
|> subtract2d(tool = circle(center = [0, 15], radius = 5))
example = extrude(exampleSketch, length = 5)
```

View File

@ -12,7 +12,7 @@ patternTransform(
@solids: [Solid; 1+],
instances: number(_),
transform: fn(number(_)): { },
useOriginal?: bool,
useOriginal?: boolean,
): [Solid; 1+]
```
@ -61,7 +61,7 @@ Its properties are:
| `solids` | [`[Solid; 1+]`](/docs/kcl-std/types/std-types-Solid) | The solid(s) to duplicate. | Yes |
| `instances` | [`number(_)`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `transform` | [`fn(number(_)): { }`](/docs/kcl-std/types/std-types-fn) | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. | No |
| `useOriginal` | `boolean` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. | No |
### Returns

View File

@ -8,7 +8,7 @@ layout: manual
Get the shared edge between two faces.
```kcl
getCommonEdge(faces: [tag; 2]): Edge
getCommonEdge(faces: [TagIdentifier]): Uuid
```
@ -17,11 +17,11 @@ getCommonEdge(faces: [tag; 2]): Edge
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `faces` | `[tag; 2]` | The tags of the faces you want to find the common edge between. | Yes |
| `faces` | [`[TagIdentifier]`](/docs/kcl-lang/types#TagIdentifier) | The tags of the faces you want to find the common edge between | Yes |
### Returns
[`Edge`](/docs/kcl-std/types/std-types-Edge) - An edge of a solid.
`Uuid`
### Examples
@ -29,16 +29,17 @@ getCommonEdge(faces: [tag; 2]): Edge
```kcl
// Get an edge shared between two faces, created after a chamfer.
scale = 20
part001 = startSketchOn(XY)
|> startProfile(at = [0, 0])
|> line(end = [0, scale])
|> line(end = [scale, 0])
|> line(end = [0, -scale])
|> close(tag = $line0)
|> extrude(length = 20, tagEnd = $end0)
// We tag the chamfer to reference it later.
|> chamfer(length = 10, tags = [getOppositeEdge(line0)], tag = $chamfer0)
|> startProfile(at = [0, 0])
|> line(end = [0, scale])
|> line(end = [scale, 0])
|> line(end = [0, -scale])
|> close(tag = $line0)
|> extrude(length = 20, tagEnd = $end0)
// We tag the chamfer to reference it later.
|> chamfer(length = 10, tags = [getOppositeEdge(line0)], tag = $chamfer0)
// Get the shared edge between the chamfer and the extrusion.
commonEdge = getCommonEdge(faces = [chamfer0, end0])

File diff suppressed because one or more lines are too long

View File

@ -9,14 +9,13 @@ layout: manual
### Functions
* [**std**](/docs/kcl-std/modules/std)
* [`appearance`](/docs/kcl-std/appearance)
* [`assert`](/docs/kcl-std/assert)
* [`assertIs`](/docs/kcl-std/assertIs)
* [`clone`](/docs/kcl-std/functions/std-clone)
* [`helix`](/docs/kcl-std/functions/std-helix)
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d)
* [**std::appearance**](/docs/kcl-std/modules/std-appearance)
* [`appearance::hexString`](/docs/kcl-std/functions/std-appearance-hexString)
* [**std::array**](/docs/kcl-std/modules/std-array)
* [`map`](/docs/kcl-std/functions/std-array-map)
* [`pop`](/docs/kcl-std/functions/std-array-pop)
@ -56,10 +55,10 @@ layout: manual
* [`circleThreePoint`](/docs/kcl-std/circleThreePoint)
* [`close`](/docs/kcl-std/close)
* [`extrude`](/docs/kcl-std/extrude)
* [`getCommonEdge`](/docs/kcl-std/functions/std-sketch-getCommonEdge)
* [`getNextAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getNextAdjacentEdge)
* [`getOppositeEdge`](/docs/kcl-std/functions/std-sketch-getOppositeEdge)
* [`getPreviousAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getPreviousAdjacentEdge)
* [`getCommonEdge`](/docs/kcl-std/getCommonEdge)
* [`getNextAdjacentEdge`](/docs/kcl-std/getNextAdjacentEdge)
* [`getOppositeEdge`](/docs/kcl-std/getOppositeEdge)
* [`getPreviousAdjacentEdge`](/docs/kcl-std/getPreviousAdjacentEdge)
* [`involuteCircular`](/docs/kcl-std/involuteCircular)
* [`lastSegX`](/docs/kcl-std/lastSegX)
* [`lastSegY`](/docs/kcl-std/lastSegY)
@ -89,22 +88,21 @@ layout: manual
* [`xLine`](/docs/kcl-std/xLine)
* [`yLine`](/docs/kcl-std/yLine)
* [**std::solid**](/docs/kcl-std/modules/std-solid)
* [`appearance`](/docs/kcl-std/functions/std-solid-appearance)
* [`chamfer`](/docs/kcl-std/functions/std-solid-chamfer)
* [`fillet`](/docs/kcl-std/functions/std-solid-fillet)
* [`hollow`](/docs/kcl-std/functions/std-solid-hollow)
* [`intersect`](/docs/kcl-std/functions/std-solid-intersect)
* [`patternCircular3d`](/docs/kcl-std/functions/std-solid-patternCircular3d)
* [`patternLinear3d`](/docs/kcl-std/functions/std-solid-patternLinear3d)
* [`intersect`](/docs/kcl-std/intersect)
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform)
* [`shell`](/docs/kcl-std/functions/std-solid-shell)
* [`subtract`](/docs/kcl-std/functions/std-solid-subtract)
* [`union`](/docs/kcl-std/functions/std-solid-union)
* [`subtract`](/docs/kcl-std/subtract)
* [`union`](/docs/kcl-std/union)
* [**std::transform**](/docs/kcl-std/modules/std-transform)
* [`mirror2d`](/docs/kcl-std/functions/std-transform-mirror2d)
* [`rotate`](/docs/kcl-std/functions/std-transform-rotate)
* [`scale`](/docs/kcl-std/functions/std-transform-scale)
* [`translate`](/docs/kcl-std/functions/std-transform-translate)
* [`rotate`](/docs/kcl-std/rotate)
* [`scale`](/docs/kcl-std/scale)
* [`translate`](/docs/kcl-std/translate)
* [**std::units**](/docs/kcl-std/modules/std-units)
* [`units::toCentimeters`](/docs/kcl-std/functions/std-units-toCentimeters)
* [`units::toDegrees`](/docs/kcl-std/functions/std-units-toDegrees)

View File

@ -1,36 +1,31 @@
---
title: "intersect"
subtitle: "Function in std::solid"
excerpt: ""
excerpt: "Intersect returns the shared volume between multiple solids, preserving only overlapping regions."
layout: manual
---
Intersect returns the shared volume between multiple solids, preserving only overlapping regions.
```kcl
intersect(
@solids: [Solid; 2+],
tolerance?: number(Length),
): [Solid; 1+]
@solids: [Solid],
tolerance?: number,
): [Solid]
```
Intersect returns the shared volume between multiple solids, preserving only
overlapping regions.
Intersect computes the geometric intersection of multiple solid bodies,
returning a new solid representing the volume that is common to all input
solids. This operation is useful for determining shared material regions,
verifying fit, and analyzing overlapping geometries in assemblies.
Intersect computes the geometric intersection of multiple solid bodies, returning a new solid representing the volume that is common to all input solids. This operation is useful for determining shared material regions, verifying fit, and analyzing overlapping geometries in assemblies.
### Arguments
| 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 |
| `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) | The solids to intersect. | Yes |
| `tolerance` | [`number`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the intersection operation. | No |
### Returns
[`[Solid; 1+]`](/docs/kcl-std/types/std-types-Solid)
[`[Solid]`](/docs/kcl-std/types/std-types-Solid)
### Examples
@ -38,19 +33,20 @@ verifying fit, and analyzing overlapping geometries in assemblies.
```kcl
// Intersect two cubes using the stdlib functions.
fn cube(center, size) {
return startSketchOn(XY)
|> startProfile(at = [center[0] - size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
return startSketchOn(XY)
|> startProfile(at = [center[0] - size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
}
part001 = cube(center = [0, 0], size = 10)
part002 = cube(center = [7, 3], size = 5)
|> translate(z = 1)
|> translate(z = 1)
intersectedPart = intersect([part001, part002])
```
@ -62,19 +58,20 @@ intersectedPart = intersect([part001, part002])
// NOTE: This will not work when using codemods through the UI.
// Codemods will generate the stdlib function call instead.
fn cube(center, size) {
return startSketchOn(XY)
|> startProfile(at = [center[0] - size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
return startSketchOn(XY)
|> startProfile(at = [center[0] - size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
}
part001 = cube(center = [0, 0], size = 10)
part002 = cube(center = [7, 3], size = 5)
|> translate(z = 1)
|> translate(z = 1)
// This is the equivalent of: intersect([part001, part002])
intersectedPart = part001 & part002

View File

@ -1,16 +0,0 @@
---
title: "appearance"
subtitle: "Module in std"
excerpt: ""
layout: manual
---
## Functions and constants
* [`appearance::hexString`](/docs/kcl-std/functions/std-appearance-hexString)

View File

@ -20,10 +20,10 @@ This module contains functions for creating and manipulating sketches, and makin
* [`circleThreePoint`](/docs/kcl-std/circleThreePoint)
* [`close`](/docs/kcl-std/close)
* [`extrude`](/docs/kcl-std/extrude)
* [`getCommonEdge`](/docs/kcl-std/functions/std-sketch-getCommonEdge)
* [`getNextAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getNextAdjacentEdge)
* [`getOppositeEdge`](/docs/kcl-std/functions/std-sketch-getOppositeEdge)
* [`getPreviousAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getPreviousAdjacentEdge)
* [`getCommonEdge`](/docs/kcl-std/getCommonEdge)
* [`getNextAdjacentEdge`](/docs/kcl-std/getNextAdjacentEdge)
* [`getOppositeEdge`](/docs/kcl-std/getOppositeEdge)
* [`getPreviousAdjacentEdge`](/docs/kcl-std/getPreviousAdjacentEdge)
* [`involuteCircular`](/docs/kcl-std/involuteCircular)
* [`lastSegX`](/docs/kcl-std/lastSegX)
* [`lastSegY`](/docs/kcl-std/lastSegY)

View File

@ -12,15 +12,14 @@ This module contains functions for modifying solids, e.g., by adding a fillet or
## Functions and constants
* [`appearance`](/docs/kcl-std/functions/std-solid-appearance)
* [`chamfer`](/docs/kcl-std/functions/std-solid-chamfer)
* [`fillet`](/docs/kcl-std/functions/std-solid-fillet)
* [`hollow`](/docs/kcl-std/functions/std-solid-hollow)
* [`intersect`](/docs/kcl-std/functions/std-solid-intersect)
* [`patternCircular3d`](/docs/kcl-std/functions/std-solid-patternCircular3d)
* [`patternLinear3d`](/docs/kcl-std/functions/std-solid-patternLinear3d)
* [`intersect`](/docs/kcl-std/intersect)
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform)
* [`shell`](/docs/kcl-std/functions/std-solid-shell)
* [`subtract`](/docs/kcl-std/functions/std-solid-subtract)
* [`union`](/docs/kcl-std/functions/std-solid-union)
* [`subtract`](/docs/kcl-std/subtract)
* [`union`](/docs/kcl-std/union)

View File

@ -13,7 +13,7 @@ This module contains functions for transforming sketches and solids.
## Functions and constants
* [`mirror2d`](/docs/kcl-std/functions/std-transform-mirror2d)
* [`rotate`](/docs/kcl-std/functions/std-transform-rotate)
* [`scale`](/docs/kcl-std/functions/std-transform-scale)
* [`translate`](/docs/kcl-std/functions/std-transform-translate)
* [`rotate`](/docs/kcl-std/rotate)
* [`scale`](/docs/kcl-std/scale)
* [`translate`](/docs/kcl-std/translate)

View File

@ -15,7 +15,6 @@ You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL gui
## Modules
* [`appearance::appearance`](/docs/kcl-std/modules/std-appearance)
* [`array`](/docs/kcl-std/modules/std-array)
* [`math`](/docs/kcl-std/modules/std-math)
* [`sketch`](/docs/kcl-std/modules/std-sketch)
@ -36,6 +35,7 @@ You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL gui
* [`Y`](/docs/kcl-std/consts/std-Y)
* [`YZ`](/docs/kcl-std/consts/std-YZ)
* [`Z`](/docs/kcl-std/consts/std-Z)
* [`appearance`](/docs/kcl-std/appearance)
* [`assert`](/docs/kcl-std/assert)
* [`assertIs`](/docs/kcl-std/assertIs)
* [`clone`](/docs/kcl-std/functions/std-clone)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -13,7 +13,6 @@ tangentialArc(
endAbsolute?: Point2d,
end?: Point2d,
radius?: number,
diameter?: number,
angle?: number,
tag?: TagDeclarator,
): Sketch
@ -28,8 +27,7 @@ When using radius and angle, draw a curved line segment along part of an imagina
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | Which sketch should this path be added to? | Yes |
| `endAbsolute` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | Which absolute point should this arc go to? Incompatible with `end`, `radius`, and `offset`. | No |
| `end` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | How far away (along the X and Y axes) should this arc go? Incompatible with `endAbsolute`, `radius`, and `offset`. | No |
| `radius` | [`number`](/docs/kcl-std/types/std-types-number) | Radius of the imaginary circle. `angle` must be given. Incompatible with `end` and `endAbsolute` and `diameter`. | No |
| `diameter` | [`number`](/docs/kcl-std/types/std-types-number) | Diameter of the imaginary circle. `angle` must be given. Incompatible with `end` and `endAbsolute` and `radius`. | No |
| `radius` | [`number`](/docs/kcl-std/types/std-types-number) | Radius of the imaginary circle. `angle` must be given. Incompatible with `end` and `endAbsolute`. | No |
| `angle` | [`number`](/docs/kcl-std/types/std-types-number) | Offset of the arc in degrees. `radius` must be given. Incompatible with `end` and `endAbsolute`. | No |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator) | Create a new tag which refers to this arc | No |

File diff suppressed because one or more lines are too long

View File

@ -9,9 +9,9 @@ Union two or more solids into a single solid.
```kcl
union(
@solids: [Solid; 2+],
tolerance?: number(Length),
): [Solid; 1+]
@solids: [Solid],
tolerance?: number,
): [Solid]
```
@ -20,12 +20,12 @@ 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 |
| `solids` | [`[Solid]`](/docs/kcl-std/types/std-types-Solid) | The solids to union. | Yes |
| `tolerance` | [`number`](/docs/kcl-std/types/std-types-number) | The tolerance to use for the union operation. | No |
### Returns
[`[Solid; 1+]`](/docs/kcl-std/types/std-types-Solid)
[`[Solid]`](/docs/kcl-std/types/std-types-Solid)
### Examples
@ -33,19 +33,20 @@ union(
```kcl
// Union two cubes using the stdlib functions.
fn cube(center, size) {
return startSketchOn(XY)
|> startProfile(at = [center[0] - size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
return startSketchOn(XY)
|> startProfile(at = [center[0] - size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
}
part001 = cube(center = [0, 0], size = 10)
part002 = cube(center = [7, 3], size = 5)
|> translate(z = 1)
|> translate(z = 1)
unionedPart = union([part001, part002])
```
@ -57,19 +58,20 @@ unionedPart = union([part001, part002])
// NOTE: This will not work when using codemods through the UI.
// Codemods will generate the stdlib function call instead.
fn cube(center, size) {
return startSketchOn(XY)
|> startProfile(at = [center[0] - size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
return startSketchOn(XY)
|> startProfile(at = [center[0] - size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
}
part001 = cube(center = [0, 0], size = 10)
part002 = cube(center = [7, 3], size = 5)
|> translate(z = 1)
|> translate(z = 1)
// This is the equivalent of: union([part001, part002])
unionedPart = part001 + part002
@ -82,22 +84,23 @@ unionedPart = part001 + part002
// NOTE: This will not work when using codemods through the UI.
// Codemods will generate the stdlib function call instead.
fn cube(center, size) {
return startSketchOn(XY)
|> startProfile(at = [center[0] - size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
return startSketchOn(XY)
|> startProfile(at = [center[0] - size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] - size])
|> line(endAbsolute = [center[0] + size, center[1] + size])
|> line(endAbsolute = [center[0] - size, center[1] + size])
|> close()
|> extrude(length = 10)
}
part001 = cube(center = [0, 0], size = 10)
part002 = cube(center = [7, 3], size = 5)
|> translate(z = 1)
|> translate(z = 1)
// This is the equivalent of: union([part001, part002])
// Programmers will understand `|` as a union operation, but mechanical engineers
// This is the equivalent of: union([part001, part002])
// Programmers will understand `|` as a union operation, but mechanical engineers
// will understand `+`, we made both work.
unionedPart = part001 | part002
```

View File

@ -5,7 +5,7 @@ import * as fsp from 'fs/promises'
test.describe('Electron app header tests', () => {
test(
'Open Command Palette button has correct shortcut',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page }, testInfo) => {
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -30,7 +30,7 @@ test.describe('Electron app header tests', () => {
test(
'User settings has correct shortcut',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, toolbar }, testInfo) => {
await page.setBodyDimensions({ width: 1200, height: 500 })

View File

@ -4,7 +4,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Authentication tests', () => {
test(
`The user can sign out and back in`,
{ tag: ['@desktop'] },
{ tag: ['@electron'] },
async ({ page, homePage, signInPage, toolbar, tronApp }) => {
if (!tronApp) {
fail()

View File

@ -78,10 +78,11 @@ extrude001 = extrude(sketch001, length = 5)`
// Delete a character to break the KCL
await editor.openPane()
await editor.scrollToText('extrude(%, length = width)')
await page.getByText('extrude(%, length = width)').click()
await page.keyboard.press(')')
await editor.scrollToText('bracketLeg1Sketch, length = thickness)')
await page
.getByText('extrude(bracketLeg1Sketch, length = thickness)')
.click()
await page.keyboard.press('Backspace')
// Ensure that a badge appears on the button
await expect(codePaneButtonHolder).toContainText('notification')
@ -98,11 +99,16 @@ extrude001 = extrude(sketch001, length = 5)`
await page.waitForTimeout(500)
// Ensure that a badge appears on the button
await expect(codePaneButtonHolder).toContainText('notification')
// Ensure we have no errors in the gutter.
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
// Open the code pane
await editor.openPane()
// Go to our problematic code again
await editor.scrollToText('extrude(%, length = w')
// Go to our problematic code again (missing closing paren!)
await editor.scrollToText('extrude(bracketLeg1Sketch, length = thickness')
// Ensure that a badge appears on the button
await expect(codePaneButtonHolder).toContainText('notification')
@ -275,7 +281,7 @@ middle()`)
test(
'Opening multiple panes persists when switching projects',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
// Setup multiple projects.
await context.folderSetupFn(async (dir) => {
@ -346,7 +352,7 @@ test(
test(
'external change of file contents are reflected in editor',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const PROJECT_DIR_NAME = 'lee-was-here'
const { dir: projectsDir } = await context.folderSetupFn(async (dir) => {

View File

@ -45,16 +45,15 @@ test.describe('Command bar tests', () => {
await cmdBar.expectState({
stage: 'arguments',
commandName: 'Extrude',
currentArgKey: 'sketches',
currentArgValue: '',
currentArgKey: 'length',
currentArgValue: '5',
headerArguments: {
Profiles: '',
Profiles: '1 profile',
Length: '',
},
highlightedHeaderArg: 'Profiles',
highlightedHeaderArg: 'length',
})
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
commandName: 'Extrude',
@ -519,7 +518,7 @@ test.describe('Command bar tests', () => {
`Zoom to fit to shared model on web`,
{ tag: ['@web'] },
async ({ page, scene }) => {
if (process.env.TARGET !== 'web') {
if (process.env.PLATFORM !== 'web') {
// This test is web-only
// TODO: re-enable on CI as part of a new @web test suite
return

View File

@ -10,7 +10,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
test(
'export works on the first try',
{ tag: ['@desktop', '@macos', '@windows', '@skipLocalEngine'] },
{ tag: ['@electron', '@macos', '@windows', '@skipLocalEngine'] },
async ({ page, context, scene, tronApp, cmdBar }, testInfo) => {
if (!tronApp) {
fail()

View File

@ -1001,7 +1001,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfile(%, at = [0, 12])
|> startProfile(%, at = [3.14, 12])
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
)
@ -1076,7 +1076,7 @@ sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfile(%, at = [0, 12])
|> startProfile(%, at = [3.14, 12])
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
)
})
@ -1134,7 +1134,6 @@ sketch001 = startSketchOn(XZ)
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
await page.waitForTimeout(200)
await toolbar.extrudeButton.click()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'length',
@ -1337,7 +1336,7 @@ sketch001 = startSketchOn(XZ)
test(
`Can import a local OBJ file`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, context }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'cube')
@ -1356,7 +1355,9 @@ sketch001 = startSketchOn(XZ)
const u = await getUtils(page)
const projectLink = page.getByRole('link', { name: 'cube' })
const gizmo = page.locator('[aria-label*=gizmo]')
const resetCameraButton = page.getByRole('button', { name: 'Reset view' })
const resetCameraButton = page.getByRole('button', {
name: 'Reset view',
})
const locationToHaveColor = async (
position: { x: number; y: number },
color: [number, number, number]
@ -1590,38 +1591,4 @@ sketch001 = startSketchOn(XZ)
await expect(page.getByTestId('center-rectangle')).toBeVisible()
})
})
test('syntax errors still show when reopening KCL pane', async ({
page,
homePage,
scene,
cmdBar,
}) => {
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
// Wait for connection, this is especially important for this test, because safeParse is invoked when
// connection is established which would interfere with the test if it happened during later steps.
await scene.connectionEstablished()
await scene.settled(cmdBar)
// Code with no error
await u.codeLocator.fill(`x = 7`)
await page.waitForTimeout(200) // allow some time for the error to show potentially
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
// Code with error
await u.codeLocator.fill(`x 7`)
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(1)
// Close and reopen KCL code panel
await u.closeKclCodePanel()
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0) // error disappears on close
await u.openKclCodePanel()
// Verify error is still visible
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(1)
})
})

View File

@ -57,7 +57,7 @@ sketch003 = startSketchOn(plane001)
test.describe('Feature Tree pane', () => {
test(
'User can go to definition and go to function definition',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, homePage, scene, editor, toolbar, cmdBar, page }) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'test-sample')
@ -150,7 +150,7 @@ test.describe('Feature Tree pane', () => {
test(
`User can edit sketch (but not on offset plane yet) from the feature tree`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, homePage, scene, editor, toolbar, page }) => {
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)

View File

@ -13,7 +13,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('integrations tests', () => {
test(
'Creating a new file or switching file while in sketchMode should exit sketchMode',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'test-sample')
@ -100,7 +100,7 @@ test.describe('when using the file tree to', () => {
test(
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page }, testInfo) => {
const { panesOpen, pasteCodeInEditor, renameFile, editorTextMatches } =
await getUtils(page, test)
@ -142,7 +142,7 @@ test.describe('when using the file tree to', () => {
test(
`create many new files of the same name, incrementing their names`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page }, testInfo) => {
const { panesOpen, createNewFile } = await getUtils(page, test)
@ -174,7 +174,7 @@ test.describe('when using the file tree to', () => {
test(
'create a new file with the same name as an existing file cancels the operation',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, homePage, scene, editor, toolbar }, testInfo) => {
const projectName = 'cube'
const mainFile = 'main.kcl'
@ -240,7 +240,7 @@ test.describe('when using the file tree to', () => {
test(
`create new folders and that doesn't trigger a navigation`,
{ tag: ['@desktop', '@macos', '@windows'] },
{ tag: ['@electron', '@macos', '@windows'] },
async ({ page, homePage, scene, toolbar, cmdBar }) => {
await homePage.goToModelingScene()
await scene.settled(cmdBar)
@ -260,7 +260,7 @@ test.describe('when using the file tree to', () => {
test(
'deleting all files recreates a default main.kcl with no code',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page }, testInfo) => {
const { panesOpen, pasteCodeInEditor, deleteFile, editorTextMatches } =
await getUtils(page, test)
@ -291,7 +291,7 @@ test.describe('when using the file tree to', () => {
test(
'loading small file, then large, then back to small',
{
tag: '@desktop',
tag: '@electron',
},
async ({ page }, testInfo) => {
const {
@ -361,7 +361,7 @@ test.describe('when using the file tree to', () => {
test.describe('Renaming in the file tree', () => {
test(
'A file you have open',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const { dir } = await context.folderSetupFn(async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
@ -450,7 +450,7 @@ test.describe('Renaming in the file tree', () => {
test(
'A file you do not have open',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const { dir } = await context.folderSetupFn(async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
@ -536,7 +536,7 @@ test.describe('Renaming in the file tree', () => {
test(
`A folder you're not inside`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const { dir } = await context.folderSetupFn(async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
@ -618,7 +618,7 @@ test.describe('Renaming in the file tree', () => {
test(
`A folder you are inside`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, context }, testInfo) => {
const { dir } = await context.folderSetupFn(async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
@ -721,7 +721,7 @@ test.describe('Renaming in the file tree', () => {
test.describe('Deleting items from the file pane', () => {
test(
`delete file when main.kcl exists, navigate to main.kcl`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, context }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const testDir = join(dir, 'testProject')
@ -786,7 +786,7 @@ test.describe('Deleting items from the file pane', () => {
test(
`Delete folder we are not in, don't navigate`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
@ -840,7 +840,7 @@ test.describe('Deleting items from the file pane', () => {
test(
`Delete folder we are in, navigate to main.kcl`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
@ -906,7 +906,7 @@ test.describe('Deleting items from the file pane', () => {
// Copied from tests above.
test(
`external deletion of project navigates back home`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const TEST_PROJECT_NAME = 'Test Project'
const { dir: projectsDirName } = await context.folderSetupFn(
@ -970,7 +970,7 @@ test.describe('Deleting items from the file pane', () => {
// Similar to the above
test(
`external deletion of file in sub-directory updates the file tree and recreates it on code editor typing`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const TEST_PROJECT_NAME = 'Test Project'
const { dir: projectsDirName } = await context.folderSetupFn(
@ -1045,7 +1045,7 @@ test.describe('Deleting items from the file pane', () => {
test.describe('Undo and redo do not keep history when navigating between files', () => {
test(
`open a file, change something, open a different file, hitting undo should do nothing`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const testDir = join(dir, 'testProject')
@ -1112,7 +1112,7 @@ test.describe('Undo and redo do not keep history when navigating between files',
test(
`open a file, change something, undo it, open a different file, hitting redo should do nothing`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const testDir = join(dir, 'testProject')
@ -1212,7 +1212,7 @@ test.describe('Undo and redo do not keep history when navigating between files',
test(
`cloned file has an incremented name and same contents`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, context, homePage }, testInfo) => {
const { panesOpen, cloneFile } = await getUtils(page, test)

View File

@ -394,7 +394,7 @@ const fixturesBasedOnProcessEnvPlatform = {
},
}
if (process.env.TARGET === 'web') {
if (process.env.PLATFORM === 'web') {
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForWeb)
} else {
Object.assign(fixturesBasedOnProcessEnvPlatform, fixturesForElectron)

View File

@ -126,7 +126,7 @@ export class HomePageFixture {
/** Returns the project name in case caller has used the default and needs it */
goToModelingScene = async (name = 'testDefault') => {
// On web this is a no-op. There is no project view.
if (process.env.TARGET === 'web') return ''
if (process.env.PLATFORM === 'web') return ''
await this.createAndGoToProject(name)
return name

View File

@ -5,7 +5,7 @@ import * as fsp from 'fs/promises'
test.describe('Import UI tests', () => {
test(
'shows toast when trying to sketch on imported face, and hovering over imported geometry should NOT highlight any code',
{ tag: ['@desktop', '@macos', '@windows'] },
{ tag: ['@electron', '@macos', '@windows'] },
async ({ context, page, homePage, toolbar, scene, editor, cmdBar }) => {
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, 'import-test')

View File

@ -6,7 +6,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
test(
'When machine-api server not found butt is disabled and shows the reason',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, scene, cmdBar }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'bracket')
@ -43,7 +43,7 @@ test(
test(
'When machine-api server not found home screen & project status shows the reason',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, scene, cmdBar }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'bracket')

View File

@ -13,7 +13,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
*/
test.describe(
'Native file menu',
{ tag: ['@desktop', '@macos', '@windows'] },
{ tag: ['@electron', '@macos', '@windows'] },
() => {
test('Home page', async ({ tronApp, cmdBar, page, homePage }) => {
if (!tronApp) fail()

View File

@ -43,7 +43,7 @@ async function insertPartIntoAssembly(
test.describe('Point-and-click assemblies tests', () => {
test(
`Insert kcl parts into assembly as whole module import`,
{ tag: ['@desktop', '@macos', '@windows'] },
{ tag: ['@electron', '@macos', '@windows'] },
async ({
context,
page,
@ -70,28 +70,22 @@ test.describe('Point-and-click assemblies tests', () => {
await test.step('Setup parts and expect empty assembly scene', async () => {
const projectName = 'assembly'
await context.folderSetupFn(async (dir) => {
const projDir = path.join(dir, projectName)
const nestedProjDir = path.join(dir, projectName, 'nested', 'twice')
await fsp.mkdir(projDir, { recursive: true })
await fsp.mkdir(nestedProjDir, { recursive: true })
const bracketDir = path.join(dir, projectName)
await fsp.mkdir(bracketDir, { recursive: true })
await Promise.all([
fsp.copyFile(
executorInputPath('cylinder.kcl'),
path.join(projDir, 'cylinder.kcl')
),
fsp.copyFile(
executorInputPath('cylinder.kcl'),
path.join(nestedProjDir, 'main.kcl')
path.join(bracketDir, 'cylinder.kcl')
),
fsp.copyFile(
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
path.join(projDir, 'bracket.kcl')
path.join(bracketDir, 'bracket.kcl')
),
fsp.copyFile(
testsInputPath('cube.step'),
path.join(projDir, 'cube.step')
path.join(bracketDir, 'cube.step')
),
fsp.writeFile(path.join(projDir, 'main.kcl'), ''),
fsp.writeFile(path.join(bracketDir, 'main.kcl'), ''),
])
})
await page.setBodyDimensions({ width: 1000, height: 500 })
@ -173,32 +167,13 @@ test.describe('Point-and-click assemblies tests', () => {
await expect(
page.getByText('This file is already imported')
).toBeVisible()
await cmdBar.closeCmdBar()
})
await test.step('Insert a nested kcl part', async () => {
await insertPartIntoAssembly(
'nested/twice/main.kcl',
'main',
toolbar,
cmdBar,
page
)
await toolbar.openPane('code')
await page.waitForTimeout(10000)
await editor.expectEditor.toContain(
`
import "nested/twice/main.kcl" as main
`,
{ shouldNormalise: true }
)
})
}
)
test(
`Can still translate, rotate, and delete inserted parts even with non standard code`,
{ tag: ['@desktop', '@macos', '@windows'] },
{ tag: ['@electron', '@macos', '@windows'] },
async ({
context,
page,
@ -394,7 +369,7 @@ test.describe('Point-and-click assemblies tests', () => {
test(
`Insert the bracket part into an assembly and transform it`,
{ tag: ['@desktop', '@macos', '@windows'] },
{ tag: ['@electron', '@macos', '@windows'] },
async ({
context,
page,
@ -585,7 +560,7 @@ test.describe('Point-and-click assemblies tests', () => {
test(
`Insert foreign parts into assembly and delete them`,
{ tag: ['@desktop', '@macos', '@windows'] },
{ tag: ['@electron', '@macos', '@windows'] },
async ({
context,
page,
@ -736,7 +711,7 @@ test.describe('Point-and-click assemblies tests', () => {
test(
'Assembly gets reexecuted when imported models are updated externally',
{ tag: ['@desktop', '@macos', '@windows'] },
{ tag: ['@electron', '@macos', '@windows'] },
async ({ context, page, homePage, scene, toolbar, cmdBar, tronApp }) => {
if (!tronApp) {
fail()
@ -826,7 +801,7 @@ foreign
test(
`Point-and-click clone`,
{ tag: ['@desktop', '@macos', '@windows'] },
{ tag: ['@electron', '@macos', '@windows'] },
async ({
context,
page,

View File

@ -74,15 +74,6 @@ test.describe('Point-and-click tests', () => {
await test.step('do extrude flow and check extrude code is added to editor', async () => {
await toolbar.extrudeButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: { Profiles: '', Length: '' },
highlightedHeaderArg: 'Profiles',
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'length',
@ -1654,15 +1645,6 @@ sketch002 = startSketchOn(plane001)
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
await toolbar.loftButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: { Profiles: '' },
highlightedHeaderArg: 'Profiles',
commandName: 'Loft',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Profiles: '2 profiles' },
@ -2110,18 +2092,6 @@ extrude001 = extrude(sketch001, length = -12)
await test.step(`Apply fillet to the preselected edge`, async () => {
await page.waitForTimeout(100)
await toolbar.filletButton.click()
await cmdBar.expectState({
commandName: 'Fillet',
highlightedHeaderArg: 'selection',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Radius: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Fillet',
highlightedHeaderArg: 'radius',
@ -2651,18 +2621,6 @@ extrude001 = extrude(profile001, length = 5)
await test.step(`Apply fillet`, async () => {
await page.waitForTimeout(100)
await toolbar.filletButton.click()
await cmdBar.expectState({
commandName: 'Fillet',
highlightedHeaderArg: 'selection',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Radius: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Fillet',
highlightedHeaderArg: 'radius',
@ -2768,19 +2726,6 @@ extrude001 = extrude(sketch001, length = -12)
await test.step(`Apply chamfer to the preselected edge`, async () => {
await page.waitForTimeout(100)
await toolbar.chamferButton.click()
await cmdBar.expectState({
commandName: 'Chamfer',
highlightedHeaderArg: 'selection',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: {
Selection: '',
Length: '',
},
stage: 'arguments',
})
await cmdBar.progressCmdBar()
await page.waitForTimeout(1000)
await cmdBar.expectState({
commandName: 'Chamfer',
highlightedHeaderArg: 'length',
@ -3264,8 +3209,6 @@ extrude001 = extrude(sketch001, length = 30)
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
await toolbar.shellButton.click()
await cmdBar.progressCmdBar()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
@ -3699,12 +3642,13 @@ tag=$rectangleSegmentC002,
// revolve
await editor.scrollToText(codeToSelection)
await page.getByText(codeToSelection).click()
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
await page.waitForTimeout(200)
await toolbar.revolveButton.click()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = X)`
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
@ -4633,18 +4577,6 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => {
await toolbar.extrudeButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Profiles: '',
Length: '',
},
highlightedHeaderArg: 'Profiles',
commandName: 'Extrude',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'length',
@ -4727,19 +4659,6 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => {
await toolbar.sweepButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Profiles: '',
Path: '',
Sectional: '',
},
highlightedHeaderArg: 'Profiles',
commandName: 'Sweep',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'path',
@ -4824,19 +4743,6 @@ path001 = startProfile(sketch001, at = [0, 0])
await test.step('Go through command bar flow', async () => {
await toolbar.closePane('code')
await toolbar.revolveButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'sketches',
currentArgValue: '',
headerArguments: {
Profiles: '',
AxisOrEdge: '',
Angle: '',
},
highlightedHeaderArg: 'Profiles',
commandName: 'Revolve',
})
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'axisOrEdge',

View File

@ -11,13 +11,12 @@ import {
getPlaywrightDownloadDir,
getUtils,
isOutOfViewInScrollContainer,
runningOnWindows,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
test(
'projects reload if a new one is created, deleted, or renamed externally',
{ tag: ['@desktop', '@macos', '@windows'] },
{ tag: ['@electron', '@macos', '@windows'] },
async ({ context, page }, testInfo) => {
let externalCreatedProjectName = 'external-created-project'
@ -63,7 +62,7 @@ test(
test(
'click help/keybindings from home page',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page }, testInfo) => {
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -81,7 +80,7 @@ test(
test(
'click help/keybindings from project page',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ scene, cmdBar, context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
@ -112,7 +111,7 @@ test(
test(
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ scene, cmdBar, context, page, editor }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
@ -184,7 +183,7 @@ test(
test(
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ scene, cmdBar, context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
@ -244,7 +243,7 @@ test(
test(
'open a file in a project works and renders, open empty file, it should clear the scene',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
@ -310,7 +309,7 @@ test(
test(
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ scene, cmdBar, context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
@ -382,7 +381,7 @@ test(
test(
'when code with error first loads you get errors in console',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, editor }, testInfo) => {
await context.folderSetupFn(async (dir) => {
await fsp.mkdir(path.join(dir, 'broken-code'), { recursive: true })
@ -416,7 +415,7 @@ test.describe('Can export from electron app', () => {
for (const method of exportMethods) {
test(
`Can export using ${method}`,
{ tag: ['@desktop', '@skipLocalEngine'] },
{ tag: ['@electron', '@skipLocalEngine'] },
async ({ scene, cmdBar, context, page, tronApp }, testInfo) => {
if (!tronApp) {
fail()
@ -507,7 +506,7 @@ test.describe('Can export from electron app', () => {
})
test(
'Rename and delete projects, also spam arrow keys when renaming',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
@ -723,7 +722,7 @@ test(
test(
'pressing "delete" on home screen should do nothing',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, homePage }, testInfo) => {
await context.folderSetupFn(async (dir) => {
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
@ -753,7 +752,7 @@ test(
test.describe(`Project management commands`, () => {
test(
`Rename from project page`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, scene, cmdBar }, testInfo) => {
const projectName = `my_project_to_rename`
await context.folderSetupFn(async (dir) => {
@ -815,7 +814,7 @@ test.describe(`Project management commands`, () => {
test(
`Delete from project page`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, scene, cmdBar }, testInfo) => {
const projectName = `my_project_to_delete`
await context.folderSetupFn(async (dir) => {
@ -869,7 +868,7 @@ test.describe(`Project management commands`, () => {
)
test(
`Rename from home page`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, homePage, scene, cmdBar }, testInfo) => {
const projectName = `my_project_to_rename`
await context.folderSetupFn(async (dir) => {
@ -927,7 +926,7 @@ test.describe(`Project management commands`, () => {
)
test(
`Delete from home page`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, scene, cmdBar }, testInfo) => {
const projectName = `my_project_to_delete`
await context.folderSetupFn(async (dir) => {
@ -1103,7 +1102,7 @@ test(`Create a few projects using the default project name`, async ({
test(
'File in the file pane should open with a single click',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, homePage, page }, testInfo) => {
const projectName = 'router-template-slate'
await context.folderSetupFn(async (dir) => {
@ -1145,7 +1144,7 @@ test(
test(
'Nested directories in project without main.kcl do not create main.kcl',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ scene, cmdBar, context, page }, testInfo) => {
let testDir: string | undefined
await context.folderSetupFn(async (dir) => {
@ -1202,7 +1201,7 @@ test(
test(
'Deleting projects, can delete individual project, can still create projects after deleting all',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const projectData = [
['router-template-slate', 'cylinder.kcl'],
@ -1280,7 +1279,7 @@ test(
test(
'Can load a file with CRLF line endings',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, scene, cmdBar }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const routerTemplateDir = path.join(dir, 'router-template-slate')
@ -1312,7 +1311,7 @@ test(
test(
'Can sort projects on home page',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const projectData = [
['router-template-slate', 'cylinder.kcl'],
@ -1419,7 +1418,7 @@ test(
test(
'When the project folder is empty, user can create new project and open it.',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page }, testInfo) => {
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -1515,7 +1514,7 @@ extrude001 = extrude(sketch001, length = 200)`)
test(
'Opening a project should successfully load the stream, (regression test that this also works when switching between projects)',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, cmdBar, homePage, scene }, testInfo) => {
await context.folderSetupFn(async (dir) => {
await fsp.mkdir(path.join(dir, 'router-template-slate'), {
@ -1628,7 +1627,7 @@ test(
test(
'You can change the root projects directory and nothing is lost',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page, tronApp, homePage }, testInfo) => {
if (!tronApp) {
fail()
@ -1735,7 +1734,7 @@ test(
test(
'Search projects on desktop home',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const projectData = [
['basic bracket', 'focusrite_scarlett_mounting_bracket.kcl'],
@ -1791,7 +1790,7 @@ test(
test(
'file pane is scrollable when there are many files',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ scene, cmdBar, context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const testDir = path.join(dir, 'testProject')
@ -1892,7 +1891,7 @@ test(
test(
'select all in code editor does not actually select all, just what is visible (regression)',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
// rust/kcl-lib/e2e/executor/inputs/mike_stress_test.kcl
@ -1950,7 +1949,7 @@ test(
test(
'Settings persist across restarts',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, toolbar }, testInfo) => {
await test.step('We can change a user setting like theme', async () => {
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -1980,9 +1979,10 @@ test(
}
)
// Flaky
test(
'Original project name persist after onboarding',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, toolbar }, testInfo) => {
const nextButton = page.getByTestId('onboarding-next')
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -2022,7 +2022,7 @@ test(
test(
'project name with foreign characters should open',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'العربية')
@ -2066,9 +2066,13 @@ test(
)
test(
'import from nested directory',
{ tag: ['@desktop', '@windows', '@macos'] },
async ({ scene, cmdBar, context, page }) => {
'nested dir import works on windows',
{ tag: ['@electron', '@windows'] },
async ({ scene, cmdBar, context, page }, testInfo) => {
// Skip if on non-windows
if (process.platform !== 'win32') {
test.skip()
}
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
@ -2081,9 +2085,9 @@ test(
)
await fsp.writeFile(
path.join(bracketDir, 'main.kcl'),
runningOnWindows()
? `import 'nested\\main.kcl' as thing\n\nthing`
: `import 'nested/main.kcl' as thing\n\nthing`
`import 'nested\\main.kcl' as thing
thing`
)
})

View File

@ -63,7 +63,7 @@ test.describe('edit with AI example snapshots', () => {
test(
`change colour`,
// TODO this is more of a snapshot, but atm it needs to be manually run locally to update the files
{ tag: ['@desktop'] },
{ tag: ['@electron'] },
async ({ context, homePage, cmdBar, editor, page, scene }) => {
const project = 'test-dir'
await context.folderSetupFn(async (dir) => {

View File

@ -558,7 +558,7 @@ extrude002 = extrude(profile002, length = 150)
test(
`Network health indicator only appears in modeling view`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')

View File

@ -17,7 +17,7 @@ test.describe('Share link tests', () => {
`Open in desktop app with ${codeLength}-long code ${isWindows && showsErrorOnWindows ? 'shows error' : "doesn't show error"}`,
{ tag: ['@web'] },
async ({ page }) => {
if (process.env.TARGET !== 'web') {
if (process.env.PLATFORM !== 'web') {
// This test is web-only
// TODO: re-enable on CI as part of a new @web test suite
return

View File

@ -1016,7 +1016,6 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
// sketch selection should already have been made.
// otherwise the cmdbar would be waiting for a selection.
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'length',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -279,7 +279,7 @@ profile001 = startProfile(sketch001, at = [12.34, -12.34])
test(
'Paused stream freezes view frame, unpause reconnect is seamless to user',
{ tag: ['@desktop', '@skipLocalEngine'] },
{ tag: ['@electron', '@skipLocalEngine'] },
async ({ page, homePage, scene, cmdBar, toolbar, tronApp }) => {
const networkToggle = page.getByTestId('network-toggle')
const networkToggleConnectedText = page.getByText('Connected')

View File

@ -79,6 +79,20 @@ export function runningOnWindows() {
return process.platform === 'win32'
}
async function waitForPageLoadWithRetry(page: Page) {
await expect(async () => {
await page.goto('/')
const errorMessage = 'App failed to load - 🔃 Retrying ...'
await expect(
page.getByRole('button', { name: 'sketch Start Sketch' }),
errorMessage
).toBeEnabled({
timeout: 20_000,
})
}).toPass({ timeout: 70_000, intervals: [1_000] })
}
// lee: This needs to be replaced by scene.settled() eventually.
async function waitForPageLoad(page: Page) {
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeEnabled({
@ -340,8 +354,13 @@ async function waitForAuthAndLsp(page: Page) {
},
timeout: 45_000,
})
await page.goto('/')
await waitForPageLoad(page)
if (process.env.CI) {
await waitForPageLoadWithRetry(page)
} else {
await page.goto('/')
await waitForPageLoad(page)
}
return waitForLspPromise
}
@ -372,6 +391,7 @@ export async function getUtils(page: Page, test_?: typeof test) {
const util = {
waitForAuthSkipAppStart: () => waitForAuthAndLsp(page),
waitForPageLoad: () => waitForPageLoad(page),
waitForPageLoadWithRetry: () => waitForPageLoadWithRetry(page),
removeCurrentCode: () => removeCurrentCode(page),
sendCustomCmd: (cmd: EngineCommand) => sendCustomCmd(page, cmd),
updateCamPosition: async (xyz: [number, number, number]) => {

View File

@ -1120,7 +1120,7 @@ part002 = startSketchOn(XZ)
test.describe('Electron constraint tests', () => {
test(
'Able to double click label to set constraint',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'test-sample')

View File

@ -84,7 +84,7 @@ test.describe('Testing loading external models', () => {
*/
test(
'Desktop: should create new file by default, creates a second file with automatic unique name',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ editor, context, page, scene, cmdBar, toolbar }) => {
if (runningOnWindows()) {
}
@ -103,8 +103,6 @@ test.describe('Testing loading external models', () => {
file: 'ball-bearing' + FILE_EXT,
title: 'Ball Bearing',
file1: 'ball-bearing-1' + FILE_EXT,
folderName: 'ball-bearing',
folderName1: 'ball-bearing-1',
}
const projectCard = page.getByRole('link', { name: 'bracket' })
const overwriteWarning = page.getByText(
@ -156,10 +154,8 @@ test.describe('Testing loading external models', () => {
await test.step(`Ensure we made and opened a new file`, async () => {
await editor.expectEditor.toContain('// ' + sampleOne.title)
await expect(
page.getByTestId('file-tree-item').getByText(sampleOne.folderName)
).toBeVisible()
await expect(projectMenuButton).toContainText('main.kcl')
await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
await expect(projectMenuButton).toContainText(sampleOne.file)
})
await test.step(`Load a KCL sample with the command palette`, async () => {
@ -173,10 +169,8 @@ test.describe('Testing loading external models', () => {
await test.step(`Ensure we made and opened a new file with a unique name`, async () => {
await editor.expectEditor.toContain('// ' + sampleOne.title)
await expect(
page.getByTestId('file-tree-item').getByText(sampleOne.folderName1)
).toBeVisible()
await expect(projectMenuButton).toContainText('main.kcl')
await expect(newlyCreatedFile(sampleOne.file1)).toBeVisible()
await expect(projectMenuButton).toContainText(sampleOne.file1)
})
}
)
@ -196,7 +190,7 @@ test.describe('Testing loading external models', () => {
externalModelCases.map(({ modelName, deconflictedModelName, modelPath }) => {
test(
`Load external models from local drive - ${modelName}`,
{ tag: ['@desktop'] },
{ tag: ['@electron'] },
async ({ page, homePage, scene, toolbar, cmdBar, tronApp }) => {
if (!tronApp) {
fail()

File diff suppressed because it is too large Load Diff

View File

@ -278,7 +278,7 @@ test.describe(
test(
`Project settings override user settings on desktop`,
{ tag: ['@desktop'] },
{ tag: ['@electron'] },
async ({ context, page }, testInfo) => {
const projectName = 'bracket'
const { dir: projectDirName } = await context.folderSetupFn(
@ -373,7 +373,7 @@ test.describe(
test(
`Load desktop app with no settings file`,
{
tag: '@desktop',
tag: '@electron',
},
async ({ page }, testInfo) => {
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -393,7 +393,7 @@ test.describe(
test(
`Load desktop app with a settings file, but no project directory setting`,
{
tag: '@desktop',
tag: '@electron',
},
async ({ context, page, tronApp }, testInfo) => {
if (!tronApp) {
@ -425,7 +425,7 @@ test.describe(
test(
'user settings reload on external change, on project and modeling view',
{
tag: '@desktop',
tag: '@electron',
},
async ({ context, page, tronApp }, testInfo) => {
if (!tronApp) {
@ -486,7 +486,7 @@ test.describe(
test(
'project settings reload on external change',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const { dir: projectDirName } = await context.folderSetupFn(
async () => {}
@ -536,7 +536,7 @@ test.describe(
test(
`Closing settings modal should go back to the original file being viewed`,
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'project-000')

View File

@ -1,3 +1,5 @@
import fs from 'fs'
import { join } from 'path'
import type { Page } from '@playwright/test'
import { createProject, getUtils } from '@e2e/playwright/test-utils'
@ -401,6 +403,106 @@ test.describe('Text-to-CAD tests', () => {
await expect(page.getByText(promptWithNewline)).toBeVisible()
})
// This will be fine once greg makes prompt at top of file deterministic
test('can do many at once and get many prompts back, and interact with many', async ({
page,
homePage,
cmdBar,
}) => {
// Let this test run longer since we've seen it timeout.
test.setTimeout(180_000)
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await u.waitForPageLoad()
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x4 lego',
cmdBar
)
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x8 lego',
cmdBar
)
await sendPromptFromCommandBarAndSetExistingProject(
page,
'a 2x10 lego',
cmdBar
)
// Find the toast.
// Look out for the toast message
const submittingToastMessage = page.getByText(
`Submitting to Text-to-CAD API...`
)
await expect(submittingToastMessage.first()).toBeVisible()
const generatingToastMessage = page.getByText(
`Generating parametric model...`
)
await expect(generatingToastMessage.first()).toBeVisible({
timeout: 10_000,
})
const successToastMessage = page.getByText(`Text-to-CAD successful`)
// We should have three success toasts.
await expect(successToastMessage).toHaveCount(3, { timeout: 25_000 })
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
await expect(page.getByText(`a 2x10 lego`)).toBeVisible()
// Ensure if you reject one, the others stay.
const rejectButton = page.getByRole('button', { name: 'Reject' })
await expect(rejectButton.first()).toBeVisible()
// Click the reject button on the first toast.
await rejectButton.first().click()
// The first toast should disappear, but not the others.
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
await expect(page.getByText(`a 2x8 lego`)).toBeVisible()
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
// Ensure you can copy the code for one of the models remaining.
const copyToClipboardButton = page.getByRole('button', {
name: 'Accept',
})
await expect(copyToClipboardButton.first()).toBeVisible()
// Click the button.
await copyToClipboardButton.first().click()
// Do NOT do AI tests like this: "Expect the code to be pasted."
// Reason: AI tests are NONDETERMINISTIC. Thus we need to be as most
// general as we can for the assertion.
// We can use Kolmogorov complexity as a measurement of the
// "probably most minimal version of this program" to have a lower
// bound to work with. It is completely by feel because there are
// no proofs that any program is its smallest self.
const code2x8 = await page.locator('.cm-content').innerText()
await expect(code2x8.length).toBeGreaterThan(249)
// Ensure the final toast remains.
await expect(page.getByText(`a 2x10 lego`)).not.toBeVisible()
await expect(page.getByText(`Prompt: "a 2x8 lego`)).not.toBeVisible()
await expect(page.getByText(`a 2x4 lego`)).toBeVisible()
// Ensure you can copy the code for the final model.
await expect(copyToClipboardButton).toBeVisible()
// Click the button.
await copyToClipboardButton.click()
// Expect the code to be pasted.
const code2x4 = await page.locator('.cm-content').innerText()
await expect(code2x4.length).toBeGreaterThan(249)
})
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
page,
homePage,
@ -573,6 +675,82 @@ async function sendPromptFromCommandBarAndSetExistingProject(
})
}
test(
'Text-to-CAD functionality',
{ tag: '@electron' },
async ({ context, page, cmdBar }, testInfo) => {
const projectName = 'project-000'
const prompt = 'lego 2x4'
const textToCadFileName = 'lego-2x4.kcl'
const { dir } = await context.folderSetupFn(async () => {})
const fileExists = () =>
fs.existsSync(join(dir, projectName, textToCadFileName))
const { openFilePanel, openKclCodePanel, waitForPageLoad } = await getUtils(
page,
test
)
await page.setBodyDimensions({ width: 1200, height: 500 })
// Locators
const projectMenuButton = page
.getByTestId('project-sidebar-toggle')
.filter({ hasText: projectName })
const textToCadFileButton = page.getByRole('listitem').filter({
has: page.getByRole('button', { name: textToCadFileName }),
})
const textToCadComment = page.getByText(
`// Generated by Text-to-CAD: ${prompt}`
)
// Create and navigate to the project
await createProject({ name: 'project-000', page })
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
await waitForPageLoad()
await openFilePanel()
await openKclCodePanel()
await test.step(`Test file creation`, async () => {
await sendPromptFromCommandBarAndSetExistingProject(
page,
prompt,
cmdBar,
projectName
)
// File is considered created if it shows up in the Project Files pane
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
expect(fileExists()).toBeTruthy()
})
await test.step(`Test file navigation`, async () => {
await expect(projectMenuButton).toContainText('main.kcl')
await textToCadFileButton.click()
// File can be navigated and loaded assuming a specific KCL comment is loaded into the KCL code pane
await expect(textToCadComment).toBeVisible({ timeout: 20_000 })
await expect(projectMenuButton).toContainText(textToCadFileName)
})
await test.step(`Test file deletion on rejection`, async () => {
const rejectButton = page.getByRole('button', { name: 'Reject' })
// A file is created and can be navigated to while this prompt is still opened
// Click the "Reject" button within the prompt and it will delete the file.
await rejectButton.click()
const submittingToastMessage = page.getByText(
`Successfully deleted file "lego-2x4.kcl"`
)
await expect(submittingToastMessage).toBeVisible()
expect(fileExists()).toBeFalsy()
// Confirm we've navigated back to the main.kcl file after deletion
await expect(projectMenuButton).toContainText('main.kcl')
})
}
)
/**
* Below there are twelve (12) tests for testing the navigation and file creation
* logic around text to cad. The Text to CAD command is now globally available
@ -640,7 +818,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
test(
'Home Page -> Text To CAD -> New Project -> Stay in home page -> Reject -> Project should be deleted',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const projectName = 'my-project-name'
const prompt = '2x2x2 cube'
@ -678,7 +856,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
test(
'Home Page -> Text To CAD -> New Project -> Stay in home page -> Accept -> should navigate to file',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
const u = await getUtils(page)
const projectName = 'my-project-name'
@ -724,7 +902,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
test(
'Home Page -> Text To CAD -> Existing Project -> Stay in home page -> Reject -> should delete single file',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ homePage, page }, testInfo) => {
const projectName = 'my-project-name'
const prompt = '2x2x2 cube'
@ -767,7 +945,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
test(
'Home Page -> Text To CAD -> Existing Project -> Stay in home page -> Accept -> should navigate to file',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ homePage, page }, testInfo) => {
const u = await getUtils(page)
const projectName = 'my-project-name'
@ -806,19 +984,19 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
)
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
await expect(page.getByTestId('app-header-file-name')).toContainText(
'main.kcl'
'2x2x2-cube.kcl'
)
await u.openFilePanel()
await expect(
page.getByTestId('file-tree-item').getByText('2x2x2-cube')
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
).toBeVisible()
}
)
test(
'Home Page -> Text To CAD -> New Project -> Navigate to the project -> Reject -> should go to home page',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ homePage, page }, testInfo) => {
const projectName = 'my-project-name'
const prompt = '2x2x2 cube'
@ -864,7 +1042,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
test(
'Home Page -> Text To CAD -> New Project -> Navigate to the project -> Accept -> should stay in same file',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ homePage, page }, testInfo) => {
const projectName = 'my-project-name'
const prompt = '2x2x2 cube'
@ -907,7 +1085,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
test(
'Home Page -> Text To CAD -> Existing Project -> Navigate to the project -> Reject -> should load main.kcl',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ homePage, page }, testInfo) => {
const u = await getUtils(page)
const projectName = 'my-project-name'
@ -962,7 +1140,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
test(
'Home Page -> Text To CAD -> Existing Project -> Navigate to the project -> Accept -> should load 2x2x2-cube.kcl',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ homePage, page }, testInfo) => {
const u = await getUtils(page)
const projectName = 'my-project-name'
@ -1006,20 +1184,20 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
)
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
await expect(page.getByTestId('app-header-file-name')).toContainText(
'main.kcl'
'2x2x2-cube.kcl'
)
// Check file is created
await u.openFilePanel()
await expect(
page.getByTestId('file-tree-item').getByText('2x2x2-cube')
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
).toBeVisible()
}
)
test(
'Home Page -> Text To CAD -> New Project -> Navigate to different project -> Reject -> should stay in project',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ homePage, page }, testInfo) => {
const u = await getUtils(page)
const projectName = 'my-project-name'
@ -1096,7 +1274,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
test(
'Home Page -> Text To CAD -> New Project -> Navigate to different project -> Accept -> should go to new project',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, homePage }, testInfo) => {
const u = await getUtils(page)
const projectName = 'my-project-name'
@ -1165,7 +1343,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
test(
'Home Page -> Text To CAD -> Existing Project -> Navigate to different project -> Reject -> should stay in same project',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, homePage }, testInfo) => {
const u = await getUtils(page)
const projectName = 'my-project-name'
@ -1240,7 +1418,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
test(
'Home Page -> Text To CAD -> Existing Project -> Navigate to different project -> Accept -> should navigate to new project',
{ tag: '@desktop' },
{ tag: '@electron' },
async ({ page, homePage }, testInfo) => {
const u = await getUtils(page)
const projectName = 'my-project-name'
@ -1298,13 +1476,13 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
)
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
await expect(page.getByTestId('app-header-file-name')).toContainText(
'main.kcl'
'2x2x2-cube.kcl'
)
// Check file is created
await u.openFilePanel()
await expect(
page.getByTestId('file-tree-item').getByText('2x2x2-cube')
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
).toBeVisible()
await expect(
page.getByTestId('file-tree-item').getByText('main.kcl')

View File

@ -573,7 +573,6 @@ profile001 = startProfile(sketch002, at = [-12.34, 12.34])
await expect(page.getByTestId('command-bar')).toBeVisible()
await page.waitForTimeout(100)
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await expect(page.getByText('Confirm Extrude')).toBeVisible()
await cmdBar.progressCmdBar()

View File

@ -41,7 +41,7 @@ const playwrightTestFnWithFixtures_ = playwrightTestFn.extend<{
}>({
tronApp: [
async ({}, use, testInfo) => {
if (process.env.TARGET === 'web') {
if (process.env.PLATFORM === 'web') {
await use(undefined)
return
}

View File

@ -16,6 +16,7 @@
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<link rel="stylesheet" href="./inter/inter.css" />
<link rel="stylesheet" href="https://use.typekit.net/zzv8rvm.css" />
<script
defer
data-domain="app.zoo.dev"

1
package-lock.json generated
View File

@ -113,7 +113,6 @@
"@xstate/cli": "^0.5.17",
"autoprefixer": "^10.4.21",
"babel-preset-vite": "^1.1.3",
"cross-env": "^7.0.3",
"dpdm": "^3.14.0",
"electron": "^34.1.1",
"electron-builder": "^26.0.12",

View File

@ -122,6 +122,7 @@
"postinstall": "electron-rebuild",
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"tron:start": "electron-forge start",
"chrome:test": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert=@snapshot",
"tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development",
"tronb:vite:prod": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
"tronb:package:dev": "npm run tronb:vite:dev && electron-builder --config electron-builder.yml",
@ -129,13 +130,12 @@
"test-setup": "npm install && npm run build:wasm",
"test": "vitest --mode development",
"test:rust": "(cd rust && just test && just lint)",
"test:snapshots": "cross-env TARGET=web NODE_ENV=development playwright test --config=playwright.config.ts --grep=@snapshot --trace=on",
"test:snapshots": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --grep=@snapshot --trace=on --shard=1/1",
"test:unit": "vitest run --mode development --exclude **/jest-component-unit-tests/*",
"test:unit:components": "jest -c jest-component-unit-tests/jest.config.ts --rootDir jest-component-unit-tests/",
"test:e2e:web": "cross-env TARGET=web NODE_ENV=development playwright test --config=playwright.config.ts --project=\"Google Chrome\" --grep=@web",
"test:e2e:desktop": "cross-env TARGET=desktop playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:e2e:desktop:local": "cross-env TARGET=desktop npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:e2e:desktop:local-engine": "cross-env TARGET=desktop npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot|@skipLocalEngine' --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:local": "npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:playwright:electron:local-engine": "npm run tronb:vite:dev && playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot|@skipLocalEngine' --grep-invert=\"$(curl --silent https://test-analysis-bot.hawk-dinosaur.ts.net/projects/KittyCAD/modeling-app/tests/disabled/regex)\"",
"test:unit:local": "npm run simpleserver:bg && npm run test:unit; kill-port 3000"
},
"browserslist": {
@ -188,7 +188,6 @@
"@xstate/cli": "^0.5.17",
"autoprefixer": "^10.4.21",
"babel-preset-vite": "^1.1.3",
"cross-env": "^7.0.3",
"dpdm": "^3.14.0",
"electron": "^34.1.1",
"electron-builder": "^26.0.12",

View File

@ -27,7 +27,7 @@ if len(modified_release_body) > max_length:
# Message to send to Discord
data = {
"content": textwrap.dedent(f'''
**{release_version}** is now available! Check out the latest features and improvements here: <https://zoo.dev/design-studio>
**{release_version}** is now available! Check out the latest features and improvements here: <https://zoo.dev/modeling-app/download>
{modified_release_body}
'''),

View File

@ -0,0 +1,142 @@
// 80/20 Rail
// An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position
// Set units
@settings(defaultLengthUnit = in, kclVersion = 1.0)
// Create a function to make the 80-20 rail
fn rail8020(originStart, railHeight, railLength) {
// Sketch side 1 of profile
sketch001 = startSketchOn(-XZ)
|> startProfile(at = [
originStart[0],
0.1 * railHeight + originStart[1]
])
|> arc(angleStart = 180, angleEnd = 270, radius = 0.1 * railHeight)
|> arc(angleStart = 180, angleEnd = 0, radius = 0.072 / 4 * railHeight)
|> xLine(length = 0.1 * railHeight)
|> arc(angleStart = 180, angleEnd = 0, radius = 0.072 / 4 * railHeight)
|> xLine(length = 0.06 * railHeight, tag = $edge1)
|> yLine(length = 0.087 * railHeight, tag = $edge2)
|> xLine(length = -0.183 * railHeight, tag = $edge3)
|> angledLine(angle = 45, endAbsoluteY = (1 - 0.356) / 2 * railHeight + originStart[1], tag = $edge4)
|> xLine(length = 0.232 * railHeight, tag = $edge5)
|> angledLine(angle = -45, endAbsoluteY = 0.087 * railHeight + originStart[1], tag = $edge6)
|> xLine(length = -0.183 * railHeight, tag = $edge7)
|> yLine(length = -0.087 * railHeight, tag = $edge8)
|> xLine(length = 0.06 * railHeight)
|> arc(angleStart = 180, angleEnd = 0, radius = 0.072 / 4 * railHeight)
|> xLine(length = 0.1 * railHeight)
|> arc(angleStart = 180, angleEnd = 0, radius = 0.072 / 4 * railHeight)
|> arc(angleStart = -90, angleEnd = 0, radius = 0.1 * railHeight)
// Sketch side 2 of profile
|> arc(angleStart = 270, angleEnd = 90, radius = 0.072 / 4 * railHeight)
|> yLine(length = 0.1 * railHeight)
|> arc(angleStart = 270, angleEnd = 90, radius = 0.072 / 4 * railHeight)
|> yLine(length = 0.06 * railHeight, tag = $edge9)
|> xLine(length = -0.087 * railHeight, tag = $edge10)
|> yLine(length = -0.183 * railHeight, tag = $edge11) // edge11
|> angledLine(angle = 135, endAbsoluteX = ((1 - 0.356) / 2 + 0.356) * railHeight + originStart[0], tag = $edge12) // edge12
|> yLine(length = 0.232 * railHeight, tag = $edge13) // 13
|> angledLine(angle = 45, endAbsoluteX = (1 - 0.087) * railHeight + originStart[0], tag = $edge14) // 14
|> yLine(length = -0.183 * railHeight, tag = $edge15) // 15
|> xLine(length = 0.087 * railHeight, tag = $edge16)
|> yLine(length = 0.06 * railHeight)
|> arc(angleStart = 270, angleEnd = 90, radius = 0.072 / 4 * railHeight)
|> yLine(length = 0.1 * railHeight)
|> arc(angleStart = 270, angleEnd = 90, radius = 0.072 / 4 * railHeight)
// Sketch side 3 of profile
|> arc(angleStart = 0, angleEnd = 90, radius = 0.1 * railHeight)
|> arc(angleStart = 0, angleEnd = -180, radius = 0.072 / 4 * railHeight)
|> xLine(length = -0.1 * railHeight)
|> arc(angleStart = 0, angleEnd = -180, radius = 0.072 / 4 * railHeight)
|> xLine(length = -0.06 * railHeight, tag = $edge17)
|> yLine(length = -0.087 * railHeight, tag = $edge18)
|> xLine(length = 0.183 * railHeight, tag = $edge19)
|> angledLine(angle = 45, endAbsoluteY = ((1 - 0.356) / 2 + 0.356) * railHeight + originStart[1], tag = $edge20)
|> xLine(length = -0.232 * railHeight, tag = $edge21)
|> angledLine(angle = 135, endAbsoluteY = (1 - 0.087) * railHeight + originStart[1], tag = $edge22)
|> xLine(length = 0.183 * railHeight, tag = $edge23)
|> yLine(length = 0.087 * railHeight, tag = $edge24)
|> xLine(length = -0.06 * railHeight)
|> arc(angleStart = 0, angleEnd = -180, radius = 0.072 / 4 * railHeight)
|> xLine(length = -0.1 * railHeight)
|> arc(angleStart = 0, angleEnd = -180, radius = 0.072 / 4 * railHeight)
|> arc(angleStart = 90, angleEnd = 180, radius = 0.1 * railHeight)
// Sketch side 4 of profile
|> arc(angleStart = 90, angleEnd = -90, radius = 0.072 / 4 * railHeight)
|> yLine(length = -0.1 * railHeight)
|> arc(angleStart = 90, angleEnd = -90, radius = 0.072 / 4 * railHeight)
|> yLine(length = -0.06 * railHeight, tag = $edge25)
|> xLine(length = 0.087 * railHeight, tag = $edge26)
|> yLine(length = 0.183 * railHeight, tag = $edge27)
|> angledLine(angle = 135, endAbsoluteX = (1 - 0.356) / 2 * railHeight + originStart[0], tag = $edge28)
|> yLine(length = -0.232 * railHeight, tag = $edge29)
|> angledLine(angle = 45, endAbsoluteX = 0.087 * railHeight + originStart[0], tag = $edge30)
|> yLine(length = 0.183 * railHeight, tag = $edge31)
|> xLine(length = -0.087 * railHeight, tag = $edge32)
|> yLine(length = -0.06 * railHeight)
|> arc(angleStart = 90, angleEnd = -90, radius = 0.072 / 4 * railHeight)
|> yLine(length = -0.1 * railHeight)
|> arc(angleStart = 90, angleEnd = -90, radius = 0.072 / 4 * railHeight)
|> close()
// Sketch center hole of profile
|> subtract2d(tool = circle(
center = [
.5 * railHeight + originStart[0],
.5 * railHeight + originStart[1]
],
radius = .205 * railHeight / 2,
))
|> extrude(length = railLength)
|> fillet(
radius = 0.06,
tags = [
getNextAdjacentEdge(edge3),
getNextAdjacentEdge(edge4),
getNextAdjacentEdge(edge5),
getNextAdjacentEdge(edge6),
getNextAdjacentEdge(edge11),
getNextAdjacentEdge(edge12),
getNextAdjacentEdge(edge13),
getNextAdjacentEdge(edge14),
getNextAdjacentEdge(edge19),
getNextAdjacentEdge(edge20),
getNextAdjacentEdge(edge21),
getNextAdjacentEdge(edge22),
getNextAdjacentEdge(edge27),
getNextAdjacentEdge(edge28),
getNextAdjacentEdge(edge29),
getNextAdjacentEdge(edge30)
],
)
|> fillet(
radius = 0.03,
tags = [
getNextAdjacentEdge(edge1),
getNextAdjacentEdge(edge2),
getNextAdjacentEdge(edge7),
getNextAdjacentEdge(edge8),
getNextAdjacentEdge(edge9),
getNextAdjacentEdge(edge10),
getNextAdjacentEdge(edge15),
getNextAdjacentEdge(edge16),
getNextAdjacentEdge(edge17),
getNextAdjacentEdge(edge18),
getNextAdjacentEdge(edge23),
getNextAdjacentEdge(edge24),
getNextAdjacentEdge(edge25),
getNextAdjacentEdge(edge26),
getNextAdjacentEdge(edge31),
getNextAdjacentEdge(edge32)
],
)
return sketch001
}
// Generate one adjustable rail of 80/20
rail8020(originStart = [0, 0], railHeight = 1.5, railLength = 48)

View File

@ -23,12 +23,12 @@ KCL samples conform to a set of style guidelines to ensure consistency and reada
When you submit a PR to add or modify KCL samples, images will be generated and added to the repository automatically.
---
#### [80-20-rail](80-20-rail/main.kcl) ([screenshot](screenshots/80-20-rail.png))
[![80-20-rail](screenshots/80-20-rail.png)](80-20-rail/main.kcl)
#### [axial-fan](axial-fan/main.kcl) ([screenshot](screenshots/axial-fan.png))
[![axial-fan](screenshots/axial-fan.png)](axial-fan/main.kcl)
#### [ball-bearing](ball-bearing/main.kcl) ([screenshot](screenshots/ball-bearing.png))
[![ball-bearing](screenshots/ball-bearing.png)](ball-bearing/main.kcl)
#### [ball-joint-rod-end](ball-joint-rod-end/main.kcl) ([screenshot](screenshots/ball-joint-rod-end.png))
[![ball-joint-rod-end](screenshots/ball-joint-rod-end.png)](ball-joint-rod-end/main.kcl)
#### [bench](bench/main.kcl) ([screenshot](screenshots/bench.png))
[![bench](screenshots/bench.png)](bench/main.kcl)
#### [bone-plate](bone-plate/main.kcl) ([screenshot](screenshots/bone-plate.png))
@ -39,14 +39,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![bracket](screenshots/bracket.png)](bracket/main.kcl)
#### [brake-rotor](brake-rotor/main.kcl) ([screenshot](screenshots/brake-rotor.png))
[![brake-rotor](screenshots/brake-rotor.png)](brake-rotor/main.kcl)
#### [cable-gland](cable-gland/main.kcl) ([screenshot](screenshots/cable-gland.png))
[![cable-gland](screenshots/cable-gland.png)](cable-gland/main.kcl)
#### [camshaft](camshaft/main.kcl) ([screenshot](screenshots/camshaft.png))
[![camshaft](screenshots/camshaft.png)](camshaft/main.kcl)
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png))
[![car-wheel-assembly](screenshots/car-wheel-assembly.png)](car-wheel-assembly/main.kcl)
#### [clock](clock/main.kcl) ([screenshot](screenshots/clock.png))
[![clock](screenshots/clock.png)](clock/main.kcl)
#### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png))
[![cold-plate](screenshots/cold-plate.png)](cold-plate/main.kcl)
#### [color-cube](color-cube/main.kcl) ([screenshot](screenshots/color-cube.png))
@ -65,8 +59,6 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![dodecahedron](screenshots/dodecahedron.png)](dodecahedron/main.kcl)
#### [enclosure](enclosure/main.kcl) ([screenshot](screenshots/enclosure.png))
[![enclosure](screenshots/enclosure.png)](enclosure/main.kcl)
#### [end-effector-grippers](end-effector-grippers/main.kcl) ([screenshot](screenshots/end-effector-grippers.png))
[![end-effector-grippers](screenshots/end-effector-grippers.png)](end-effector-grippers/main.kcl)
#### [engine-valve](engine-valve/main.kcl) ([screenshot](screenshots/engine-valve.png))
[![engine-valve](screenshots/engine-valve.png)](engine-valve/main.kcl)
#### [exhaust-manifold](exhaust-manifold/main.kcl) ([screenshot](screenshots/exhaust-manifold.png))
@ -151,24 +143,16 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![spur-reduction-gearset](screenshots/spur-reduction-gearset.png)](spur-reduction-gearset/main.kcl)
#### [surgical-drill-guide](surgical-drill-guide/main.kcl) ([screenshot](screenshots/surgical-drill-guide.png))
[![surgical-drill-guide](screenshots/surgical-drill-guide.png)](surgical-drill-guide/main.kcl)
#### [t-slot-rail](t-slot-rail/main.kcl) ([screenshot](screenshots/t-slot-rail.png))
[![t-slot-rail](screenshots/t-slot-rail.png)](t-slot-rail/main.kcl)
#### [telemetry-antenna](telemetry-antenna/main.kcl) ([screenshot](screenshots/telemetry-antenna.png))
[![telemetry-antenna](screenshots/telemetry-antenna.png)](telemetry-antenna/main.kcl)
#### [thermal-block-insert](thermal-block-insert/main.kcl) ([screenshot](screenshots/thermal-block-insert.png))
[![thermal-block-insert](screenshots/thermal-block-insert.png)](thermal-block-insert/main.kcl)
#### [tooling-nest-block](tooling-nest-block/main.kcl) ([screenshot](screenshots/tooling-nest-block.png))
[![tooling-nest-block](screenshots/tooling-nest-block.png)](tooling-nest-block/main.kcl)
#### [truss-structure](truss-structure/main.kcl) ([screenshot](screenshots/truss-structure.png))
[![truss-structure](screenshots/truss-structure.png)](truss-structure/main.kcl)
#### [utility-sink](utility-sink/main.kcl) ([screenshot](screenshots/utility-sink.png))
[![utility-sink](screenshots/utility-sink.png)](utility-sink/main.kcl)
#### [walkie-talkie](walkie-talkie/main.kcl) ([screenshot](screenshots/walkie-talkie.png))
[![walkie-talkie](screenshots/walkie-talkie.png)](walkie-talkie/main.kcl)
#### [washer](washer/main.kcl) ([screenshot](screenshots/washer.png))
[![washer](screenshots/washer.png)](washer/main.kcl)
#### [wind-turbine-blade](wind-turbine-blade/main.kcl) ([screenshot](screenshots/wind-turbine-blade.png))
[![wind-turbine-blade](screenshots/wind-turbine-blade.png)](wind-turbine-blade/main.kcl)
#### [wing-spar](wing-spar/main.kcl) ([screenshot](screenshots/wing-spar.png))
[![wing-spar](screenshots/wing-spar.png)](wing-spar/main.kcl)

View File

@ -1,105 +0,0 @@
// ball joint rod end
// A ball joint rod end is a mechanical linkage component that consists of a spherical ball housed within a socket, connected to a threaded rod, allowing rotational movement in multiple directions while providing a secure connection point between two parts of a mechanical system. Commonly used in steering systems and suspension components.
// Set Units
@settings(defaultLengthUnit = in, kclVersion = 1.0)
// variables
ballBoltLength = 6
ballRadius = 8
sketchStartAngle = asin(ballBoltLength / ballRadius)
housingThicknessHalf = 4.5
housingR1 = 11
housingR2 = 8
tolerance = 0.1
shaftR = 8
distanceBetweenEyeAndShaftEnd = 36
radiusToFlat = 12
flatsWidth = 14
tapperInAng = 45
holeDForM8Tap = 6.8
holdDepth = 18
// calculated variables
retainingLoopSketchAngle1 = asin(housingThicknessHalf / housingR1)
retainingLoopSketchAngle2 = asin(housingThicknessHalf / housingR2)
pointOnRingPolar = polar(angle = retainingLoopSketchAngle2 + 90, length = housingR2 + tolerance)
polarY = pointOnRingPolar[1]
intersectPoint = sqrt(pow(housingR1, exp = 2) - pow(shaftR, exp = 2))
// start modeling section
// start with inner ball
ballSketch = startSketchOn(YZ)
ballProfile = startProfile(ballSketch, at = polar(angle = sketchStartAngle + 90, length = ballRadius))
|> arc(angleStart = sketchStartAngle + 90, angleEnd = 90 - sketchStartAngle, radius = ballRadius)
|> yLine(endAbsolute = 4)
|> xLine(endAbsolute = -ballBoltLength)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
ballRevolve = revolve(ballProfile, angle = 360, axis = X)
|> appearance(%, color = "#519afb")
// next the retaining loop that keep the ball in place
retainingLoopSketch = startSketchOn(YZ)
retainingLoopProfile = startProfile(retainingLoopSketch, at = polar(angle = retainingLoopSketchAngle1 + 90, length = housingR1))
|> arc(angleStart = retainingLoopSketchAngle1 + 90, angleEnd = 90 - retainingLoopSketchAngle1, radius = housingR1)
|> yLine(endAbsolute = polarY)
|> arc(angleStart = -retainingLoopSketchAngle2 + 90, angleEnd = 90 + retainingLoopSketchAngle2, radius = housingR2)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
retainingLoopRevolve = revolve(retainingLoopProfile, angle = 360, axis = X)
intersectPoint2 = polar(angle = -5, length = housingR1 - 0.08)
// the shaft is modeled in two parts, and intersected together
// starting with a revolve
threadedShaftBodyRevolveSketch = startSketchOn(XZ)
threadedShaftBodyRevolveProfile = startProfile(threadedShaftBodyRevolveSketch, at = [0, -distanceBetweenEyeAndShaftEnd])
|> xLine(length = shaftR - 0.07, tag = $seg05) // 0.07 dither to make CSG work
|> yLine(endAbsolute = -intersectPoint, tag = $kink)
|> arc(interiorAbsolute = intersectPoint2, endAbsolute = [housingR1 - 0.08, 0])
// |> line(endAbsolute = [housingR1, 0])
|> xLine(endAbsolute = 0)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
threadedShaftBodyRevolve = revolve(threadedShaftBodyRevolveProfile, angle = 360, axis = Y)
// second part of the shalft is a extrude that will add the flats and the filleted taper to get to the retaining ring width
threadedShaftBodySketch = startSketchOn(-YZ)
threadedShaftBodyProfile = startProfile(threadedShaftBodySketch, at = [0, -distanceBetweenEyeAndShaftEnd - 1])
|> xLine(length = flatsWidth / 2)
|> yLine(endAbsolute = -intersectPoint - 2.5 - 0.11, tag = $longflats)
|> angledLine(tag = $seg01, angle = tapperInAng + 90, endAbsoluteX = housingThicknessHalf - 0.1)
|> yLine(endAbsolute = 0, tag = $seg02)
|> xLine(endAbsolute = -housingThicknessHalf + 0.15)
|> yLine(length = -segLen(seg02), tag = $seg03)
|> angledLine(angle = tapperInAng, endAbsoluteX = -flatsWidth / 2, tag = $seg04)
|> yLine(length = -segLen(longflats))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
threadedShaftBodyFlats = extrude(threadedShaftBodyProfile, length = 40)
|> translate(x = 20, y = 0, z = 0)
|> fillet(radius = 1.9, tags = [getCommonEdge(faces = [seg01, seg02])])
|> fillet(radius = 1.5, tags = [getCommonEdge(faces = [seg03, seg04])])
solid001 = intersect([
threadedShaftBodyRevolve,
threadedShaftBodyFlats
])
sketch005 = startSketchOn(-XZ)
profile005 = circle(sketch005, center = [0, 0], radius = ballRadius - 2)
extrude002 = extrude(profile005, length = 100)
|> translate(x = 0, y = -50, z = 0)
solid002 = subtract([solid001], tools = [extrude002])
// Join the thread body with the retaining loop for the balljoint
solid003 = union([solid002, retainingLoopRevolve])
plane001 = offsetPlane(XY, offset = -distanceBetweenEyeAndShaftEnd - 1)
sketch001 = startSketchOn(plane001)
profile001 = circle(sketch001, center = [0, 0], radius = holeDForM8Tap / 2)
threadedRodHole = extrude(profile001, length = holdDepth + 1)
// cut hole for threaded rod
solid004 = subtract([solid003], tools = [threadedRodHole])

View File

@ -1,4 +1,3 @@
// Brake Rotor
// A 320mm vented brake disc (rotor), with straight vanes, 30mm thick. The disc bell should accommodate 5 M12 wheel studs on a 114.3mm pitch circle diameter.

View File

@ -1,39 +0,0 @@
// Cable Gland
// A cable gland is a mechanical fitting used to attach and secure the end of an electrical cable to equipment or an enclosure. It provides strain relief, sealing against environmental factors like dust and moisture, and can ensure earth continuity if required.
// Set units
@settings(defaultLengthUnit = mm)
// Define parameters
width = 40
length = 40
trim = 39
cableDiameter = 14
// Model a starting hexagon with a center securement hole for the cable diameter
core = startSketchOn(XY)
|> polygon(radius = width / 2, numSides = 6, center = [0, 0])
|> subtract2d(tool = circle(center = [0, 0], diameter = cableDiameter))
|> extrude(length)
// Cut around the exterior to form the cable gland shape
revolveCut = startSketchOn(YZ)
|> startProfile(at = [cableDiameter / 2 + 5, -0.01])
|> angledLine(angle = 15, endAbsoluteX = trim / 2)
|> yLine(length = length / 6)
|> angledLine(angle = -15, endAbsoluteX = profileStartX(%))
|> yLine(length = length / 9)
|> xLine(length = -0.9)
|> yLine(length = length / 8)
|> angledLine(angle = 15, endAbsoluteX = trim / 2)
|> yLine(length = length / 6)
|> angledLine(angle = -5, endAbsoluteX = cableDiameter / 2 + 2)
|> yLine(endAbsolute = length + 0.1)
|> xLine(endAbsolute = width / 1.75)
|> yLine(endAbsolute = 0)
|> line(endAbsolute = profileStart())
|> close()
|> revolve(axis = Y)
subtract([core], tools = [revolveCut])
// Assigning a material property to represent brass
|> appearance(color = "#f2671c", metalness = 70, roughness = 30)

View File

@ -1,165 +0,0 @@
// Camshaft
// A camshaft is a shaft with cams attached, used to convert rotational motion into reciprocating motion. In internal combustion engines, it's crucial for controlling the opening and closing of intake and exhaust valves at precise moments, ensuring proper timing for combustion.
// Set units
@settings(defaultLengthUnit = in)
// Define shaft parameters
valvesPerCylinder = 4
cylinderCount = 4
shaftDiameter = 0.97
supportBearingWidth = 0.45
// Define cam lobe parameters
baseCircle = 1.34
lobeSeperation = 112
intakeCenterline = 108
intakeLift = 0.235
exhaustLift = 0.235
intakeDuration = 242
exhaustDuration = 246
camHeight = 0.522
camSpacing = 0.50
// Write a function to sketch a cam lobe profile given the specified parameters
fn lobe(duration, lift) {
camProfile = startSketchOn(offsetPlane(XY, offset = supportBearingWidth * 2))
|> startProfile(at = polar(angle = 90 + duration / 4, length = baseCircle / 2))
|> arc(interiorAbsolute = [0, -baseCircle / 2], endAbsolute = polar(angle = 90 - (duration / 4), length = baseCircle / 2), tag = $seg02)
|> angledLine(angle = tangentToEnd(seg02), endAbsoluteY = baseCircle / 2 + lift, tag = $seg04)
|> xLine(endAbsolute = 0, tag = $seg03)
|> xLine(length = -segLen(seg03))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg05)
|> close()
|> extrude(length = camHeight)
|> fillet(
radius = min([segLen(seg03), segLen(seg04)]) * 0.99,
tags = [
getCommonEdge(faces = [seg04, seg03]),
getCommonEdge(faces = [seg03, seg05])
],
)
|> rotate(yaw = intakeCenterline)
return camProfile
}
// Create an intake and exhaust cam pair
intake = lobe(duration = intakeDuration, lift = intakeLift)
exhaust = lobe(duration = exhaustDuration, lift = exhaustLift)
|> translate(z = camHeight + camSpacing)
|> rotate(yaw = lobeSeperation)
// Pattern the cam pair to represent the number of valves. Round odd number valves to the next highest even number
fn valveCount(@i) {
return {
translate = [0, 0, i * (camHeight + camSpacing) * 2],
rotation = {
angle = lobeSeperation + 30 * i,
// Rotate around the overall scene's origin.
origin = 'global'
}
}
}
// Pattern the cam instances by the number of cylinders. Rotate each group to reflect the relative position of each piston
fn cylinderPattern(@i) {
return {
translate = [
0,
0,
i * (2 * (camHeight + camSpacing) * round(valvesPerCylinder / 2) + supportBearingWidth)
],
rotation = {
angle = 360 / cylinderCount * i,
// Rotate around the overall scene's origin.
origin = 'global'
}
}
}
// Call each pattern function in turn
[intake, exhaust]
|> patternTransform(instances = round(valvesPerCylinder / 2), transform = valveCount)
|> patternTransform(instances = cylinderCount, transform = cylinderPattern)
// Extrude a center connecting cylinder shaft through each cam position
length = (2 * (camHeight + camSpacing) * round(valvesPerCylinder / 2) + supportBearingWidth) * cylinderCount + supportBearingWidth
centerShaft = startSketchOn(XY)
|> circle(center = [0, 0], diameter = shaftDiameter)
|> extrude(length)
// Attach a helical timing gear to the base of the camshaft
fn helicalGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) {
// Calculate gear parameters
pitchDiameter = module * nTeeth
addendum = module
deddendum = 1.25 * module
baseDiameter = pitchDiameter * cos(pressureAngle)
tipDiameter = pitchDiameter + 2 * module
// Define a function to create a rotated gear sketch on an offset plane
fn helicalGearSketch(@offsetHeight) {
// Calculate the amount to rotate each planar sketch of the gear given the gear helix angle and total gear height
helixCalc = acos(offsetHeight * tan(helixAngle) / (tipDiameter / 2))
// Using the gear parameters, sketch an involute tooth spanning from the base diameter to the tip diameter
helicalGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|> involuteCircular(
startRadius = baseDiameter / 2,
endRadius = tipDiameter / 2,
angle = helixCalc,
tag = $seg01,
)
|> line(endAbsolute = polar(angle = 160 / nTeeth + helixCalc, length = tipDiameter / 2))
|> involuteCircular(
startRadius = baseDiameter / 2,
endRadius = tipDiameter / 2,
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
reverse = true,
)
// Position the end line of the sketch at the start of the next tooth
|> line(endAbsolute = polar(angle = 360 / nTeeth + helixCalc, length = baseDiameter / 2))
// Pattern the sketch about the center by the specified number of teeth, then close the sketch
|> patternCircular2d(
%,
instances = nTeeth,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
|> close()
|> subtract2d(tool = circle(center = [0, 0], diameter = shaftDiameter))
return helicalGearSketch
}
// Draw a gear sketch on the base plane
gearcamProfile = helicalGearSketch(0)
// Draw a rotated gear sketch on a middle interstitial plane
gearcenterShaft = helicalGearSketch(gearHeight / 2)
// Draw a rotated gear sketch at the gear height offset plane
gearSketch003 = helicalGearSketch(gearHeight)
// Loft each rotated gear sketch together to form a helical gear
helicalGear = loft([
gearcamProfile,
gearcenterShaft,
gearSketch003
])
return helicalGear
}
// Call the timing gear function
helicalGear(
nTeeth = 15,
module = .15,
pressureAngle = 20,
helixAngle = 55,
gearHeight = supportBearingWidth,
)

View File

@ -1,441 +0,0 @@
// Clock
// A clock with roman numerals
// Define parameters
clockDiameter = 500
clockThickness = 50
minuteHandLength = 100
nubDiameter = 30
numHeight = 10
minuteHandWidth = 25
hourHandWidth = 25
hourHandLargeDiameter = 230
minuteHandLargeDiameter = 310
filletRadius = 5
ridgeThickness = 10
numberThickness = 10
// Calculated parameters
ridgeDiameter = clockDiameter - 50
hourHandArmLength = clockDiameter / 2 * .25
minuteHandArmLength = clockDiameter / 2 * .40
// Add assert for clockDiameter
assert(clockDiameter, isGreaterThan = 450, error = "clock diameter needs to be greater than 400")
// What time is it?
hour = 9
minute = 29
// Calculate hand angles
hourHandAngle = 90 - (hour * 30)
minuteHandAngle = 90 - (minute * 6)
// Create the clock body
clockBodySketch = startSketchOn(XY)
profile001 = circle(
clockBodySketch,
center = [0, 0],
diameter = clockDiameter,
tag = $seg02,
)
clockBody = extrude(profile001, length = clockThickness, tagStart = $capStart001)
|> fillet(
radius = filletRadius,
tags = [
getCommonEdge(faces = [seg02, capStart001])
],
)
// Create the ridge on the top face of the body
clockRidgeSketch = startSketchOn(clockBody, face = END)
profile002 = circle(
clockRidgeSketch,
center = [0, 0],
diameter = clockDiameter,
tag = $seg01,
)
profile003 = circle(clockRidgeSketch, center = [0, 0], diameter = ridgeDiameter)
subtract2d(profile002, tool = profile003)
clockRidge = extrude(profile002, length = ridgeThickness, tagEnd = $capEnd001)
|> fillet(
radius = filletRadius,
tags = [
getCommonEdge(faces = [seg01, capEnd001])
],
)
|> appearance(%, color = "#ab4321")
// Create an object that has all of the x and y starting positions of every number
numberObject = {
// one = { i = [90, 160] },
one = {
i = [
clockDiameter / 2 * 3 / 4 * cos(60),
clockDiameter / 2 * 3 / 4 * sin(60)
]
},
two = {
i = [
clockDiameter / 2 * 3 / 4 * cos(30) - 10,
clockDiameter / 2 * 3 / 4 * sin(30)
],
i2 = [
clockDiameter / 2 * 3 / 4 * cos(30) + 5,
clockDiameter / 2 * 3 / 4 * sin(30)
]
},
three = {
i = [
clockDiameter / 2 * 3 / 4 * cos(0) - 15,
clockDiameter / 2 * 3 / 4 * sin(0)
],
i2 = [
clockDiameter / 2 * 3 / 4 * cos(0),
clockDiameter / 2 * 3 / 4 * sin(0)
],
i3 = [
clockDiameter / 2 * 3 / 4 * cos(0) + 15,
clockDiameter / 2 * 3 / 4 * sin(0)
]
},
four = {
i = [
clockDiameter / 2 * 3 / 4 * cos(-30) - 10,
clockDiameter / 2 * 3 / 4 * sin(-30)
],
v = [
clockDiameter / 2 * 3 / 4 * cos(-30) + 13,
clockDiameter / 2 * 3 / 4 * sin(-30)
]
},
five = {
v = [
clockDiameter / 2 * 3 / 4 * cos(-60),
clockDiameter / 2 * 3 / 4 * sin(-60)
]
},
six = {
v = [
clockDiameter / 2 * 3 / 4 * cos(-90) - 10,
clockDiameter / 2 * 3 / 4 * sin(-90)
],
i = [
clockDiameter / 2 * 3 / 4 * cos(-90) + 12,
clockDiameter / 2 * 3 / 4 * sin(-90)
]
},
seven = {
v = [
clockDiameter / 2 * 3 / 4 * cos(-120) - 15,
clockDiameter / 2 * 3 / 4 * sin(-120)
],
i = [
clockDiameter / 2 * 3 / 4 * cos(-120) + 5,
clockDiameter / 2 * 3 / 4 * sin(-120)
],
i2 = [
clockDiameter / 2 * 3 / 4 * cos(-120) + 20,
clockDiameter / 2 * 3 / 4 * sin(-120)
]
},
eight = {
v = [
clockDiameter / 2 * 3 / 4 * cos(-150) - 10,
clockDiameter / 2 * 3 / 4 * sin(-150)
],
i = [
clockDiameter / 2 * 3 / 4 * cos(-150) + 10,
clockDiameter / 2 * 3 / 4 * sin(-150)
],
i2 = [
clockDiameter / 2 * 3 / 4 * cos(-150) + 25,
clockDiameter / 2 * 3 / 4 * sin(-150)
],
i3 = [
clockDiameter / 2 * 3 / 4 * cos(-150) + 40,
clockDiameter / 2 * 3 / 4 * sin(-150)
]
},
nine = {
i = [
clockDiameter / 2 * 3 / 4 * cos(180) - 15,
clockDiameter / 2 * 3 / 4 * sin(180)
],
x = [
clockDiameter / 2 * 3 / 4 * cos(180) + 15,
clockDiameter / 2 * 3 / 4 * sin(180)
]
},
ten = {
x = [
clockDiameter / 2 * 3 / 4 * cos(150) + 5,
clockDiameter / 2 * 3 / 4 * sin(150)
]
},
eleven = {
x = [
clockDiameter / 2 * 3 / 4 * cos(120),
clockDiameter / 2 * 3 / 4 * sin(120)
],
i = [
clockDiameter / 2 * 3 / 4 * cos(120) + 10,
clockDiameter / 2 * 3 / 4 * sin(120)
]
},
twelve = {
x = [
clockDiameter / 2 * 3 / 4 * cos(90) - 10,
clockDiameter / 2 * 3 / 4 * sin(90)
],
i = [
clockDiameter / 2 * 3 / 4 * cos(90) + 5,
clockDiameter / 2 * 3 / 4 * sin(90)
],
i2 = [
clockDiameter / 2 * 3 / 4 * cos(90) + 20,
clockDiameter / 2 * 3 / 4 * sin(90)
]
}
}
// Function for the letter I
fn letterI(startX, startY) {
iWidth = 8
iLength = 40
return startSketchOn(offsetPlane(XY, offset = 50))
|> startProfile(
%,
at = [
startX - (iWidth / 2),
startY + iLength / 2
],
)
|> xLine(%, length = iWidth)
|> yLine(%, length = -iLength)
|> xLine(%, length = -iWidth)
|> close(%)
|> extrude(%, length = numberThickness)
|> appearance(%, color = "#140f0f")
}
// Function for the letter X
fn letterX(startX, startY) {
xWidth = 40
xLength = 40
return startSketchOn(offsetPlane(XY, offset = 50))
|> startProfile(
%,
at = [
startX - (xWidth / 2),
startY + xLength / 2
],
)
|> xLine(%, length = xWidth / 6)
|> angledLine(%, angle = -70, lengthY = xLength * 1 / 3)
|> angledLine(%, angle = 70, lengthY = xLength * 1 / 3)
|> xLine(%, length = xWidth / 6)
|> angledLine(%, angle = 70 + 180, lengthY = xLength * 1 / 2)
|> angledLine(%, angle = -70, lengthY = xLength * 1 / 2)
|> xLine(%, length = -xWidth / 6)
|> angledLine(%, angle = -70 - 180, lengthY = xLength * 1 / 3)
|> angledLine(%, angle = 70 + 180, lengthY = xLength * 1 / 3)
|> xLine(%, length = -xWidth / 6)
|> angledLine(%, angle = 70, lengthY = xLength * 1 / 2)
|> close(%)
|> extrude(%, length = numberThickness)
|> appearance(%, color = "#140f0f")
}
// Function for the letter V
fn letterV(startX, startY) {
vWidth = 25
vLength = 40
return startSketchOn(offsetPlane(XY, offset = 50))
|> startProfile(
%,
at = [
startX - (vWidth / 2),
startY + vLength / 2
],
)
|> xLine(%, length = vWidth * 1 / 3)
|> line(%, end = [vWidth / 6, -vLength / 2])
|> line(%, end = [vWidth / 6, vLength / 2])
|> xLine(%, length = vWidth * 1 / 3)
|> line(%, end = [-vWidth * 1 / 2, -vLength])
|> close(%)
|> extrude(%, length = numberThickness)
|> appearance(%, color = "#140f0f")
}
// Create the numbers on the face of the clock
// 1 //
letterI(startX = numberObject.one.i[0], startY = numberObject.one.i[1])
// 2 //
letterI(startX = numberObject.two.i[0], startY = numberObject.two.i[1])
letterI(startX = numberObject.two.i2[0], startY = numberObject.two.i2[1])
// 3 //
letterI(startX = numberObject.three.i[0], startY = numberObject.three.i[1])
letterI(startX = numberObject.three.i2[0], startY = numberObject.three.i2[1])
letterI(startX = numberObject.three.i3[0], startY = numberObject.three.i3[1])
// 4 //
letterI(startX = numberObject.four.i[0], startY = numberObject.four.i[1])
letterV(startX = numberObject.four.v[0], startY = numberObject.four.v[1])
// 5 //
letterV(startX = numberObject.five.v[0], startY = numberObject.five.v[1])
// 6 //
letterV(startX = numberObject.six.v[0], startY = numberObject.six.v[1])
letterI(startX = numberObject.six.i[0], startY = numberObject.six.i[1])
// 7 //
letterV(startX = numberObject.seven.v[0], startY = numberObject.seven.v[1])
letterI(startX = numberObject.seven.i[0], startY = numberObject.seven.i[1])
letterI(startX = numberObject.seven.i2[0], startY = numberObject.seven.i2[1])
// 8 //
letterV(startX = numberObject.eight.v[0], startY = numberObject.eight.v[1])
letterI(startX = numberObject.eight.i[0], startY = numberObject.eight.i[1])
letterI(startX = numberObject.eight.i2[0], startY = numberObject.eight.i2[1])
letterI(startX = numberObject.eight.i3[0], startY = numberObject.eight.i3[1])
// 9 //
letterI(startX = numberObject.nine.i[0], startY = numberObject.nine.i[1])
letterX(startX = numberObject.nine.x[0], startY = numberObject.nine.x[1])
// 10 //
letterX(startX = numberObject.ten.x[0], startY = numberObject.ten.x[1])
// 11 //
letterX(startX = numberObject.eleven.x[0], startY = numberObject.eleven.x[1])
letterI(startX = numberObject.eleven.i[0], startY = numberObject.eleven.i[1])
// 12 //
letterX(startX = numberObject.twelve.x[0], startY = numberObject.twelve.x[1])
letterI(startX = numberObject.twelve.i[0], startY = numberObject.twelve.i[1])
letterI(startX = numberObject.twelve.i2[0], startY = numberObject.twelve.i2[1])
// Create nub for the minute and hour hands
startSketchOn(clockBody, face = END)
|> circle(center = [0, 0], diameter = nubDiameter)
|> extrude(%, length = numHeight)
// Create the hour hand
sketch005 = startSketchOn(offsetPlane(XY, offset = 55))
profile007 = startProfile(
sketch005,
at = [
nubDiameter / 2 * 1.375 * cos(hourHandAngle + 20),
nubDiameter / 2 * 1.375 * sin(hourHandAngle + 20)
],
)
|> arc(
%,
interiorAbsolute = [
nubDiameter / 2 * 1.375 * cos(hourHandAngle + 180),
nubDiameter / 2 * 1.375 * sin(hourHandAngle + 180)
],
endAbsolute = [
nubDiameter / 2 * 1.375 * cos(hourHandAngle + 340),
nubDiameter / 2 * 1.375 * sin(hourHandAngle + 340)
],
)
|> angledLine(%, angle = hourHandAngle, length = hourHandArmLength)
|> angledLine(
%,
angle = hourHandAngle - 90,
length = hourHandWidth / 2,
tag = $seg004,
)
|> line(
%,
endAbsolute = [
hourHandLargeDiameter / 2 * cos(hourHandAngle),
hourHandLargeDiameter / 2 * sin(hourHandAngle)
],
tag = $seg002,
)
|> angledLine(%, angle = segAng(seg002) + 120, length = segLen(seg002))
// |> angledLineThatIntersects(%, angle = segAng(seg002) + hourHandAngle - 90, intersectTag = seg004)
|> angledLine(%, angle = hourHandAngle - 90, length = segLen(seg004))
|> line(%, endAbsolute = [profileStartX(%), profileStartY(%)])
|> close(%)
profile008 = circle(sketch005, center = [0, 0], diameter = nubDiameter)
subtract2d(profile007, tool = profile008)
|> extrude(%, length = 5)
|> appearance(%, color = "#404040")
// create the minute hand
sketch006 = startSketchOn(offsetPlane(XY, offset = 50))
profile009 = startProfile(
sketch006,
at = [
nubDiameter / 2 * 1.375 * cos(minuteHandAngle + 20),
nubDiameter / 2 * 1.375 * sin(minuteHandAngle + 20)
],
)
|> arc(
%,
interiorAbsolute = [
nubDiameter / 2 * 1.375 * cos(minuteHandAngle + 180),
nubDiameter / 2 * 1.375 * sin(minuteHandAngle + 180)
],
endAbsolute = [
nubDiameter / 2 * 1.375 * cos(minuteHandAngle + 340),
nubDiameter / 2 * 1.375 * sin(minuteHandAngle + 340)
],
)
|> angledLine(%, angle = minuteHandAngle, length = minuteHandArmLength)
|> angledLine(
%,
angle = minuteHandAngle - 90,
length = minuteHandWidth / 2,
tag = $seg003,
)
|> line(
%,
endAbsolute = [
minuteHandLargeDiameter / 2 * cos(minuteHandAngle),
minuteHandLargeDiameter / 2 * sin(minuteHandAngle)
],
tag = $seg005,
)
|> angledLine(%, angle = segAng(seg005) + 120, length = segLen(seg005))
|> angledLine(%, angle = minuteHandAngle - 90, length = segLen(seg003))
|> line(%, endAbsolute = [profileStartX(%), profileStartY(%)])
|> close(%)
profile010 = circle(sketch006, center = [0, 0], diameter = 30)
subtract2d(profile009, tool = profile010)
|> extrude(%, length = 5)
|> appearance(%, color = "#404040")
// Define parameters of the screw slot for hanging the clock
screwHeadDiameter = 9.53
screwTolerance = .5
slotWidth = (screwHeadDiameter + screwTolerance) / 2
slotLength = 40
// Create the screw slot
sketch003 = startSketchOn(clockBody, face = START)
profile004 = startProfile(sketch003, at = [-slotWidth / 2, 200])
|> yLine(length = -slotLength)
|> arc(
%,
radius = screwHeadDiameter / 2 + screwTolerance,
angleStart = 120,
angleEnd = 420,
)
|> yLine(%, length = slotLength)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> extrude(%, length = -20)
// todo: create cavity for the screw to slide into (need csg update)

View File

@ -1,4 +1,14 @@
[
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "80-20-rail/main.kcl",
"multipleFiles": false,
"title": "80/20 Rail",
"description": "An 80/20 extruded aluminum linear rail. T-slot profile adjustable by profile height, rail length, and origin position",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "axial-fan/main.kcl",
@ -23,16 +33,6 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "ball-joint-rod-end/main.kcl",
"multipleFiles": false,
"title": "ball joint rod end",
"description": "A ball joint rod end is a mechanical linkage component that consists of a spherical ball housed within a socket, connected to a threaded rod, allowing rotational movement in multiple directions while providing a secure connection point between two parts of a mechanical system. Commonly used in steering systems and suspension components.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "bench/main.kcl",
@ -78,28 +78,8 @@
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "brake-rotor/main.kcl",
"multipleFiles": false,
"title": "Brake Rotor",
"description": "A 320mm vented brake disc (rotor), with straight vanes, 30mm thick. The disc bell should accommodate 5 M12 wheel studs on a 114.3mm pitch circle diameter.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "cable-gland/main.kcl",
"multipleFiles": false,
"title": "Cable Gland",
"description": "A cable gland is a mechanical fitting used to attach and secure the end of an electrical cable to equipment or an enclosure. It provides strain relief, sealing against environmental factors like dust and moisture, and can ensure earth continuity if required.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "camshaft/main.kcl",
"multipleFiles": false,
"title": "Camshaft",
"description": "A camshaft is a shaft with cams attached, used to convert rotational motion into reciprocating motion. In internal combustion engines, it's crucial for controlling the opening and closing of intake and exhaust valves at precise moments, ensuring proper timing for combustion.",
"title": "A 320mm vented brake disc (rotor), with straight vanes, 30mm thick. The disc bell should accommodate 5 M12 wheel studs on a 114.3mm pitch circle diameter.",
"description": "",
"files": [
"main.kcl"
]
@ -120,16 +100,6 @@
"parameters.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "clock/main.kcl",
"multipleFiles": false,
"title": "Clock",
"description": "A clock with roman numerals",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "cold-plate/main.kcl",
@ -227,16 +197,6 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "end-effector-grippers/main.kcl",
"multipleFiles": false,
"title": "End Effector Grippers",
"description": "End effector grippers are devices attached to a robot's arm that allow it to interact with its environment and perform tasks like picking up, moving, and manipulating objects. They are essential for robots to perform useful work. Grippers are one type of end effector, but end effectors can also be tools like welding torches or cameras",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "engine-valve/main.kcl",
@ -672,26 +632,6 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "t-slot-rail/main.kcl",
"multipleFiles": false,
"title": "T-Slotted Framing Rail",
"description": "A T-slotted framing rail, or T-slot extrusion, is a rectangular or square aluminum profile with a \"T\" shaped slot along one or more sides. These slots allow for easy attachment of various hardware components like brackets, connectors, and fasteners, making it a versatile and customizable framing system.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "telemetry-antenna/main.kcl",
"multipleFiles": false,
"title": "Aircraft telemetry antenna plate",
"description": "Consists of a circular base plate 3 inches in diameter and 0.08 inches thick, with a tapered monopole antenna mounted at the top with a base diameter of 0.65 inches and height of 1.36 inches. Also consists of a mounting base and connector at the bottom of the plate. The plate also has 6 countersunk holes at a defined pitch circle diameter.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "thermal-block-insert/main.kcl",
@ -712,16 +652,6 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "truss-structure/main.kcl",
"multipleFiles": false,
"title": "Truss Structure",
"description": "A truss structure is a framework composed of triangular units made from straight members connected at joints, often called nodes. Trusses are widely used in architecture, civil engineering, and construction for their ability to support large loads with minimal material.",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "utility-sink/main.kcl",
@ -760,16 +690,6 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "wind-turbine-blade/main.kcl",
"multipleFiles": false,
"title": "Wind Turbine Blade",
"description": "A wind turbine blade is a curved airfoil-shaped propeller that captures wind energy and converts it into rotational motion, ultimately driving a generator to produce electricity. These blades are typically made of composite materials like fiberglass or carbon fiber for strength and durability, and are designed to maximize efficiency in capturing the wind's kinetic energy. Most modern wind turbines use root inserts or a T-bolt connection to join the blade to the pitch bearing. The root insert is a metal bushing imbedded within the laminate",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "wing-spar/main.kcl",

View File

@ -33,9 +33,14 @@ stemLoftProfile2 = startSketchOn(offsetPlane(XY, offset = 75))
// Draw the third profile for the lofted femur
p3Z = 110
p3A = 25
plane003 = {
origin = [0, 0.0, p3Z],
xAxis = [cos(p3A), 0, sin(p3A)],
yAxis = [0.0, 1.0, 0.0]
}
l3 = 32
r3 = 4
stemLoftProfile3 = startSketchOn(XY)
stemLoftProfile3 = startSketchOn(plane003)
|> startProfile(at = [-15.5, -l3 / 2])
|> yLine(length = l3, tag = $seg03)
|> tangentialArc(angle = -120, radius = r3)
@ -44,14 +49,18 @@ stemLoftProfile3 = startSketchOn(XY)
|> angledLine(angle = 30, length = -segLen(seg03))
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> translate(z = p3Z)
|> rotate(pitch = -p3A)
// Draw the fourth profile for the lofted femur
p4Z = 130
p4A = 36.5
plane004 = {
origin = [0, 0.0, p4Z],
xAxis = [cos(p4A), 0, sin(p4A)],
yAxis = [0.0, 1.0, 0.0]
}
l4 = 16
r4 = 5
stemLoftProfile4 = startSketchOn(XY)
stemLoftProfile4 = startSketchOn(plane004)
|> startProfile(at = [-23, -l4 / 2])
|> yLine(length = l4, tag = $seg04)
|> tangentialArc(angle = -120, radius = r4)
@ -60,14 +69,18 @@ stemLoftProfile4 = startSketchOn(XY)
|> angledLine(angle = 30, length = -segLen(seg04))
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> translate(z = p4Z)
|> rotate(pitch = -p4A)
// Draw the first profile for the femoral stem
p5Z = 140
p5A = 36.5
plane005 = {
origin = [0, 0.0, p5Z],
xAxis = [cos(p5A), 0, sin(p5A)],
yAxis = [0.0, 1.0, 0.0]
}
l5 = 1.6
r5 = 1.6
stemLoftProfile5 = startSketchOn(XY)
stemLoftProfile5 = startSketchOn(plane005)
|> startProfile(at = [-19.5, -l5 / 2])
|> yLine(length = l5, tag = $seg05)
|> tangentialArc(angle = -120, radius = r5)
@ -76,14 +89,18 @@ stemLoftProfile5 = startSketchOn(XY)
|> angledLine(angle = 30, length = -segLen(seg05))
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> translate(z = p5Z)
|> rotate(pitch = -p5A)
// Draw the second profile for the femoral stem
p6Z = 145
p6A = 36.5
plane006 = {
origin = [0, 0.0, p6Z],
xAxis = [cos(p6A), 0, sin(p6A)],
yAxis = [0.0, 1.0, 0.0]
}
l6 = 1
r6 = 3
stemLoftProfile6 = startSketchOn(XY)
stemLoftProfile6 = startSketchOn(plane006)
|> startProfile(at = [-23.4, -l6 / 2])
|> yLine(length = l6, tag = $seg06)
|> tangentialArc(angle = -120, radius = r6)
@ -92,24 +109,27 @@ stemLoftProfile6 = startSketchOn(XY)
|> angledLine(angle = 30, length = -segLen(seg06))
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> translate(z = p6Z)
|> rotate(pitch = -p6A)
// Draw the third profile for the femoral stem
stemTab = clone(stemLoftProfile6)
|> extrude(%, length = 6)
// Loft the femur using all profiles in sequence
femur = loft([
stemLoftProfile1,
stemLoftProfile2,
stemLoftProfile3,
stemLoftProfile4
])
// Loft the femoral stem
femoralStem = loft([
clone(stemLoftProfile4),
stemLoftProfile5,
clone(stemLoftProfile6)
stemLoftProfile6
])
// Draw the third profile for the femoral stem
stemTab = stemLoftProfile6
|> extrude(length = 6)
// Revolve a hollow socket to represent the femoral head
femoralHead = startSketchOn(XZ)
|> startProfile(at = [4, 0])

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@ -1,58 +0,0 @@
// T-Slotted Framing Rail
// A T-slotted framing rail, or T-slot extrusion, is a rectangular or square aluminum profile with a "T" shaped slot along one or more sides. These slots allow for easy attachment of various hardware components like brackets, connectors, and fasteners, making it a versatile and customizable framing system.
// Set units
@settings(defaultLengthUnit = in, kclVersion = 1.0)
// Define parameters
interiorRadius = 0.01
scoreDepth = 0.018
arcEnd = 0.0275
holeDiameter = 0.262
fn railTslot(railHeight, railLength) {
// Sketch one inner leg of the extruded rail
railProfile = startSketchOn(XZ)
|> startProfile(at = [0.5, (1 - 0.356) / 2])
|> xLine(length = -0.08)
|> tangentialArc(angle = 45, radius = .09)
|> angledLine(angle = 45, endAbsoluteY = 0.113)
|> tangentialArc(angle = 135, radius = interiorRadius)
|> xLine(endAbsolute = .5 - (.320 / 2) - interiorRadius)
|> tangentialArc(angle = -90, radius = interiorRadius)
|> yLine(endAbsolute = interiorRadius)
|> tangentialArc(angle = -90, radius = interiorRadius)
|> xLine(length = -0.03)
|> arc(angleStart = 0, angleEnd = 180, radius = scoreDepth)
|> xLine(length = -0.1)
|> arc(angleStart = 0, angleEnd = 180, radius = scoreDepth)
|> xLine(length = -0.03)
|> tangentialArc(endAbsolute = [arcEnd, arcEnd])
// Mirror the sketch about the diagonal to complete the leg. Then mirror across the center of the profile in the horizontal and vertical directions. Then close the sketch
|> mirror2d(axis = {
direction = [1.0, 1.0],
origin = [0.0, 0.0]
})
|> mirror2d(axis = {
direction = [1.0, 0.0],
origin = [0.0, 0.5]
})
|> mirror2d(axis = {
direction = [0.0, 1.0],
origin = [0.5, 0.0]
})
|> close()
// Sketch a dimensioned hole in the center of the profile
|> subtract2d(tool = circle(center = [railHeight / 2, railHeight / 2], radius = holeDiameter / 2))
// Scale the entire sketch by a factor of the rail height, then extrude
|> scale(x = railHeight, z = railHeight)
|> extrude(length = -railLength)
return railProfile
}
// Generate one rail using the rail function
railTslot(railHeight = 1.5, railLength = 2ft)

View File

@ -1,63 +0,0 @@
// Aircraft telemetry antenna plate
// Consists of a circular base plate 3 inches in diameter and 0.08 inches thick, with a tapered monopole antenna mounted at the top with a base diameter of 0.65 inches and height of 1.36 inches. Also consists of a mounting base and connector at the bottom of the plate. The plate also has 6 countersunk holes at a defined pitch circle diameter.
// Set units
@settings(defaultLengthUnit = in)
// Define parameters
plateThickness = 0.08
plateDia = 3
antennaBaseDia = 0.65
antennaAngle = 95
antennaHeight = 1.36
seatingDia = 0.625
totalHeight = 2.14
boltDiameter = .196
boltPitchCircleDiameter = 2.5
// 2D cross-sectional profile of the part that will later be revolved
antennaCrossSectionSketch = startSketchOn(YZ)
antennaCrossSectionProfile = startProfile(antennaCrossSectionSketch, at = [plateDia / 2, 0])
|> yLine(length = plateThickness)
|> xLine(length = -(plateDia - antennaBaseDia) / 2, tag = $seg03)
|> angledLine(angle = antennaAngle, length = 1.1, tag = $seg01)
|> tangentialArc(endAbsolute = [0.025, antennaHeight])
|> xLine(endAbsolute = 0, tag = $seg02)
|> yLine(length = -totalHeight)
|> xLine(length = .25)
|> yLine(length = .05)
|> angledLine(angle = 45, length = 0.025)
|> yLine(length = .125)
|> angledLine(angle = 135, length = 0.025)
|> yLine(length = .125)
|> xLine(length = .025)
|> yLine(length = .025)
|> xLine(endAbsolute = seatingDia / 2)
|> yLine(endAbsolute = -0.25)
|> xLine(endAbsolute = 0.6)
|> yLine(endAbsolute = 0)
|> close()
// Revolution about y-axis of earlier profile
antennaCrossSectionRevolve = revolve(antennaCrossSectionProfile, angle = 360, axis = Y)
// Function to create a countersunk hole
fn countersink(@holePosition) {
startSketchOn(antennaCrossSectionRevolve, face = seg03)
|> circle(center = holePosition, radius = boltDiameter / 2, tag = $hole01)
|> extrude(length = -plateThickness)
|> chamfer(length = 0.04, tags = [hole01])
return { }
}
// PCD converted to radius for positioning the holes
r = boltPitchCircleDiameter / 2
// 6 countersunk holes using the countersink function
countersink([r, 0]) // 0 °
countersink([r * 0.5, r * 0.8660254]) // 60 °
countersink([-r * 0.5, r * 0.8660254]) // 120 °
countersink([-r, 0]) // 180 °
countersink([-r * 0.5, -r * 0.8660254]) // 240 °
countersink([r * 0.5, -r * 0.8660254]) // 300 °

View File

@ -1,142 +0,0 @@
// Truss Structure
// A truss structure is a framework composed of triangular units made from straight members connected at joints, often called nodes. Trusses are widely used in architecture, civil engineering, and construction for their ability to support large loads with minimal material.
@settings(defaultLengthUnit = in)
// Define parameters
thickness = 4
totalLength = 180
totalWidth = 120
totalHeight = 120
legHeight = 48
topTrussAngle = 25
beamWidth = 4
beamLength = 2
sparAngle = 30
nFrames = 3
crossBeamLength = 82
// Sketch the top frame
topFrameSketch = startSketchOn(YZ)
profile001 = startProfile(topFrameSketch, at = [totalWidth / 2, 0])
|> xLine(length = -totalWidth, tag = $bottomFace)
|> yLine(length = 12)
|> angledLine(angle = topTrussAngle, endAbsoluteX = 0, tag = $tag001)
|> angledLine(angle = -topTrussAngle, endAbsoluteX = totalWidth / 2, tag = $tag002)
|> close()
// Create two holes in the top frame sketch to create the center beam
profile002 = startProfile(topFrameSketch, at = [totalWidth / 2 - thickness, thickness])
|> xLine(endAbsolute = thickness / 2)
|> yLine(endAbsolute = segEndY(tag001) - thickness)
|> angledLine(endAbsoluteX = profileStartX(%), angle = -topTrussAngle)
|> close(%)
profile003 = startProfile(topFrameSketch, at = [-totalWidth / 2 + thickness, thickness])
|> xLine(endAbsolute = -thickness / 2)
|> yLine(endAbsolute = segEndY(tag001) - thickness)
|> angledLine(endAbsoluteX = profileStartX(%), angle = 180 + topTrussAngle)
|> close(%)
profile004 = subtract2d(profile001, tool = profile002)
subtract2d(profile001, tool = profile003)
// Extrude the sketch to make the top frame
topFrame = extrude(profile001, length = beamLength)
// Spar 1
sketch002 = startSketchOn(offsetPlane(YZ, offset = .1))
profile006 = startProfile(sketch002, at = [thickness / 2 - 1, 14])
|> angledLine(angle = sparAngle, length = 25)
|> angledLine(angle = -topTrussAngle, length = 5)
|> angledLine(angle = 180 + sparAngle, endAbsoluteX = profileStartX(%))
|> close(%)
spar001 = extrude(profile006, length = 1.8)
// Spar2
profile007 = startProfile(sketch002, at = [-thickness / 2 + 1, 14])
|> angledLine(angle = 180 - sparAngle, length = 25)
|> angledLine(angle = 180 + topTrussAngle, length = 5)
|> angledLine(angle = -sparAngle, endAbsoluteX = profileStartX(%))
|> close(%)
spar002 = extrude(profile007, length = 1.8)
// Combine the top frame with the intermediate support beams
newFrame = topFrame + spar001 + spar002
// Create the two legs on the frame
leg001Sketch = startSketchOn(offsetPlane(XY, offset = .1))
legProfile001 = startProfile(leg001Sketch, at = [0, -totalWidth / 2])
|> xLine(%, length = beamLength - .1)
|> yLine(%, length = beamWidth - 1)
|> xLine(%, endAbsolute = profileStartX(%))
|> close(%)
legProfile002 = startProfile(leg001Sketch, at = [0, totalWidth / 2])
|> xLine(%, length = beamLength - .1)
|> yLine(%, length = -(beamWidth - 1))
|> xLine(%, endAbsolute = profileStartX(%))
|> close(%)
leg001 = extrude(legProfile001, length = -legHeight - .1)
leg002 = extrude(legProfile002, length = -legHeight - .1)
// Combine the top frame with the legs and pattern
fullFrame = newFrame + leg001 + leg002
|> patternLinear3d(
%,
instances = nFrames,
distance = crossBeamLength + beamLength,
axis = [-1, 0, 0],
)
// Create the center cross beam
centerCrossBeamSketch = startSketchOn(YZ)
profile005 = startProfile(centerCrossBeamSketch, at = [0, segEndY(tag001) - 1])
|> angledLine(%, angle = -topTrussAngle, length = beamWidth * 3 / 8)
|> yLine(length = -beamWidth * 3 / 8)
|> angledLine(%, angle = 180 - topTrussAngle, length = beamWidth * 3 / 8)
|> angledLine(%, angle = 180 + topTrussAngle, length = beamWidth * 3 / 8)
|> yLine(length = beamWidth * 3 / 8)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
// Extrude the center cross beam and pattern to every frame
centerCrossBeam = extrude(profile005, length = -crossBeamLength)
|> patternLinear3d(
%,
instances = nFrames - 1,
distance = crossBeamLength + beamLength,
axis = [-1, 0, 0],
)
// Create the two side cross beams
sideCrossBeamSketch = startSketchOn(-YZ)
profile008 = startProfile(
sideCrossBeamSketch,
at = [
-totalWidth / 2 + 0.5,
segEndY(tag002) - .5
],
)
|> yLine(length = -beamLength)
|> xLine(length = 3 / 4 * beamWidth)
|> yLine(length = beamLength)
|> close()
profile009 = startProfile(sideCrossBeamSketch, at = [totalWidth / 2, segEndY(tag002) - .5])
|> yLine(length = -beamLength)
|> xLine(%, length = -3 / 4 * beamWidth)
|> yLine(%, length = beamLength)
|> close(%)
// Extrude the side cross beams and pattern to every frame.
extrude([profile008, profile009], length = crossBeamLength)
|> patternLinear3d(
%,
instances = nFrames - 1,
distance = crossBeamLength + beamLength,
axis = [-1, 0, 0],
)

View File

@ -1,172 +0,0 @@
// Wind Turbine Blade
// A wind turbine blade is a curved airfoil-shaped propeller that captures wind energy and converts it into rotational motion, ultimately driving a generator to produce electricity. These blades are typically made of composite materials like fiberglass or carbon fiber for strength and durability, and are designed to maximize efficiency in capturing the wind's kinetic energy. Most modern wind turbines use root inserts or a T-bolt connection to join the blade to the pitch bearing. The root insert is a metal bushing imbedded within the laminate
// Set Units
@settings(defaultLengthUnit = m)
// Define parameters
interfaceDiameter = 1.5
bladeLength = 52
wallThickness = 0.2
// Model the base of the turbine blade
baseExtrude = startSketchOn(YZ)
|> circle(center = [0, 0], radius = interfaceDiameter / 2)
|> subtract2d(tool = circle(center = [0, 0], radius = interfaceDiameter / 2 - wallThickness))
|> extrude(length = 1)
// Create an interface on the base of the turbine blade
interfaceClearance = startSketchOn(baseExtrude, face = START)
|> circle(
center = [
(interfaceDiameter - wallThickness) / 2,
0
],
radius = 50mm,
)
|> patternCircular2d(
instances = 16,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = false,
)
|> extrude(length = -750mm)
// Fill each interface hole with a metal insert nut
insertNut = startSketchOn(XY)
|> startProfile(at = [
0,
(interfaceDiameter - wallThickness) / 2 + 50mm
])
|> yLine(length = 25mm)
|> xLine(length = -5mm)
|> yLine(length = -30mm)
|> xLine(length = 600mm)
|> yLine(endAbsolute = profileStartY(%))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> revolve(axis = {
direction = [1, 0],
origin = [
0,
(interfaceDiameter - wallThickness) / 2
]
})
|> patternCircular3d(
instances = 16,
axis = [1, 0, 0],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = false,
)
|> appearance(color = '#b87333')
// Model a root insert at each interface nut
rootInsert = startSketchOn(XY)
|> startProfile(at = [
0,
(interfaceDiameter - wallThickness) / 2 + 45mm
])
|> xLine(length = 0.09)
|> tangentialArc(endAbsolute = [
0.5,
(interfaceDiameter - wallThickness) / 2 + 5mm
])
|> yLine(length = -4.5mm)
|> xLine(length = -0.73)
|> yLine(length = 18mm)
|> xLine(length = 200mm)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> revolve(axis = {
direction = [1, 0],
origin = [
0,
(interfaceDiameter - wallThickness) / 2
]
})
|> patternCircular3d(
instances = 16,
axis = [1, 0, 0],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = false,
)
// Create a sketch on the end of the base plate to begin the composite blade
baseEnd = startSketchOn(offsetPlane(YZ, offset = 1))
|> circle(center = [0, 0], radius = interfaceDiameter / 2)
|> subtract2d(tool = circle(center = [0, 0], radius = 0.55))
// Define a function to sketch the hollow airfoil section of a wind turbine blade
fn fanBladeSketch(r1, offsetDistance, angle, armLength) {
d1 = r1 + armLength
airfoilCenter = startSketchOn(offsetPlane(YZ, offset = offsetDistance))
|> startProfile(at = [0, d1])
|> angledLine(angle = -90 + asin(r1 / d1), length = r1 / tan(asin(r1 / d1)))
|> tangentialArc(endAbsolute = polar(angle = -220, length = r1))
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> rotate(roll = angle)
r2 = r1 + wallThickness
d2 = d1 + wallThickness
fanBladeSketch = startSketchOn(offsetPlane(YZ, offset = offsetDistance))
|> startProfile(at = [0, d2])
|> angledLine(angle = -90 + asin(r2 / d2), length = r2 / tan(asin(r2 / d2)))
|> tangentialArc(endAbsolute = polar(angle = -220, length = r2))
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> subtract2d(tool = airfoilCenter)
|> rotate(roll = angle)
return fanBladeSketch
}
// Define a function to sketch the solid airfoil section of a wind turbine blade cap
fn endCapSketch(r1, offsetDistance, angle, armLength) {
d1 = r1 + armLength
r2 = r1 + wallThickness
d2 = d1 + wallThickness
endCapSketch = startSketchOn(offsetPlane(YZ, offset = offsetDistance))
|> startProfile(at = [0, d2])
|> angledLine(angle = -90 + asin(r2 / d2), length = r2 / tan(asin(r2 / d2)))
|> tangentialArc(endAbsolute = polar(angle = -220, length = r2))
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> rotate(roll = angle)
return endCapSketch
}
// Use the defined functions to create a lofted wind turbine blade
blade01 = fanBladeSketch(
r1 = interfaceDiameter / 2,
offsetDistance = bladeLength / 3,
angle = -30,
armLength = 1.4,
)
blade02 = fanBladeSketch(
r1 = interfaceDiameter / 2,
offsetDistance = bladeLength - 1,
angle = -20,
armLength = 0.4,
)
loft([baseEnd, blade01, blade02])
|> appearance(color = "#ffffff")
// Use the defined functions to create a lofted end cap
cap01 = endCapSketch(
r1 = interfaceDiameter / 2,
offsetDistance = bladeLength - 1,
angle = -20,
armLength = 0.4,
)
cap02 = endCapSketch(
r1 = 0.075,
offsetDistance = bladeLength,
angle = -20,
armLength = .1,
)
loft([cap01, cap02])
|> appearance(color = "#ffffff")

View File

@ -1,93 +0,0 @@
// Zoo Tag
// A metal tag with a ZOO logo cutout
// Set units
@settings(defaultLengthUnit = in)
// Define parameters
tagLength = 2
tagHeight = 1.125
sheetThickness = 0.090
// Create the tag body
tagSketch = startSketchOn(XZ)
|> startProfile(at = [.25, 0])
|> arc(interiorAbsolute = [0, tagHeight / 2], endAbsolute = [tagLength / 10, tagHeight])
|> xLine(length = tagLength * 4 / 5)
|> arc(interiorAbsolute = [tagLength, tagHeight / 2], endAbsolute = [tagLength * 9 / 10, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> subtract2d(tool = circle(center = [tagLength / 15, tagHeight / 2], diameter = tagLength / 20))
tagBody = extrude(tagSketch, length = sheetThickness)
// Define a function to draw the ZOO "Z"
fn zLogo(surface, origin, fontSize, depth) {
zSketch = startSketchOn(surface)
|> startProfile(at = [0 + origin[0], 0.15 + origin[1]])
|> yLine(length = -0.15)
|> xLine(length = 0.15)
|> angledLine(angle = 47.15, endAbsoluteX = 0.3 + origin[0], tag = $seg1)
|> yLine(endAbsolute = 0 + origin[1], tag = $seg3)
|> xLine(length = 0.63)
|> yLine(length = 0.225)
|> xLine(length = -0.57)
|> angledLine(angle = 47.15, endAbsoluteX = 0.93 + origin[0])
|> yLine(length = 0.15)
|> xLine(length = -0.15)
|> angledLine(angle = 47.15, length = -segLen(seg1), tag = $seg2)
|> yLine(length = segLen(seg3))
|> xLine(endAbsolute = 0 + origin[0])
|> yLine(length = -0.225)
|> angledLineThatIntersects(angle = 0, intersectTag = seg2, offset = 0)
|> close()
|> scale(x = fontSize / 72, y = fontSize / 72, z = fontSize / 72)
|> extrude(length = depth)
return zSketch
}
// Define a function to draw the ZOO "O"
fn oLogo(surface, origin, fontSize, depth) {
otagSketch = startSketchOn(surface)
|> startProfile(at = [.788 + origin[0], .921 + origin[1]])
|> arc(angleStart = 47.15 + 6, angleEnd = 47.15 - 6 + 180, radius = .525)
|> angledLine(angle = 47.15, length = .24)
|> arc(angleStart = 47.15 - 11 + 180, angleEnd = 47.15 + 11, radius = .288)
|> close(%)
|> scale(x = fontSize / 72, y = fontSize / 72, z = fontSize / 72)
|> extrude(length = depth)
oSketch002 = startSketchOn(surface)
|> startProfile(at = [.16 + origin[0], .079 + origin[1]])
|> arc(angleStart = 47.15 + 6 - 180, angleEnd = 47.15 - 6, radius = .525)
|> angledLine(angle = 47.15, length = -.24)
|> arc(angleStart = 47.15 - 11, angleEnd = 47.15 + 11 - 180, radius = .288)
|> close(%)
|> scale(x = fontSize / 72, y = fontSize / 72, z = fontSize / 72)
|> extrude(length = depth)
return [otagSketch, oSketch002]
}
// Create each letter using the letter functions
z = zLogo(
surface = XZ,
origin = [0.5, .6],
fontSize = 36,
depth = sheetThickness,
)
o = oLogo(
surface = XZ,
origin = [1.525, .6],
fontSize = 36,
depth = sheetThickness,
)
oo = oLogo(
surface = XZ,
origin = [2.655, .6],
fontSize = 36,
depth = sheetThickness,
)
// Cut each letter from the tag body
subtract([tagBody], tools = [z, o, oo])

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/kcma-logomark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

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