Compare commits
1 Commits
v1.0.1
...
codex/upda
Author | SHA1 | Date | |
---|---|---|---|
5135badef7 |
2
.github/ci-cd-scripts/upload-results.sh
vendored
@ -6,7 +6,6 @@ if [ -z "${TAB_API_URL:-}" ] || [ -z "${TAB_API_KEY:-}" ]; then
|
||||
fi
|
||||
|
||||
project="https://github.com/KittyCAD/modeling-app"
|
||||
suite="${CI_SUITE:-unit}"
|
||||
branch="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}"
|
||||
commit="${CI_COMMIT_SHA:-${GITHUB_SHA:-}}"
|
||||
|
||||
@ -14,7 +13,6 @@ echo "Uploading batch results"
|
||||
curl --silent --request POST \
|
||||
--header "X-API-Key: ${TAB_API_KEY}" \
|
||||
--form "project=${project}" \
|
||||
--form "suite=${suite}" \
|
||||
--form "branch=${branch}" \
|
||||
--form "commit=${commit}" \
|
||||
--form "tests=@test-results/junit.xml" \
|
||||
|
13
.github/workflows/cargo-test.yml
vendored
@ -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
|
||||
@ -94,7 +88,6 @@ jobs:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
||||
ZOO_HOST: https://api.dev.zoo.dev
|
||||
RUST_BACKTRACE: full
|
||||
RUST_MIN_STACK: 10485760000
|
||||
- name: Commit differences
|
||||
if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure'
|
||||
shell: bash
|
||||
@ -126,7 +119,6 @@ jobs:
|
||||
# Configure nextest when it's run by insta (via just).
|
||||
NEXTEST_PROFILE: ci
|
||||
RUST_BACKTRACE: full
|
||||
RUST_MIN_STACK: 10485760000
|
||||
- name: Build and archive tests
|
||||
run: |
|
||||
cd rust
|
||||
@ -190,7 +182,6 @@ jobs:
|
||||
env:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
||||
ZOO_HOST: https://api.dev.zoo.dev
|
||||
RUST_MIN_STACK: 10485760000
|
||||
- name: Upload results
|
||||
if: always()
|
||||
run: .github/ci-cd-scripts/upload-results.sh
|
||||
@ -199,7 +190,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:kcl
|
||||
run-internal-kcl-samples:
|
||||
name: cargo test (internal-kcl-samples)
|
||||
runs-on:
|
||||
@ -248,7 +238,6 @@ jobs:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
|
||||
ZOO_HOST: https://api.dev.zoo.dev
|
||||
MODELING_APP_INTERNAL_SAMPLES_SECRET: ${{secrets.MODELING_APP_INTERNAL_SAMPLES_SECRET}}
|
||||
RUST_MIN_STACK: 10485760000
|
||||
run-wasm-tests:
|
||||
name: Run wasm tests
|
||||
strategy:
|
||||
|
19
.github/workflows/e2e-tests.yml
vendored
@ -1,5 +1,4 @@
|
||||
name: E2E Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
@ -144,7 +143,7 @@ jobs:
|
||||
- name: Install browsers
|
||||
run: npm run playwright install --with-deps
|
||||
|
||||
- name: Test snapshots
|
||||
- name: Capture snapshots
|
||||
uses: nick-fields/retry@v3.0.2
|
||||
with:
|
||||
shell: bash
|
||||
@ -157,19 +156,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:snapshots
|
||||
TARGET: web
|
||||
|
||||
- name: Update snapshots
|
||||
if: always()
|
||||
run: npm run test:snapshots -- --last-failed --update-snapshots
|
||||
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:snapshots
|
||||
TARGET: web
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
@ -187,7 +173,7 @@ jobs:
|
||||
id: git-check
|
||||
run: |
|
||||
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
|
||||
if git status | grep --quiet "Changes to be committed"
|
||||
if git status | grep -q "Changes to be committed"
|
||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
@ -320,7 +306,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
|
||||
|
@ -122,11 +122,12 @@ https://github.com/KittyCAD/modeling-app/issues/new
|
||||
|
||||
#### 2. Push a new tag
|
||||
|
||||
Decide on a `v`-prefixed semver `VERSION` (eg. `v1.2.3`) with the team and tag the repo, eg. on 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`.
|
||||
|
||||
```
|
||||
VERSION=$(./scripts/semantic-release.sh)
|
||||
git tag $VERSION
|
||||
git push origin $VERSION
|
||||
git push origin --tags
|
||||
```
|
||||
|
||||
This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files.
|
||||
|
10
INSTALL.md
@ -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,16 +12,16 @@ 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.
|
||||
|
||||
3. You can then open your `Applications` directory and double-click on `Zoo Design Studio` to open.
|
||||
|
||||
|
||||
## Linux
|
||||
## 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.
|
||||
@ -29,7 +29,7 @@ Compared to other CAD software, getting Zoo Design Studio up and running is quic
|
||||
sudo apt update
|
||||
sudo apt install libfuse2
|
||||
```
|
||||
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
|
||||
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
|
||||
- Once installed, copy the downloaded `Zoo Design Studio-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
|
||||
|
||||
- `appimaged` should automatically find it and make it executable. If not, run:
|
||||
|
12
README.md
@ -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,14 @@ 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.
|
||||
|
||||
If you'd like to try out upcoming changes sooner, you can also download those from our [nightly releases](https://zoo.dev/modeling-app/download/nightly) page.
|
||||
|
||||
## 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.
|
||||
|
@ -4,7 +4,7 @@ excerpt: "Documentation of the KCL language for the Zoo Design Studio."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
This is a reference for KCL. If you are learning KCL, you may prefer the [guide](https://zoo.dev/docs/kcl-book/intro.html) which explains
|
||||
This is a reference for KCL. If you are learning KCL, you may prefer the [guide]() which explains
|
||||
things in a more tutorial fashion. See also our documentation of the [standard library](/docs/kcl-std).
|
||||
|
||||
## Topics
|
||||
|
@ -27,6 +27,9 @@ import increment from "util.kcl"
|
||||
answer = increment(41)
|
||||
```
|
||||
|
||||
Imported files _must_ be in the same project so that units are uniform across
|
||||
modules. This means that it must be in the same directory.
|
||||
|
||||
Import statements must be at the top-level of a file. It is not allowed to have
|
||||
an `import` statement inside a function or in the body of an if‑else.
|
||||
|
||||
@ -55,9 +58,6 @@ Imported symbols can be renamed for convenience or to avoid name collisions.
|
||||
import increment as inc, decrement as dec from "util.kcl"
|
||||
```
|
||||
|
||||
You can import files from the current directory or from subdirectories, but if importing from a
|
||||
subdirectory you can only import `main.kcl`.
|
||||
|
||||
---
|
||||
|
||||
## Functions vs `clone`
|
||||
@ -177,7 +177,7 @@ You can also import the whole module. This is useful if you want to use the
|
||||
result of a module as a variable, like a part.
|
||||
|
||||
```norun
|
||||
import "cube.kcl"
|
||||
import "tests/inputs/cube.kcl" as cube
|
||||
cube
|
||||
|> translate(x=10)
|
||||
```
|
||||
@ -229,19 +229,6 @@ The final statement is what's important because it's the return value of the
|
||||
entire module. The module is expected to return a single object that can be used
|
||||
as a variable by the file that imports it.
|
||||
|
||||
The name of the file or subdirectory is used as the name of the variable within the importing program.
|
||||
If you want to use a different name, you can do so by using the `as` keyword:
|
||||
|
||||
```kcl,norun
|
||||
import "cube.kcl" // Introduces a new variable called `cube`.
|
||||
import "cube.kcl" as block // Introduces a new variable called `block`.
|
||||
import "cube/main.kcl" // Introduces a new variable called `cube`.
|
||||
import "cube/main.kcl" as block // Introduces a new variable called `block`.
|
||||
```
|
||||
|
||||
If the filename includes hyphens (`-`) or starts with an underscore (`_`), then you must specify a
|
||||
variable name.
|
||||
|
||||
---
|
||||
|
||||
## Multiple instances of the same import
|
||||
@ -254,7 +241,7 @@ If you want to have multiple instances of the same object, you can use the
|
||||
[`clone`](/docs/kcl/clone) function. This will render a new instance of the object in memory.
|
||||
|
||||
```norun
|
||||
import cube from "cube.kcl"
|
||||
import cube from "tests/inputs/cube.kcl"
|
||||
|
||||
cube
|
||||
|> translate(x=10)
|
||||
@ -270,7 +257,7 @@ separate objects in memory, and can be manipulated independently.
|
||||
Here is an example with a file from another CAD system:
|
||||
|
||||
```kcl
|
||||
import "tests/inputs/cube.step"
|
||||
import "tests/inputs/cube.step" as cube
|
||||
|
||||
cube
|
||||
|> translate(x=10)
|
||||
|
@ -12,7 +12,7 @@ reduce(
|
||||
@array: [any],
|
||||
initial: any,
|
||||
f: fn(any, accum: any): any,
|
||||
): any
|
||||
): [any]
|
||||
```
|
||||
|
||||
Take a starting value. Then, for each element of an array, calculate the next value,
|
||||
@ -28,7 +28,7 @@ using the previous value and the element.
|
||||
|
||||
### Returns
|
||||
|
||||
[`any`](/docs/kcl-std/types/std-types-any)
|
||||
[`[any]`](/docs/kcl-std/types/std-types-any)
|
||||
|
||||
|
||||
### Examples
|
||||
|
@ -11,7 +11,7 @@ Compute the length of the given leg.
|
||||
legLen(
|
||||
hypotenuse: number(Length),
|
||||
leg: number(Length),
|
||||
): number(Length)
|
||||
): number(deg)
|
||||
```
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ legLen(
|
||||
|
||||
### Returns
|
||||
|
||||
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
|
||||
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
|
||||
|
||||
|
||||
### Examples
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -14,6 +14,8 @@ mirror2d(
|
||||
): Sketch
|
||||
```
|
||||
|
||||
Only works on unclosed sketches for now.
|
||||
|
||||
Mirror occurs around a local sketch axis rather than a global axis.
|
||||
|
||||
### Arguments
|
||||
|
@ -65,7 +65,7 @@ layout: manual
|
||||
* [`line`](/docs/kcl-std/line)
|
||||
* [`loft`](/docs/kcl-std/loft)
|
||||
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
|
||||
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d)
|
||||
* [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
|
||||
* [`polygon`](/docs/kcl-std/polygon)
|
||||
* [`profileStart`](/docs/kcl-std/profileStart)
|
||||
* [`profileStartX`](/docs/kcl-std/profileStartX)
|
||||
@ -94,7 +94,7 @@ layout: manual
|
||||
* [`intersect`](/docs/kcl-std/intersect)
|
||||
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
|
||||
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
|
||||
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform)
|
||||
* [`patternTransform`](/docs/kcl-std/patternTransform)
|
||||
* [`shell`](/docs/kcl-std/functions/std-solid-shell)
|
||||
* [`subtract`](/docs/kcl-std/subtract)
|
||||
* [`union`](/docs/kcl-std/union)
|
||||
|
@ -30,7 +30,7 @@ This module contains functions for creating and manipulating sketches, and makin
|
||||
* [`line`](/docs/kcl-std/line)
|
||||
* [`loft`](/docs/kcl-std/loft)
|
||||
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
|
||||
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d)
|
||||
* [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
|
||||
* [`polygon`](/docs/kcl-std/polygon)
|
||||
* [`profileStart`](/docs/kcl-std/profileStart)
|
||||
* [`profileStartX`](/docs/kcl-std/profileStartX)
|
||||
|
@ -18,7 +18,7 @@ This module contains functions for modifying solids, e.g., by adding a fillet or
|
||||
* [`intersect`](/docs/kcl-std/intersect)
|
||||
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
|
||||
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
|
||||
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform)
|
||||
* [`patternTransform`](/docs/kcl-std/patternTransform)
|
||||
* [`shell`](/docs/kcl-std/functions/std-solid-shell)
|
||||
* [`subtract`](/docs/kcl-std/subtract)
|
||||
* [`union`](/docs/kcl-std/union)
|
||||
|
@ -11,7 +11,7 @@ Contains frequently used constants, functions for interacting with the KittyCAD
|
||||
|
||||
The standard library is organised into modules (listed below), but most things are always available in KCL programs.
|
||||
|
||||
You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide](https://zoo.dev/docs/kcl-book/intro.html).
|
||||
You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide]().
|
||||
|
||||
## Modules
|
||||
|
||||
|
@ -12,8 +12,8 @@ patternCircular2d(
|
||||
@sketchSet: [Sketch],
|
||||
instances: number,
|
||||
center: Point2d,
|
||||
arcDegrees?: number,
|
||||
rotateDuplicates?: bool,
|
||||
arcDegrees: number,
|
||||
rotateDuplicates: bool,
|
||||
useOriginal?: bool,
|
||||
): [Sketch]
|
||||
```
|
||||
@ -27,8 +27,8 @@ patternCircular2d(
|
||||
| `sketchSet` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch(es) to pattern | 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 |
|
||||
| `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center about which to make the pattern. This is a 2D vector. | Yes |
|
||||
| `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. Defaults to 360. | No |
|
||||
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. Defaults to true. | No |
|
||||
| `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
|
||||
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. | 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. Defaults to false. | No |
|
||||
|
||||
### Returns
|
||||
|
@ -1,19 +1,19 @@
|
||||
---
|
||||
title: "patternTransform2d"
|
||||
subtitle: "Function in std::sketch"
|
||||
excerpt: "Just like `patternTransform`, but works on 2D sketches not 3D solids."
|
||||
excerpt: "Just like patternTransform, but works on 2D sketches not 3D solids."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Just like `patternTransform`, but works on 2D sketches not 3D solids.
|
||||
Just like patternTransform, but works on 2D sketches not 3D solids.
|
||||
|
||||
```kcl
|
||||
patternTransform2d(
|
||||
@sketches: [Sketch; 1+],
|
||||
instances: number(_),
|
||||
transform: fn(number(_)): { },
|
||||
useOriginal?: boolean,
|
||||
): [Sketch; 1+]
|
||||
@sketches: [Sketch],
|
||||
instances: number,
|
||||
transform: FunctionSource,
|
||||
useOriginal?: bool,
|
||||
): [Sketch]
|
||||
```
|
||||
|
||||
|
||||
@ -22,14 +22,14 @@ patternTransform2d(
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketches` | [`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) | The sketch(es) 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` | `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 |
|
||||
| `sketches` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | The sketch(es) 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` | `FunctionSource` | 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. Defaults to false. | No |
|
||||
|
||||
### Returns
|
||||
|
||||
[`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch)
|
||||
[`[Sketch]`](/docs/kcl-std/types/std-types-Sketch)
|
||||
|
||||
|
||||
### Examples
|
21101
docs/kcl-std/std.json
@ -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')
|
||||
@ -229,48 +235,6 @@ extrude001 = extrude(sketch001, length = 5)`
|
||||
.first()
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
test('KCL errors with functions show hints for the entire backtrace', async ({
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
cmdBar,
|
||||
editor,
|
||||
toolbar,
|
||||
}) => {
|
||||
await homePage.goToModelingScene()
|
||||
await scene.settled(cmdBar)
|
||||
|
||||
const code = `fn check(@x) {
|
||||
return assert(x, isGreaterThan = 0)
|
||||
}
|
||||
|
||||
fn middle(@x) {
|
||||
return check(x)
|
||||
}
|
||||
|
||||
middle(1)
|
||||
middle(0)
|
||||
`
|
||||
await test.step('Set the code with a KCL error', async () => {
|
||||
await toolbar.openPane('code')
|
||||
await editor.replaceCode('', code)
|
||||
})
|
||||
// This shows all the diagnostics in a way that doesn't require the mouse
|
||||
// pointer hovering over a coordinate, which would be brittle.
|
||||
await test.step('Open CodeMirror diagnostics list', async () => {
|
||||
// Ensure keyboard focus is in the editor.
|
||||
await page.getByText('fn check(').click()
|
||||
await page.keyboard.press('ControlOrMeta+Shift+M')
|
||||
})
|
||||
await expect(
|
||||
page.getByText(`assert failed: Expected 0 to be greater than 0 but it wasn't
|
||||
check()
|
||||
middle()`)
|
||||
).toBeVisible()
|
||||
// There should be one hint inside middle() and one at the top level.
|
||||
await expect(page.getByText('Part of the error backtrace')).toHaveCount(2)
|
||||
})
|
||||
})
|
||||
|
||||
test(
|
||||
|
@ -36,10 +36,7 @@ test.describe('Command bar tests', () => {
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Click the line of code for xLine.
|
||||
await page.getByText(`startProfile(at = [-10, -10])`).click()
|
||||
|
||||
// Wait for the selection to register (TODO: we need a definitive way to wait for this)
|
||||
await page.waitForTimeout(200)
|
||||
await page.getByText(`close()`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||
|
||||
await toolbar.extrudeButton.click()
|
||||
await cmdBar.expectState({
|
||||
@ -48,10 +45,10 @@ test.describe('Command bar tests', () => {
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Profiles: '',
|
||||
Sketches: '',
|
||||
Length: '',
|
||||
},
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
highlightedHeaderArg: 'sketches',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
@ -59,7 +56,7 @@ test.describe('Command bar tests', () => {
|
||||
stage: 'review',
|
||||
commandName: 'Extrude',
|
||||
headerArguments: {
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 segment',
|
||||
Length: '5',
|
||||
},
|
||||
})
|
||||
@ -289,7 +286,7 @@ test.describe('Command bar tests', () => {
|
||||
await cmdBar.cmdOptions.getByText('Extrude').click()
|
||||
|
||||
// Assert that we're on the selection step
|
||||
await expect(page.getByRole('button', { name: 'Profiles' })).toBeDisabled()
|
||||
await expect(page.getByRole('button', { name: 'sketches' })).toBeDisabled()
|
||||
// Select a face
|
||||
await page.mouse.move(700, 200)
|
||||
await page.mouse.click(700, 200)
|
||||
@ -402,6 +399,7 @@ test.describe('Command bar tests', () => {
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await page.goto(page.url() + targetURL)
|
||||
expect(page.url()).toContain(targetURL)
|
||||
})
|
||||
|
||||
await test.step(`Submit the command`, async () => {
|
||||
@ -412,7 +410,7 @@ test.describe('Command bar tests', () => {
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Method: '',
|
||||
Name: 'main.kcl',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
highlightedHeaderArg: 'method',
|
||||
@ -423,7 +421,7 @@ test.describe('Command bar tests', () => {
|
||||
commandName: 'Import file from URL',
|
||||
headerArguments: {
|
||||
Method: 'New project',
|
||||
Name: 'main.kcl',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
})
|
||||
@ -465,6 +463,7 @@ test.describe('Command bar tests', () => {
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await page.goto(page.url() + targetURL)
|
||||
expect(page.url()).toContain(targetURL)
|
||||
})
|
||||
|
||||
await test.step(`Submit the command`, async () => {
|
||||
@ -475,7 +474,7 @@ test.describe('Command bar tests', () => {
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Method: '',
|
||||
Name: 'main.kcl',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
highlightedHeaderArg: 'method',
|
||||
@ -488,7 +487,7 @@ test.describe('Command bar tests', () => {
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
Name: 'main.kcl',
|
||||
Name: 'test',
|
||||
ProjectName: '',
|
||||
Code: '1 line',
|
||||
},
|
||||
@ -501,7 +500,7 @@ test.describe('Command bar tests', () => {
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
ProjectName: 'testProjectDir',
|
||||
Name: 'main.kcl',
|
||||
Name: 'test',
|
||||
Code: '1 line',
|
||||
},
|
||||
})
|
||||
@ -511,7 +510,7 @@ test.describe('Command bar tests', () => {
|
||||
await test.step(`Ensure we created the project and are in the modeling scene`, async () => {
|
||||
await editor.expectEditor.toContain('extrusionDistance = 12')
|
||||
await toolbar.openPane('files')
|
||||
await toolbar.expectFileTreeState(['main-1.kcl', 'main.kcl'])
|
||||
await toolbar.expectFileTreeState(['main.kcl', 'test.kcl'])
|
||||
})
|
||||
})
|
||||
|
||||
@ -662,56 +661,4 @@ c = 3 + a`
|
||||
`a = 5b = a * amyParameter001 = ${newValue}c = 3 + a`
|
||||
)
|
||||
})
|
||||
|
||||
test('Command palette can be opened via query parameter', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
await page.goto(`${page.url()}/?cmd=app.theme&groupId=settings`)
|
||||
await homePage.expectState({
|
||||
projectCards: [],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
commandName: 'Settings · app · theme',
|
||||
currentArgKey: 'value',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Level: 'user',
|
||||
Value: '',
|
||||
},
|
||||
highlightedHeaderArg: 'value',
|
||||
})
|
||||
})
|
||||
|
||||
test('Text-to-CAD command can be closed with escape while in prompt', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
await homePage.expectState({
|
||||
projectCards: [],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await homePage.textToCadBtn.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
commandName: 'Text-to-CAD Create',
|
||||
currentArgKey: 'prompt',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Method: 'New project',
|
||||
NewProjectName: 'untitled',
|
||||
Prompt: '',
|
||||
},
|
||||
highlightedHeaderArg: 'prompt',
|
||||
})
|
||||
await page.keyboard.press('Escape')
|
||||
await cmdBar.toBeClosed()
|
||||
await cmdBar.expectState({
|
||||
stage: 'commandBarClosed',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -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', '')
|
||||
)
|
||||
})
|
||||
@ -1131,8 +1131,6 @@ sketch001 = startSketchOn(XZ)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.getByText('startProfile(at = [4.61, -14.01])').click()
|
||||
// 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({
|
||||
@ -1140,7 +1138,7 @@ sketch001 = startSketchOn(XZ)
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: '5',
|
||||
headerArguments: {
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Length: '',
|
||||
},
|
||||
highlightedHeaderArg: 'length',
|
||||
@ -1150,7 +1148,7 @@ sketch001 = startSketchOn(XZ)
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Length: '5',
|
||||
},
|
||||
commandName: 'Extrude',
|
||||
@ -1590,38 +1588,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)
|
||||
})
|
||||
})
|
||||
|
@ -238,26 +238,6 @@ test.describe('when using the file tree to', () => {
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`create new folders and that doesn't trigger a navigation`,
|
||||
{ tag: ['@electron', '@macos', '@windows'] },
|
||||
async ({ page, homePage, scene, toolbar, cmdBar }) => {
|
||||
await homePage.goToModelingScene()
|
||||
await scene.settled(cmdBar)
|
||||
await toolbar.openPane('files')
|
||||
const { createNewFolder } = await getUtils(page, test)
|
||||
|
||||
await createNewFolder('folder')
|
||||
|
||||
await createNewFolder('folder.kcl')
|
||||
|
||||
await test.step(`Postcondition: folders are created and we didn't navigate`, async () => {
|
||||
await toolbar.expectFileTreeState(['folder', 'folder.kcl', 'main.kcl'])
|
||||
await expect(toolbar.fileName).toHaveText('main.kcl')
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'deleting all files recreates a default main.kcl with no code',
|
||||
{ tag: '@electron' },
|
||||
|
@ -105,19 +105,14 @@ export class CmdBarFixture {
|
||||
expectState = async (expected: CmdBarSerialised) => {
|
||||
return expect.poll(() => this._serialiseCmdBar()).toEqual(expected)
|
||||
}
|
||||
/**
|
||||
* This method is used to progress the command bar to the next step, defaulting to clicking the next button.
|
||||
* Optionally, with the `shouldUseKeyboard` parameter, it will hit `Enter` to progress.
|
||||
* * TODO: This method assumes the user has a valid input to the current stage,
|
||||
/** The method will use buttons OR press enter randomly to progress the cmdbar,
|
||||
* this could have unexpected results depending on what's focused
|
||||
*
|
||||
* TODO: This method assumes the user has a valid input to the current stage,
|
||||
* and assumes we are past the `pickCommand` step.
|
||||
*/
|
||||
progressCmdBar = async (shouldUseKeyboard = false) => {
|
||||
progressCmdBar = async (shouldFuzzProgressMethod = true) => {
|
||||
await this.page.waitForTimeout(2000)
|
||||
if (shouldUseKeyboard) {
|
||||
await this.page.keyboard.press('Enter')
|
||||
return
|
||||
}
|
||||
|
||||
const arrowButton = this.page.getByRole('button', {
|
||||
name: 'arrow right Continue',
|
||||
})
|
||||
@ -151,7 +146,9 @@ export class CmdBarFixture {
|
||||
await this.cmdBarOpenBtn.click()
|
||||
await expect(this.page.getByPlaceholder('Search commands')).toBeVisible()
|
||||
if (selectCmd === 'promptToEdit') {
|
||||
const promptEditCommand = this.selectOption({ name: 'Text-to-CAD Edit' })
|
||||
const promptEditCommand = this.page.getByText(
|
||||
'Use Zoo AI to edit your parts and code.'
|
||||
)
|
||||
await expect(promptEditCommand.first()).toBeVisible()
|
||||
await promptEditCommand.first().scrollIntoViewIfNeeded()
|
||||
await promptEditCommand.first().click()
|
||||
@ -313,11 +310,6 @@ export class CmdBarFixture {
|
||||
await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 })
|
||||
}
|
||||
|
||||
async toBeClosed() {
|
||||
// Check that the command bar is closed
|
||||
await expect(this.cmdBarElement).not.toBeVisible({ timeout: 10_000 })
|
||||
}
|
||||
|
||||
async expectArgValue(value: string) {
|
||||
// Check the placeholder project name exists
|
||||
const actualArgument = await this.cmdBarElement
|
||||
|
@ -26,7 +26,6 @@ export class HomePageFixture {
|
||||
sortByNameBtn!: Locator
|
||||
appHeader!: Locator
|
||||
tutorialBtn!: Locator
|
||||
textToCadBtn!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
@ -48,7 +47,6 @@ export class HomePageFixture {
|
||||
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
|
||||
this.appHeader = this.page.getByTestId('app-header')
|
||||
this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
|
||||
this.textToCadBtn = this.page.getByTestId('home-text-to-cad')
|
||||
}
|
||||
|
||||
private _serialiseSortBy = async (): Promise<
|
||||
@ -123,13 +121,11 @@ export class HomePageFixture {
|
||||
await projectCard.click()
|
||||
}
|
||||
|
||||
/** Returns the project name in case caller has used the default and needs it */
|
||||
goToModelingScene = async (name = 'testDefault') => {
|
||||
goToModelingScene = async (name: string = 'testDefault') => {
|
||||
// On web this is a no-op. There is no project view.
|
||||
if (process.env.PLATFORM === 'web') return ''
|
||||
if (process.env.PLATFORM === 'web') return
|
||||
|
||||
await this.createAndGoToProject(name)
|
||||
return name
|
||||
}
|
||||
|
||||
isNativeFileMenuCreated = async () => {
|
||||
|
@ -61,7 +61,6 @@ class MyAPIReporter implements Reporter {
|
||||
const payload = {
|
||||
// Required information
|
||||
project: 'https://github.com/KittyCAD/modeling-app',
|
||||
suite: process.env.CI_SUITE || 'e2e',
|
||||
branch: process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || '',
|
||||
commit: process.env.CI_COMMIT_SHA || process.env.GITHUB_SHA || '',
|
||||
test: test.titlePath().slice(2).join(' › '),
|
||||
|
@ -252,7 +252,7 @@ test.describe(
|
||||
tronApp,
|
||||
'Edit.Modify with Zoo Text-To-CAD'
|
||||
)
|
||||
await cmdBar.expectCommandName('Text-to-CAD Edit')
|
||||
await cmdBar.expectCommandName('Prompt-to-edit')
|
||||
})
|
||||
await test.step('Modeling.Edit.Edit parameter', async () => {
|
||||
await page.waitForTimeout(250)
|
||||
@ -518,7 +518,7 @@ test.describe(
|
||||
'Design.Create with Zoo Text-To-CAD'
|
||||
)
|
||||
await cmdBar.toBeOpened()
|
||||
await cmdBar.expectCommandName('Text-to-CAD Create')
|
||||
await cmdBar.expectCommandName('Text to CAD')
|
||||
})
|
||||
|
||||
await test.step('Modeling.Design.Modify with Zoo Text-To-CAD', async () => {
|
||||
@ -528,7 +528,7 @@ test.describe(
|
||||
'Design.Modify with Zoo Text-To-CAD'
|
||||
)
|
||||
await cmdBar.toBeOpened()
|
||||
await cmdBar.expectCommandName('Text-to-CAD Edit')
|
||||
await cmdBar.expectCommandName('Prompt-to-edit')
|
||||
})
|
||||
|
||||
await test.step('Modeling.Help.KCL code samples', async () => {
|
||||
|
@ -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,25 +167,6 @@ 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 }
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -78,8 +78,8 @@ test.describe('Point-and-click tests', () => {
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: { Profiles: '', Length: '' },
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
headerArguments: { Sketches: '', Length: '' },
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Extrude',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
@ -87,7 +87,7 @@ test.describe('Point-and-click tests', () => {
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: '5',
|
||||
headerArguments: { Profiles: '1 profile', Length: '' },
|
||||
headerArguments: { Sketches: '1 face', Length: '' },
|
||||
highlightedHeaderArg: 'length',
|
||||
commandName: 'Extrude',
|
||||
})
|
||||
@ -98,7 +98,7 @@ test.describe('Point-and-click tests', () => {
|
||||
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: { Profiles: '1 profile', Length: '5' },
|
||||
headerArguments: { Sketches: '1 face', Length: '5' },
|
||||
commandName: 'Extrude',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
@ -1634,15 +1634,15 @@ sketch002 = startSketchOn(plane001)
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: { Profiles: '' },
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
headerArguments: { Sketches: '' },
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await selectSketches()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: { Profiles: '2 profiles' },
|
||||
headerArguments: { Sketches: '2 faces' },
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.submit()
|
||||
@ -1658,14 +1658,14 @@ sketch002 = startSketchOn(plane001)
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: { Profiles: '' },
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
headerArguments: { Sketches: '' },
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: { Profiles: '2 profiles' },
|
||||
headerArguments: { Sketches: '2 faces' },
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.submit()
|
||||
@ -1830,10 +1830,10 @@ sketch002 = startSketchOn(XZ)
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '',
|
||||
Sketches: '',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
highlightedHeaderArg: 'sketches',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await clickOnSketch1()
|
||||
@ -1844,7 +1844,7 @@ sketch002 = startSketchOn(XZ)
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'path',
|
||||
@ -1857,7 +1857,7 @@ sketch002 = startSketchOn(XZ)
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'path',
|
||||
@ -1867,17 +1867,13 @@ sketch002 = startSketchOn(XZ)
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
headerArguments: {
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '1 segment',
|
||||
Sectional: '',
|
||||
},
|
||||
stage: 'review',
|
||||
})
|
||||
// Confirm we can submit from the review step with just `Enter`
|
||||
await cmdBar.progressCmdBar(true)
|
||||
await cmdBar.expectState({
|
||||
stage: 'commandBarClosed',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
@ -1972,10 +1968,10 @@ profile001 = ${circleCode}`
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '',
|
||||
Sketches: '',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
highlightedHeaderArg: 'sketches',
|
||||
stage: 'arguments',
|
||||
})
|
||||
await editor.scrollToText(circleCode)
|
||||
@ -1987,7 +1983,7 @@ profile001 = ${circleCode}`
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'path',
|
||||
@ -2001,7 +1997,7 @@ profile001 = ${circleCode}`
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Sectional: '',
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '',
|
||||
},
|
||||
highlightedHeaderArg: 'path',
|
||||
@ -2011,13 +2007,13 @@ profile001 = ${circleCode}`
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Sweep',
|
||||
headerArguments: {
|
||||
Profiles: '1 profile',
|
||||
Sketches: '1 face',
|
||||
Path: '1 helix',
|
||||
Sectional: '',
|
||||
},
|
||||
stage: 'review',
|
||||
})
|
||||
await cmdBar.progressCmdBar(true)
|
||||
await cmdBar.progressCmdBar()
|
||||
await editor.expectEditor.toContain(sweepDeclaration)
|
||||
})
|
||||
|
||||
@ -3695,7 +3691,7 @@ tag=$rectangleSegmentC002,
|
||||
await scene.settled(cmdBar)
|
||||
|
||||
// select line of code
|
||||
const codeToSelection = `startProfile(at = [-66.77, 84.81])`
|
||||
const codeToSelection = `segAng(rectangleSegmentA002) - 90,`
|
||||
// revolve
|
||||
await editor.scrollToText(codeToSelection)
|
||||
await page.getByText(codeToSelection).click()
|
||||
@ -4638,10 +4634,10 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Profiles: '',
|
||||
Sketches: '',
|
||||
Length: '',
|
||||
},
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Extrude',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
@ -4650,7 +4646,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: '5',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
Length: '',
|
||||
},
|
||||
highlightedHeaderArg: 'length',
|
||||
@ -4661,7 +4657,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
Length: '1',
|
||||
},
|
||||
commandName: 'Extrude',
|
||||
@ -4732,11 +4728,11 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Profiles: '',
|
||||
Sketches: '',
|
||||
Path: '',
|
||||
Sectional: '',
|
||||
},
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Sweep',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
@ -4745,7 +4741,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
currentArgKey: 'path',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
Path: '',
|
||||
Sectional: '',
|
||||
},
|
||||
@ -4758,7 +4754,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
Path: '1 segment',
|
||||
Sectional: '',
|
||||
},
|
||||
@ -4829,11 +4825,11 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
currentArgKey: 'sketches',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Profiles: '',
|
||||
Sketches: '',
|
||||
AxisOrEdge: '',
|
||||
Angle: '',
|
||||
},
|
||||
highlightedHeaderArg: 'Profiles',
|
||||
highlightedHeaderArg: 'sketches',
|
||||
commandName: 'Revolve',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
@ -4842,7 +4838,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
currentArgKey: 'axisOrEdge',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
AxisOrEdge: '',
|
||||
Angle: '',
|
||||
},
|
||||
@ -4858,7 +4854,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
currentArgKey: 'angle',
|
||||
currentArgValue: '360',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
AxisOrEdge: 'Edge',
|
||||
Edge: '1 segment',
|
||||
Angle: '',
|
||||
@ -4871,7 +4867,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Profiles: '2 profiles',
|
||||
Sketches: '2 faces',
|
||||
AxisOrEdge: 'Edge',
|
||||
Edge: '1 segment',
|
||||
Angle: '180',
|
||||
|
@ -11,7 +11,6 @@ import {
|
||||
getPlaywrightDownloadDir,
|
||||
getUtils,
|
||||
isOutOfViewInScrollContainer,
|
||||
runningOnWindows,
|
||||
} from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
@ -1980,6 +1979,7 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
// Flaky
|
||||
test(
|
||||
'Original project name persist after onboarding',
|
||||
{ tag: '@electron' },
|
||||
@ -2064,55 +2064,3 @@ test(
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'import from nested directory',
|
||||
{ tag: ['@electron', '@windows', '@macos'] },
|
||||
async ({ scene, cmdBar, context, page }) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
const nestedDir = path.join(bracketDir, 'nested')
|
||||
await fsp.mkdir(nestedDir, { recursive: true })
|
||||
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder-inches.kcl'),
|
||||
path.join(nestedDir, 'main.kcl')
|
||||
)
|
||||
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`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
const pointOnModel = { x: 630, y: 280 }
|
||||
|
||||
await test.step('Opening the bracket project should load the stream', async () => {
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await scene.settled(cmdBar)
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [125, 125, 125]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -995,8 +995,8 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// click profile in code
|
||||
await page.getByText(`startProfile(at = [-0.45, 0.87])`).click()
|
||||
// click "line(end = [1.32, 0.38])"
|
||||
await page.getByText(`line(end = [1.32, 0.38])`).click()
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
||||
{ timeout: 10_000 }
|
||||
@ -1014,14 +1014,14 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
|
||||
// click extrude
|
||||
await toolbar.extrudeButton.click()
|
||||
|
||||
// sketch selection should already have been made.
|
||||
// sketch selection should already have been made. "Sketches: 1 face" only show up when the selection has been made already
|
||||
// otherwise the cmdbar would be waiting for a selection.
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: '5',
|
||||
headerArguments: { Profiles: '1 profile', Length: '' },
|
||||
headerArguments: { Sketches: '1 segment', Length: '' },
|
||||
highlightedHeaderArg: 'length',
|
||||
commandName: 'Extrude',
|
||||
})
|
||||
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 116 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 132 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 63 KiB |
@ -557,14 +557,6 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
||||
})
|
||||
},
|
||||
|
||||
createNewFolder: async (name: string) => {
|
||||
return test?.step(`Create a folder named ${name}`, async () => {
|
||||
await page.getByTestId('create-folder-button').click()
|
||||
await page.getByTestId('tree-input-field').fill(name)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
},
|
||||
|
||||
cloneFile: async (name: string) => {
|
||||
return test?.step(`Cloning file '${name}'`, async () => {
|
||||
await page
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
@ -1,11 +1,12 @@
|
||||
import fs from 'fs'
|
||||
import { join } from 'path'
|
||||
import type { Page } from '@playwright/test'
|
||||
|
||||
import { createProject, getUtils } from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||
|
||||
test.describe('Text-to-CAD tests', () => {
|
||||
test('basic lego happy case', async ({ page, homePage, cmdBar }) => {
|
||||
test('basic lego happy case', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await test.step('Set up', async () => {
|
||||
@ -14,11 +15,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await u.waitForPageLoad()
|
||||
})
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x4 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -59,7 +56,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('success model, then ignore success toast, user can create new prompt from command bar', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -68,11 +64,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x6 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x6 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -90,11 +82,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||
|
||||
// Can send a new prompt from the command bar.
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x4 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -112,7 +100,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('you can reject text-to-cad output and it does nothing', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -121,11 +108,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x4 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -158,7 +141,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('sending a bad prompt fails, can dismiss', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -168,11 +150,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await u.waitForPageLoad()
|
||||
|
||||
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
randomPrompt,
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, randomPrompt)
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -210,7 +188,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('sending a bad prompt fails, can start over from toast', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -220,7 +197,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await u.waitForPageLoad()
|
||||
|
||||
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
||||
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -279,7 +256,6 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -289,7 +265,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await u.waitForPageLoad()
|
||||
|
||||
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
||||
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -316,11 +292,7 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||
|
||||
// They should be able to try again from the command bar.
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x4 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
@ -338,40 +310,17 @@ test.describe('Text-to-CAD tests', () => {
|
||||
test('ensure you can shift+enter in the prompt box', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
const projectName = await homePage.goToModelingScene()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
const promptWithNewline = `a 2x4\nlego`
|
||||
|
||||
await test.step('Get to the prompt step to test', async () => {
|
||||
await cmdBar.openCmdBar()
|
||||
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
|
||||
|
||||
await cmdBar.currentArgumentInput.fill('existing')
|
||||
await cmdBar.progressCmdBar()
|
||||
|
||||
await cmdBar.currentArgumentInput.fill(projectName)
|
||||
await cmdBar.progressCmdBar()
|
||||
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Text-to-CAD Create',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'prompt',
|
||||
currentArgValue: '',
|
||||
highlightedHeaderArg: 'prompt',
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
ProjectName: projectName,
|
||||
Prompt: '',
|
||||
},
|
||||
})
|
||||
})
|
||||
await page.getByTestId('text-to-cad').click()
|
||||
|
||||
// Type the prompt.
|
||||
await page.keyboard.type('a 2x4')
|
||||
@ -401,10 +350,96 @@ 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,
|
||||
}) => {
|
||||
// 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 sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x8 lego')
|
||||
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x10 lego')
|
||||
|
||||
// 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,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -413,16 +448,11 @@ test.describe('Text-to-CAD tests', () => {
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
page,
|
||||
'a 2x4 lego',
|
||||
cmdBar
|
||||
)
|
||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
||||
|
||||
await sendPromptFromCommandBarAndSetExistingProject(
|
||||
await sendPromptFromCommandBarTriggeredByButton(
|
||||
page,
|
||||
'alkjsdnlajshdbfjlhsbdf a;askjdnf',
|
||||
cmdBar
|
||||
'alkjsdnlajshdbfjlhsbdf a;askjdnf'
|
||||
)
|
||||
|
||||
// Find the toast.
|
||||
@ -496,9 +526,7 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
const textToCadCommand = page.getByRole('option', {
|
||||
name: 'Text-to-CAD Create',
|
||||
})
|
||||
const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API')
|
||||
await expect(textToCadCommand.first()).toBeVisible()
|
||||
// Click the Text-to-CAD command
|
||||
await textToCadCommand.first().scrollIntoViewIfNeeded()
|
||||
@ -516,63 +544,96 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
|
||||
})
|
||||
}
|
||||
|
||||
async function sendPromptFromCommandBarAndSetExistingProject(
|
||||
async function sendPromptFromCommandBarTriggeredByButton(
|
||||
page: Page,
|
||||
promptStr: string,
|
||||
cmdBar: CmdBarFixture,
|
||||
projectName = 'testDefault'
|
||||
promptStr: string
|
||||
) {
|
||||
await page.waitForTimeout(1000)
|
||||
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
||||
await cmdBar.openCmdBar()
|
||||
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
|
||||
await page.getByTestId('text-to-cad').click()
|
||||
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Text-to-CAD Create',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'method',
|
||||
currentArgValue: '',
|
||||
highlightedHeaderArg: 'method',
|
||||
headerArguments: {
|
||||
Method: '',
|
||||
Prompt: '',
|
||||
},
|
||||
})
|
||||
await cmdBar.currentArgumentInput.fill('existing')
|
||||
await cmdBar.progressCmdBar()
|
||||
// Enter the prompt.
|
||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
||||
await expect(prompt.first()).toBeVisible()
|
||||
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Text-to-CAD Create',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'projectName',
|
||||
currentArgValue: '',
|
||||
highlightedHeaderArg: 'projectName',
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
ProjectName: '',
|
||||
Prompt: '',
|
||||
},
|
||||
})
|
||||
await cmdBar.currentArgumentInput.fill(projectName)
|
||||
await cmdBar.progressCmdBar()
|
||||
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Text-to-CAD Create',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'prompt',
|
||||
currentArgValue: '',
|
||||
highlightedHeaderArg: 'prompt',
|
||||
headerArguments: {
|
||||
Method: 'Existing project',
|
||||
ProjectName: projectName,
|
||||
Prompt: '',
|
||||
},
|
||||
})
|
||||
await cmdBar.currentArgumentInput.fill(promptStr)
|
||||
await cmdBar.progressCmdBar()
|
||||
// Type the prompt.
|
||||
await page.keyboard.type(promptStr)
|
||||
await page.waitForTimeout(200)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
}
|
||||
|
||||
test(
|
||||
'Text-to-CAD functionality',
|
||||
{ tag: '@electron' },
|
||||
async ({ context, page }, 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 sendPromptFromCommandBarTriggeredByButton(page, prompt)
|
||||
// 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
|
||||
@ -712,12 +773,12 @@ 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('main.kcl')
|
||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
||||
).toBeVisible()
|
||||
}
|
||||
)
|
||||
@ -806,12 +867,12 @@ 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()
|
||||
}
|
||||
)
|
||||
@ -852,7 +913,7 @@ 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 page.getByRole('button', { name: 'Reject' }).click()
|
||||
@ -900,7 +961,7 @@ 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'
|
||||
)
|
||||
}
|
||||
)
|
||||
@ -1006,13 +1067,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()
|
||||
}
|
||||
)
|
||||
@ -1152,14 +1213,18 @@ 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('main.kcl')
|
||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
||||
).toBeVisible()
|
||||
|
||||
await expect(
|
||||
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||
).not.toBeVisible()
|
||||
}
|
||||
)
|
||||
|
||||
@ -1298,13 +1363,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')
|
||||
|
@ -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
@ -2492,7 +2492,6 @@
|
||||
},
|
||||
"node_modules/@clack/prompts/node_modules/is-unicode-supported": {
|
||||
"version": "1.3.0",
|
||||
"extraneous": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
@ -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}
|
||||
'''),
|
||||
|
@ -37,8 +37,6 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](bottle/main.kcl)
|
||||
#### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png))
|
||||
[](bracket/main.kcl)
|
||||
#### [brake-rotor](brake-rotor/main.kcl) ([screenshot](screenshots/brake-rotor.png))
|
||||
[](brake-rotor/main.kcl)
|
||||
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png))
|
||||
[](car-wheel-assembly/main.kcl)
|
||||
#### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png))
|
||||
@ -51,16 +49,12 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](countersunk-plate/main.kcl)
|
||||
#### [cpu-cooler](cpu-cooler/main.kcl) ([screenshot](screenshots/cpu-cooler.png))
|
||||
[](cpu-cooler/main.kcl)
|
||||
#### [curtain-wall-anchor-plate](curtain-wall-anchor-plate/main.kcl) ([screenshot](screenshots/curtain-wall-anchor-plate.png))
|
||||
[](curtain-wall-anchor-plate/main.kcl)
|
||||
#### [cycloidal-gear](cycloidal-gear/main.kcl) ([screenshot](screenshots/cycloidal-gear.png))
|
||||
[](cycloidal-gear/main.kcl)
|
||||
#### [dodecahedron](dodecahedron/main.kcl) ([screenshot](screenshots/dodecahedron.png))
|
||||
[](dodecahedron/main.kcl)
|
||||
#### [enclosure](enclosure/main.kcl) ([screenshot](screenshots/enclosure.png))
|
||||
[](enclosure/main.kcl)
|
||||
#### [engine-valve](engine-valve/main.kcl) ([screenshot](screenshots/engine-valve.png))
|
||||
[](engine-valve/main.kcl)
|
||||
#### [exhaust-manifold](exhaust-manifold/main.kcl) ([screenshot](screenshots/exhaust-manifold.png))
|
||||
[](exhaust-manifold/main.kcl)
|
||||
#### [flange](flange/main.kcl) ([screenshot](screenshots/flange.png))
|
||||
@ -109,8 +103,6 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](mounting-plate/main.kcl)
|
||||
#### [multi-axis-robot](multi-axis-robot/main.kcl) ([screenshot](screenshots/multi-axis-robot.png))
|
||||
[](multi-axis-robot/main.kcl)
|
||||
#### [pdu-faceplate](pdu-faceplate/main.kcl) ([screenshot](screenshots/pdu-faceplate.png))
|
||||
[](pdu-faceplate/main.kcl)
|
||||
#### [pillow-block-bearing](pillow-block-bearing/main.kcl) ([screenshot](screenshots/pillow-block-bearing.png))
|
||||
[](pillow-block-bearing/main.kcl)
|
||||
#### [pipe](pipe/main.kcl) ([screenshot](screenshots/pipe.png))
|
||||
@ -127,30 +119,18 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](router-template-cross-bar/main.kcl)
|
||||
#### [router-template-slate](router-template-slate/main.kcl) ([screenshot](screenshots/router-template-slate.png))
|
||||
[](router-template-slate/main.kcl)
|
||||
#### [sash-window](sash-window/main.kcl) ([screenshot](screenshots/sash-window.png))
|
||||
[](sash-window/main.kcl)
|
||||
#### [sheet-metal-bracket](sheet-metal-bracket/main.kcl) ([screenshot](screenshots/sheet-metal-bracket.png))
|
||||
[](sheet-metal-bracket/main.kcl)
|
||||
#### [shepherds-hook-bolt](shepherds-hook-bolt/main.kcl) ([screenshot](screenshots/shepherds-hook-bolt.png))
|
||||
[](shepherds-hook-bolt/main.kcl)
|
||||
#### [socket-head-cap-screw](socket-head-cap-screw/main.kcl) ([screenshot](screenshots/socket-head-cap-screw.png))
|
||||
[](socket-head-cap-screw/main.kcl)
|
||||
#### [spinning-highrise-tower](spinning-highrise-tower/main.kcl) ([screenshot](screenshots/spinning-highrise-tower.png))
|
||||
[](spinning-highrise-tower/main.kcl)
|
||||
#### [spur-gear](spur-gear/main.kcl) ([screenshot](screenshots/spur-gear.png))
|
||||
[](spur-gear/main.kcl)
|
||||
#### [spur-reduction-gearset](spur-reduction-gearset/main.kcl) ([screenshot](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/main.kcl)
|
||||
#### [telemetry-antenna](telemetry-antenna/main.kcl) ([screenshot](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/main.kcl)
|
||||
#### [tooling-nest-block](tooling-nest-block/main.kcl) ([screenshot](screenshots/tooling-nest-block.png))
|
||||
[](tooling-nest-block/main.kcl)
|
||||
#### [truss-structure](truss-structure/main.kcl) ([screenshot](screenshots/truss-structure.png))
|
||||
[](truss-structure/main.kcl)
|
||||
#### [utility-sink](utility-sink/main.kcl) ([screenshot](screenshots/utility-sink.png))
|
||||
[](utility-sink/main.kcl)
|
||||
#### [walkie-talkie](walkie-talkie/main.kcl) ([screenshot](screenshots/walkie-talkie.png))
|
||||
|
@ -1,180 +0,0 @@
|
||||
// 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.
|
||||
|
||||
|
||||
|
||||
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// Define parameters.
|
||||
dDisc = 320
|
||||
dPitchCircle = 114.3
|
||||
dBore = 64
|
||||
nStuds = 5
|
||||
dStudDrilling = 12.5 // M12
|
||||
hFrictionSurface = 60
|
||||
tDiscHalf = 10
|
||||
|
||||
// Vent parameters.
|
||||
tVent = 10
|
||||
wVent = 6
|
||||
rVentFillet = 2
|
||||
nVentBosses = 36
|
||||
|
||||
// Drilling parameters.
|
||||
dDrillDia = 6
|
||||
aBase = 90
|
||||
aSweep = 30
|
||||
nArcs = 12
|
||||
|
||||
// Bell parameters.
|
||||
aDraftBell = 5
|
||||
tBell = 5 // Wall thickness.
|
||||
hBellAboveDiscFace = 40
|
||||
hBellSubflush = 4
|
||||
wUndercut = 8
|
||||
|
||||
fn drillHole(activeSketch, t) {
|
||||
// Sketch a vent hole at line parameter value t on an arc drawn across the disc surface.
|
||||
rInner = dDisc / 2 - hFrictionSurface
|
||||
rOuter = dDisc / 2
|
||||
|
||||
aStart = aBase
|
||||
aEnd = aBase - aSweep
|
||||
|
||||
// Linear interpolation of radius.
|
||||
rCurrent = rInner + t * (rOuter - rInner)
|
||||
|
||||
// Linear interpolation of angle.
|
||||
aCurrent = aStart + t * (aEnd - aStart)
|
||||
|
||||
// Calculate position.
|
||||
xCenter = rCurrent * cos(aCurrent)
|
||||
yCenter = rCurrent * sin(aCurrent)
|
||||
|
||||
// Draw.
|
||||
drillCircle = circle(activeSketch, center = [xCenter, yCenter], radius = dDrillDia / 2)
|
||||
return drillCircle
|
||||
}
|
||||
|
||||
fn createDiscHalf(plane, dDiscParam, hFrictionSurfaceParam, tDiscHalfParam) {
|
||||
// Create a disc half with a vent hole pattern.
|
||||
sketchFace = startSketchOn(plane)
|
||||
profileFace = circle(sketchFace, center = [0, 0], radius = dDiscParam / 2)
|
||||
|> subtract2d(tool = circle(sketchFace, center = [0, 0], radius = dDiscParam / 2 - hFrictionSurfaceParam))
|
||||
|
||||
// Create three circles at t = 0, 0.5, and 1
|
||||
hole1 = drillHole(activeSketch = sketchFace, t = 0.2)
|
||||
hole2 = drillHole(activeSketch = sketchFace, t = 0.5)
|
||||
hole3 = drillHole(activeSketch = sketchFace, t = 0.8)
|
||||
|
||||
// Pattern and cut.
|
||||
holes = patternCircular2d(
|
||||
[hole1, hole2, hole3],
|
||||
instances = nArcs,
|
||||
center = [0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
profileDrilled = subtract2d(profileFace, tool = holes)
|
||||
|
||||
// Extrude.
|
||||
discHalf = extrude(profileFace, length = tDiscHalfParam)
|
||||
return discHalf
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
// Create inboard half.
|
||||
discInboard = createDiscHalf(
|
||||
plane = XY,
|
||||
dDiscParam = dDisc,
|
||||
hFrictionSurfaceParam = hFrictionSurface,
|
||||
tDiscHalfParam = tDiscHalf,
|
||||
)
|
||||
|
||||
// Create vents.
|
||||
planeVent = offsetPlane(XY, offset = tDiscHalf)
|
||||
sketchVent = startSketchOn(planeVent)
|
||||
profileVent = startProfile(sketchVent, at = [-wVent, dDisc / 2])
|
||||
|> angledLine(angle = 0, length = wVent, tag = $rectangleSegmentA001)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = hFrictionSurface, tag = $seg02)
|
||||
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg03)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|
||||
|> close()
|
||||
|
||||
ventPad = extrude(profileVent, length = tVent)
|
||||
|> fillet(
|
||||
radius = rVentFillet,
|
||||
tags = [
|
||||
getCommonEdge(faces = [seg01, rectangleSegmentA001]),
|
||||
getCommonEdge(faces = [seg02, rectangleSegmentA001]),
|
||||
getCommonEdge(faces = [seg01, seg03]),
|
||||
getCommonEdge(faces = [seg03, seg02])
|
||||
],
|
||||
)
|
||||
ventSet = patternCircular3d(
|
||||
ventPad,
|
||||
instances = nVentBosses,
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, tDiscHalf],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|
||||
// Create outboard half.
|
||||
planeOutboard = offsetPlane(XY, offset = tDiscHalf + tVent)
|
||||
discOutboard = createDiscHalf(
|
||||
plane = planeOutboard,
|
||||
dDiscParam = dDisc,
|
||||
hFrictionSurfaceParam = hFrictionSurface,
|
||||
tDiscHalfParam = tDiscHalf,
|
||||
)
|
||||
|
||||
// Now create bell.
|
||||
rCenter = dDisc / 2 - hFrictionSurface - wUndercut
|
||||
rBore = dBore / 2
|
||||
lDraftExterior = hBellAboveDiscFace / tan(90 - aDraftBell)
|
||||
lDraftInterior = (hBellAboveDiscFace - tBell) / tan(90 - aDraftBell)
|
||||
|
||||
// Inner and outer radius of outboard face of disc bell.
|
||||
rOuter = rCenter - lDraftExterior - rBore
|
||||
rInner = rOuter + lDraftExterior - (tBell + lDraftInterior)
|
||||
|
||||
sketchDiscBell = startSketchOn(-YZ)
|
||||
bodyDiscBell = startProfile(
|
||||
sketchDiscBell,
|
||||
at = [
|
||||
-dDisc / 2 + hFrictionSurface,
|
||||
tDiscHalf * 2 + tVent
|
||||
],
|
||||
)
|
||||
|> arc(
|
||||
%,
|
||||
angleStart = -180,
|
||||
angleEnd = 0,
|
||||
radius = wUndercut / 2,
|
||||
)
|
||||
|> line(end = [lDraftExterior, hBellAboveDiscFace])
|
||||
|> xLine(length = rOuter, tag = $seg04)
|
||||
|> yLine(length = -tBell)
|
||||
|> xLine(length = -rInner)
|
||||
|> line(end = [-lDraftInterior, -hBellAboveDiscFace])
|
||||
|> line(end = [0, -2]) // Wall thickness.
|
||||
|> xLine(length = -1 * (tBell + wUndercut))
|
||||
|> close(%)
|
||||
|> revolve(axis = Y)
|
||||
|
||||
// Drill lug holes.
|
||||
sketchLugs = startSketchOn(bodyDiscBell, face = seg04)
|
||||
profileStud = circle(sketchLugs, center = [0, dPitchCircle / 2], radius = dStudDrilling / 2)
|
||||
|> patternCircular2d(
|
||||
%,
|
||||
instances = nStuds,
|
||||
center = [0, 0],
|
||||
arcDegrees = 360,
|
||||
rotateDuplicates = true,
|
||||
)
|
||||
|
||||
clearance = 2 // Some margin on negative extrude.
|
||||
lugs = extrude(profileStud, length = -1 * (tBell + clearance))
|
@ -1,155 +0,0 @@
|
||||
// Curtain Wall Anchor Plate
|
||||
// A structural steel L-plate used to anchor curtain wall systems to concrete slabs, with elongated holes for adjustability and bolts with nuts and base plates for secure fastening
|
||||
|
||||
// Set units in millimeters (mm)
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Define parameters
|
||||
slabPlateBaseLength = 300
|
||||
slabPlateHookLength = 80
|
||||
slabPlateWidth = 200
|
||||
slabPlateThickness = 8
|
||||
offsetSlabRail = 200
|
||||
|
||||
// Generate L-shaped anchor profile with base and hook flange
|
||||
// Includes fillets at internal and external corners for strength and safety
|
||||
fn lProfileFn(lengthBase, lengthHook, width, thickness) {
|
||||
profilePlane = startSketchOn(offsetPlane(XZ, offset = -width / 2))
|
||||
profileShape = startProfile(profilePlane, at = [0, 0])
|
||||
|> yLine(length = lengthHook, tag = $hookOutside)
|
||||
|> xLine(length = thickness)
|
||||
|> yLine(length = thickness - lengthHook, tag = $hookInside)
|
||||
|> xLine(length = lengthBase - thickness, tag = $baseInside)
|
||||
|> yLine(length = -thickness)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $baseOutside)
|
||||
|> close()
|
||||
profileBody = extrude(profileShape, length = width)
|
||||
|> fillet(
|
||||
radius = thickness,
|
||||
tags = [
|
||||
getCommonEdge(faces = [baseInside, hookInside])
|
||||
],
|
||||
)
|
||||
|> fillet(
|
||||
radius = thickness * 2,
|
||||
tags = [
|
||||
getCommonEdge(faces = [baseOutside, hookOutside])
|
||||
],
|
||||
)
|
||||
return profileBody
|
||||
}
|
||||
|
||||
// Create a hexagonal shape used for bolt and nut heads
|
||||
fn hexagonFn(plane, radius) {
|
||||
shape = startProfile(plane, at = [-radius, 0])
|
||||
|> angledLine(angle = 60, length = radius)
|
||||
|> xLine(length = radius)
|
||||
|> angledLine(angle = -60, length = radius)
|
||||
|> angledLine(angle = -120, length = radius)
|
||||
|> xLine(length = -radius)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
return shape
|
||||
}
|
||||
|
||||
// Build a bolt with a hexagonal head and cylindrical shaft
|
||||
fn boltFn(diameter, length) {
|
||||
boltHeadPlane = startSketchOn(XY)
|
||||
boltHeadShape = hexagonFn(plane = boltHeadPlane, radius = diameter)
|
||||
boltHeadBody = extrude(boltHeadShape, length = diameter * 0.7)
|
||||
boltPlane = startSketchOn(boltHeadBody, face = START)
|
||||
boltShape = circle(boltPlane, center = [0, 0], radius = diameter / 2)
|
||||
boltBody = extrude(boltShape, length = length)
|
||||
return boltBody
|
||||
}
|
||||
|
||||
// Construct a bolt assembly with base plate and hex nut
|
||||
// Assembles all parts for realistic anchor simulation
|
||||
fn boltWithPlateAndNutFn(diameter, length, gap) {
|
||||
plateSide = diameter * 3
|
||||
plateplane = startSketchOn(offsetPlane(XY, offset = -gap))
|
||||
plateShape = startProfile(plateplane, at = [-plateSide / 2, -plateSide / 2])
|
||||
|> yLine(length = plateSide)
|
||||
|> xLine(length = plateSide)
|
||||
|> yLine(length = -plateSide)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
plateBody = extrude(plateShape, length = -diameter * 0.3)
|
||||
nutPlane = startSketchOn(plateBody, face = START)
|
||||
boltHeadShape = hexagonFn(plane = nutPlane, radius = 12)
|
||||
boltHeadBody = extrude(boltHeadShape, length = diameter * 0.7)
|
||||
boltBody = boltFn(diameter = diameter, length = gap + diameter + 3)
|
||||
mergedBody = union([boltHeadBody, boltBody])
|
||||
return mergedBody
|
||||
}
|
||||
|
||||
// Generate the plate geometry with a vertical hook for slab attachment
|
||||
slabPlate = lProfileFn(
|
||||
lengthBase = slabPlateBaseLength,
|
||||
lengthHook = slabPlateHookLength,
|
||||
width = slabPlateWidth,
|
||||
thickness = slabPlateThickness,
|
||||
)
|
||||
|
||||
// Define oblong holes for bolts, allowing positional adjustment
|
||||
wideHoleWidth = 12
|
||||
wideHoleLength = 60
|
||||
wideHoleOffset = 30
|
||||
|
||||
// Two slots mirrored across the plate width
|
||||
wideHolePlane = startSketchOn(XY)
|
||||
wideHoleShape = startProfile(
|
||||
wideHolePlane,
|
||||
at = [
|
||||
-(wideHoleLength - wideHoleWidth) / 2,
|
||||
wideHoleWidth / 2
|
||||
],
|
||||
)
|
||||
|> xLine(length = wideHoleLength - wideHoleWidth)
|
||||
|> tangentialArc(endAbsolute = [
|
||||
(wideHoleLength - wideHoleWidth) / 2,
|
||||
-wideHoleWidth / 2
|
||||
])
|
||||
|> xLine(length = wideHoleWidth - wideHoleLength)
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> translate(
|
||||
%,
|
||||
x = offsetSlabRail,
|
||||
y = wideHoleOffset - (slabPlateWidth / 2),
|
||||
z = -1,
|
||||
)
|
||||
wideHoleVoidLeft = extrude(wideHoleShape, length = slabPlateThickness + 2)
|
||||
wideHoleVoidRight = clone(wideHoleVoidLeft)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = slabPlateWidth - (wideHoleOffset * 2),
|
||||
z = 0,
|
||||
)
|
||||
|
||||
// Cut the holes into the anchor plate body
|
||||
slabPlatePunchOne = subtract([slabPlate], tools = [wideHoleVoidLeft])
|
||||
slabPlatePunchTwo = subtract([slabPlatePunchOne], tools = [wideHoleVoidRight])
|
||||
|
||||
// Add two bolt assemblies into the oblong slots
|
||||
// Properly rotated and spaced to match anchor hole layout
|
||||
slabPlateBolts = boltWithPlateAndNutFn(diameter = 10, length = 20, gap = slabPlateThickness + 5)
|
||||
|> rotate(
|
||||
%,
|
||||
roll = 180,
|
||||
pitch = 0,
|
||||
yaw = 0,
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = offsetSlabRail,
|
||||
y = wideHoleOffset - (slabPlateWidth / 2),
|
||||
z = 5,
|
||||
)
|
||||
|> patternLinear3d(
|
||||
%,
|
||||
instances = 2,
|
||||
distance = slabPlateWidth - (wideHoleOffset * 2),
|
||||
axis = [0, -1, 0],
|
||||
)
|
@ -1,79 +0,0 @@
|
||||
// Engine Valve
|
||||
// A mechanical valve used in internal combustion engines to control intake or exhaust flow
|
||||
|
||||
|
||||
|
||||
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Define parameters
|
||||
valveDiameter = 30
|
||||
valveLength = 120
|
||||
valveHeadLength = valveDiameter * 1.0
|
||||
valveHeadThickness = 3
|
||||
stemDiameter = 6
|
||||
stemHeadLength = 9
|
||||
stemLength = valveLength - valveHeadLength - stemHeadLength
|
||||
|
||||
// Create the valve head
|
||||
valveRadius = valveDiameter / 2
|
||||
valveHeadPlane = startSketchOn(XZ)
|
||||
valveHeadShape = startProfile(valveHeadPlane, at = [-0.01, valveHeadLength])
|
||||
|> xLine(length = 0.01 - (stemDiameter / 2))
|
||||
|> line(endAbsolute = [0.01 - (stemDiameter / 2), valveRadius])
|
||||
|> tangentialArc(endAbsolute = [-0.8 * valveRadius, valveHeadThickness], tag = $seg01)
|
||||
|> tangentialArc(endAbsolute = [-valveRadius, 0])
|
||||
|> xLine(length = 0.3 * valveRadius)
|
||||
|> arc(
|
||||
interiorAbsolute = [
|
||||
-0.34 * valveRadius,
|
||||
0.08 * valveRadius
|
||||
],
|
||||
endAbsolute = [
|
||||
-0.02 * valveRadius,
|
||||
0.11 * valveRadius
|
||||
],
|
||||
)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
valveHead = revolve(valveHeadShape, angle = 360, axis = Y)
|
||||
|
||||
// Create the valve stem
|
||||
valveStemSketch = startSketchOn(offsetPlane(XY, offset = valveHeadLength))
|
||||
|> circle(center = [0, 0], radius = stemDiameter / 2)
|
||||
|> extrude(length = stemLength - valveHeadLength - stemHeadLength)
|
||||
|
||||
// Create the valve stem end
|
||||
stepLength = stemHeadLength / 10
|
||||
step1 = startSketchOn(valveStemSketch, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|
||||
|> extrude(%, length = stepLength * 2)
|
||||
step2 = startSketchOn(step1, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|
||||
|> extrude(%, length = stepLength)
|
||||
step3 = startSketchOn(step2, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|
||||
|> extrude(%, length = stepLength)
|
||||
step4 = startSketchOn(step3, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|
||||
|> extrude(%, length = stepLength)
|
||||
step5 = startSketchOn(step4, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|
||||
|> extrude(%, length = stepLength)
|
||||
step6 = startSketchOn(step5, face = END)
|
||||
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|
||||
|> extrude(%, length = stepLength)
|
||||
step7 = startSketchOn(step6, face = END)
|
||||
|> circle(
|
||||
%,
|
||||
center = [0, 0],
|
||||
radius = stemDiameter / 2 * 0.9,
|
||||
tag = $seg02,
|
||||
)
|
||||
|> extrude(%, length = stepLength * 3, tagEnd = $capEnd001)
|
||||
|> chamfer(
|
||||
length = 0.5,
|
||||
tags = [
|
||||
getCommonEdge(faces = [seg02, capEnd001])
|
||||
],
|
||||
)
|
@ -74,16 +74,6 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": "car-wheel-assembly/main.kcl",
|
||||
@ -157,16 +147,6 @@
|
||||
"removable-sticker.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "curtain-wall-anchor-plate/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Curtain Wall Anchor Plate",
|
||||
"description": "A structural steel L-plate used to anchor curtain wall systems to concrete slabs, with elongated holes for adjustability and bolts with nuts and base plates for secure fastening",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "cycloidal-gear/main.kcl",
|
||||
@ -197,16 +177,6 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "engine-valve/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Engine Valve",
|
||||
"description": "A mechanical valve used in internal combustion engines to control intake or exhaust flow",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "exhaust-manifold/main.kcl",
|
||||
@ -452,16 +422,6 @@
|
||||
"robot-rotating-base.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "pdu-faceplate/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Power Distribution Unit (PDU) faceplate with European plug sockets and switch",
|
||||
"description": "Designed for standard 19-inch rack systems with 1U height and 8 sockets",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "pillow-block-bearing/main.kcl",
|
||||
@ -552,16 +512,6 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "sash-window/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Sash Window",
|
||||
"description": "A traditional wooden sash window with two vertically sliding panels and a central locking mechanism",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "sheet-metal-bracket/main.kcl",
|
||||
@ -572,16 +522,6 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "shepherds-hook-bolt/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Shepherd’s Hook Bolt",
|
||||
"description": "A bent bolt with a curved hook, typically used for hanging or anchoring loads. The threaded end allows secure attachment to surfaces or materials, while the curved hook resists pull-out under tension.",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "socket-head-cap-screw/main.kcl",
|
||||
@ -592,16 +532,6 @@
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "spinning-highrise-tower/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Spinning Highrise Tower",
|
||||
"description": "A conceptual high-rise tower with a central core and rotating floor slabs, demonstrating dynamic form through vertical repetition and transformation",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "spur-gear/main.kcl",
|
||||
@ -632,26 +562,6 @@
|
||||
"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",
|
||||
"multipleFiles": false,
|
||||
"title": "Thermal Block Insert",
|
||||
"description": "Interlocking insulation insert for masonry walls, designed with a tongue-and-groove profile for modular alignment and thermal efficiency",
|
||||
"files": [
|
||||
"main.kcl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "tooling-nest-block/main.kcl",
|
||||
@ -662,16 +572,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",
|
||||
|
@ -1,240 +0,0 @@
|
||||
// Power Distribution Unit (PDU) faceplate with European plug sockets and switch
|
||||
// Designed for standard 19-inch rack systems with 1U height and 8 sockets
|
||||
|
||||
// Set units in millimeters (mm)
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Define the dimensions
|
||||
// Width fits standard 19” rack, height is 1U, depth is variable
|
||||
faceplateWidth = 482.6 // this is standardized to fit 19-inch racks)
|
||||
faceplateHeight = 44.45 // usually 1U (44.45 mm), but can be 2U (88.9 mm) or more
|
||||
faceplateDepth = 100 // varies by manufacturer, but commonly between 100 mm and 300 mm
|
||||
|
||||
|
||||
// Define dimensions of side supports (width and thickness)
|
||||
supportWidth = 50
|
||||
supportThickness = 3
|
||||
|
||||
// Main body of the PDU faceplate with integrated rack mounting flanges
|
||||
faceplateShape = startSketchOn(offsetPlane(XY, offset = -faceplateHeight / 2))
|
||||
|> startProfile(%, at = [-faceplateWidth / 2 - supportWidth, 0])
|
||||
|> yLine(length = supportThickness)
|
||||
|> xLine(length = supportWidth)
|
||||
|> yLine(length = faceplateDepth - supportThickness)
|
||||
|> xLine(length = faceplateWidth)
|
||||
|> yLine(length = supportThickness - faceplateDepth)
|
||||
|> xLine(length = supportWidth)
|
||||
|> yLine(length = -supportThickness)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|
||||
|> close()
|
||||
faceplateBody = extrude(faceplateShape, length = faceplateHeight)
|
||||
faceplateFrontFace = startSketchOn(faceplateBody, face = seg01)
|
||||
|
||||
// Creates recessed volume within the faceplate for inserting modules
|
||||
nestWall = 2
|
||||
nestWidth = faceplateWidth - (nestWall * 2)
|
||||
nestHeight = faceplateHeight - (nestWall * 2)
|
||||
nestDepth = faceplateDepth - nestWall
|
||||
nestShape = startProfile(faceplateFrontFace, at = [-nestWidth / 2, nestHeight / 2])
|
||||
|> xLine(length = nestWidth)
|
||||
|> yLine(length = -nestHeight)
|
||||
|> xLine(length = -nestWidth)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
nestVoid = extrude(nestShape, length = -nestDepth)
|
||||
|
||||
// Spacer block on the left side, used to position components correctly
|
||||
moduleHeight = nestHeight
|
||||
moduleWidth = nestHeight
|
||||
moduleDepth = nestHeight
|
||||
|
||||
leftSpacerWidth = moduleWidth * 1.5
|
||||
leftSpacerPosition = leftSpacerWidth / 2 - (nestWidth / 2)
|
||||
|
||||
fn boxModuleFn(width) {
|
||||
shape = startSketchOn(XZ)
|
||||
|> startProfile(%, at = [-width / 2, moduleHeight / 2])
|
||||
|> xLine(length = width)
|
||||
|> yLine(length = -moduleHeight)
|
||||
|> xLine(length = -width)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
body = extrude(shape, length = -moduleDepth)
|
||||
return body
|
||||
}
|
||||
leftSpacerShape = boxModuleFn(width = leftSpacerWidth)
|
||||
|> translate(
|
||||
%,
|
||||
x = leftSpacerPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|
||||
// Module for power switch including front plate and red rocker button
|
||||
switchPosition = leftSpacerPosition + leftSpacerWidth / 2 + moduleWidth / 2
|
||||
switchWidth = moduleWidth
|
||||
|
||||
// Switch Body
|
||||
switchBody = boxModuleFn(width = moduleWidth)
|
||||
|
||||
// Switch Plate
|
||||
switchPlateWidth = 20
|
||||
switchPlateHeight = 30
|
||||
switchPlateThickness = 3
|
||||
switchPlateShape = startSketchOn(switchBody, face = END)
|
||||
|> startProfile(
|
||||
%,
|
||||
at = [
|
||||
-switchPlateWidth / 2,
|
||||
-switchPlateHeight / 2
|
||||
],
|
||||
)
|
||||
|> yLine(length = switchPlateHeight)
|
||||
|> xLine(length = switchPlateWidth)
|
||||
|> yLine(length = -switchPlateHeight)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
switchPlateBody = extrude(switchPlateShape, length = switchPlateThickness)
|
||||
|> translate(
|
||||
%,
|
||||
x = switchPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|
||||
// Switch Button
|
||||
switchButtonHeight = 26
|
||||
switchButtonWidth = 15
|
||||
switchButtonShape = startSketchOn(offsetPlane(-YZ, offset = -switchButtonWidth / 2))
|
||||
|> startProfile(
|
||||
%,
|
||||
at = [
|
||||
switchPlateThickness,
|
||||
switchButtonHeight / 2
|
||||
],
|
||||
)
|
||||
|> line(end = [3, -1])
|
||||
|> arc(interiorAbsolute = [6, 0], endAbsolute = [12, -9])
|
||||
|> line(endAbsolute = [
|
||||
switchPlateThickness,
|
||||
-switchButtonHeight / 2
|
||||
])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
switchButtonBody = extrude(switchButtonShape, length = switchButtonWidth)
|
||||
|> translate(
|
||||
%,
|
||||
x = switchPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|> appearance(%, color = "#ff0000")
|
||||
|
||||
// Spacer between switch and plug modules for layout alignment
|
||||
secondSpacerWidth = moduleWidth / 2
|
||||
secondSpacerPosition = switchPosition + switchWidth / 2 + secondSpacerWidth / 2
|
||||
secondSpacerBody = boxModuleFn(width = secondSpacerWidth)
|
||||
|> translate(
|
||||
%,
|
||||
x = secondSpacerPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|
||||
// European power plug modules with circular sockets and two-pin holes
|
||||
// 8 identical sockets, each with grounding notch and dual-pin recesses
|
||||
powerPlugWidth = moduleWidth
|
||||
powerPlugCount = 8
|
||||
powerPlugOveralWidth = powerPlugWidth * powerPlugCount
|
||||
firstPowerPlugPosition = secondSpacerPosition + secondSpacerWidth / 2 + powerPlugWidth / 2
|
||||
lastPowerPlugPosition = firstPowerPlugPosition + powerPlugWidth * (powerPlugCount - 1)
|
||||
powerPlugBody = boxModuleFn(width = powerPlugWidth)
|
||||
|> translate(
|
||||
%,
|
||||
x = firstPowerPlugPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
plugShape = startSketchOn(powerPlugBody, face = END)
|
||||
|> circle(%, center = [0, 0], radius = 17)
|
||||
|> translate(
|
||||
%,
|
||||
x = firstPowerPlugPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
plugBody = extrude(plugShape, length = -20)
|
||||
plugHoleDistance = 20
|
||||
plugHoleShape = startSketchOn(plugBody, face = START)
|
||||
|> circle(%, center = [-plugHoleDistance / 2, 0], radius = 3)
|
||||
|> translate(
|
||||
%,
|
||||
x = firstPowerPlugPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = 2,
|
||||
distance = plugHoleDistance,
|
||||
axis = [1, 0],
|
||||
)
|
||||
plugHoleBody = extrude(plugHoleShape, length = -5)
|
||||
|> patternLinear3d(
|
||||
%,
|
||||
instances = powerPlugCount,
|
||||
distance = powerPlugWidth,
|
||||
axis = [1, 0, 0],
|
||||
)
|
||||
|
||||
// Rightmost spacer to fill in remaining horizontal space
|
||||
rightSpacerWidth = nestWidth / 2 - lastPowerPlugPosition - (powerPlugWidth / 2)
|
||||
rightSpacerPosition = lastPowerPlugPosition + powerPlugWidth / 2 + rightSpacerWidth / 2
|
||||
rightSpacerBody = boxModuleFn(width = rightSpacerWidth)
|
||||
|> translate(
|
||||
%,
|
||||
x = rightSpacerPosition,
|
||||
y = 0,
|
||||
z = 0,
|
||||
)
|
||||
|
||||
// Rack mounting holes on flanges, elongated for alignment flexibility
|
||||
holeWidth = 25
|
||||
holeDiameter = 5
|
||||
holeStraightSegment = holeWidth - holeDiameter
|
||||
holeVerticalDistance = faceplateHeight * 0.3
|
||||
|
||||
holeShapes = startProfile(
|
||||
faceplateFrontFace,
|
||||
at = [
|
||||
-holeStraightSegment / 2,
|
||||
holeDiameter / 2
|
||||
],
|
||||
)
|
||||
|> xLine(length = holeStraightSegment)
|
||||
|> tangentialArc(endAbsolute = [
|
||||
holeStraightSegment / 2,
|
||||
-holeDiameter / 2
|
||||
])
|
||||
|> xLine(length = -holeStraightSegment)
|
||||
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> translate(
|
||||
%,
|
||||
x = -faceplateWidth / 2 - (supportWidth / 2),
|
||||
y = 0,
|
||||
z = -holeVerticalDistance,
|
||||
)
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = 3,
|
||||
distance = holeVerticalDistance,
|
||||
axis = [0, 1],
|
||||
)
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = 2,
|
||||
distance = faceplateWidth + supportWidth,
|
||||
axis = [1, 0],
|
||||
)
|
||||
holeVoid = extrude(holeShapes, length = -supportThickness)
|
@ -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])
|
||||
|
@ -1,214 +0,0 @@
|
||||
// Sash Window
|
||||
// A traditional wooden sash window with two vertically sliding panels and a central locking mechanism
|
||||
|
||||
// Set units in millimeters (mm)
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Window state: 0 for closed, 1 for open
|
||||
windowState = 0
|
||||
|
||||
// Basic window dimensions
|
||||
windowWidth = 500
|
||||
windowHeight = 1000
|
||||
|
||||
// Frame thickness and depth
|
||||
frameWidth = 30
|
||||
frameDepth = 50
|
||||
|
||||
// Number of divisions per sash (horizontal and vertical)
|
||||
sashOpeningCountHorizontal = 2
|
||||
sashOpeningCountVertical = 1
|
||||
|
||||
// Derived dimensions
|
||||
sashWidth = windowWidth - (frameWidth * 2)
|
||||
sashHeight = (windowHeight - (frameWidth * 2)) / 2 + frameWidth / 2
|
||||
sashDepth = frameDepth / 2 - 2
|
||||
sashTravelDistance = sashHeight * windowState * 0.8
|
||||
|
||||
// Function to create panel with frame and openings
|
||||
fn panelFn(plane, offset, width, height, depth, perimeter, divisionThickness, openingCountHorizontal, openingCountVertical) {
|
||||
// Create panel base shape
|
||||
panelPlane = startSketchOn(offsetPlane(XZ, offset = offset))
|
||||
panelShape = startProfile(panelPlane, at = [-width / 2, -height / 2])
|
||||
|> yLine(length = height)
|
||||
|> xLine(length = width)
|
||||
|> yLine(length = -height)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
panelBody = extrude(panelShape, length = depth)
|
||||
|
||||
// Create opening grid within the panel
|
||||
voidAreaWidth = width - (perimeter * 2)
|
||||
voidAreaHeight = height - (perimeter * 2)
|
||||
|
||||
divisionTotalThicknessHorizontal = divisionThickness * openingCountHorizontal - divisionThickness
|
||||
divisionTotalThicknessVertical = divisionThickness * openingCountVertical - divisionThickness
|
||||
voidWidth = (voidAreaWidth - divisionTotalThicknessHorizontal) / openingCountHorizontal
|
||||
voidHeight = (voidAreaHeight - divisionTotalThicknessVertical) / openingCountVertical
|
||||
|
||||
voidStepHorizontal = voidWidth + divisionThickness
|
||||
voidStepVertical = voidHeight + divisionThickness
|
||||
voidPlane = startSketchOn(panelBody, face = END)
|
||||
voidShape = startProfile(
|
||||
voidPlane,
|
||||
at = [
|
||||
-voidAreaWidth / 2,
|
||||
-voidAreaHeight / 2
|
||||
],
|
||||
)
|
||||
|> yLine(length = voidHeight)
|
||||
|> xLine(length = voidWidth)
|
||||
|> yLine(length = -voidHeight)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = openingCountHorizontal,
|
||||
distance = voidStepHorizontal,
|
||||
axis = [1, 0],
|
||||
)
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = openingCountVertical,
|
||||
distance = voidStepVertical,
|
||||
axis = [0, 1],
|
||||
)
|
||||
voidBody = extrude(voidShape, length = -depth)
|
||||
|> appearance(color = "#a55e2c")
|
||||
return panelBody
|
||||
}
|
||||
|
||||
// Create main window frame
|
||||
frame = panelFn(
|
||||
plane = XZ,
|
||||
offset = -frameDepth / 2,
|
||||
width = windowWidth,
|
||||
height = windowHeight,
|
||||
depth = frameDepth,
|
||||
perimeter = frameWidth,
|
||||
divisionThickness = 10,
|
||||
openingCountHorizontal = 1,
|
||||
openingCountVertical = 1,
|
||||
)
|
||||
|
||||
// Create bottom sliding sash
|
||||
bottomSash = panelFn(
|
||||
plane = XZ,
|
||||
offset = (frameDepth / 2 - sashDepth) / 2,
|
||||
width = sashWidth,
|
||||
height = sashHeight,
|
||||
depth = sashDepth,
|
||||
perimeter = frameWidth,
|
||||
divisionThickness = 10,
|
||||
openingCountHorizontal = sashOpeningCountHorizontal,
|
||||
openingCountVertical = sashOpeningCountVertical,
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = 0,
|
||||
z = frameWidth / 2 - (sashHeight / 2),
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = 0,
|
||||
z = sashTravelDistance,
|
||||
) // open / close
|
||||
|
||||
// Latch mechanism on bottom sash
|
||||
// Create latch plate
|
||||
latchPlateWidth = 13
|
||||
latchPlateLength = 30
|
||||
latchPlateThickness = 1
|
||||
|
||||
latchPlatePlane = startSketchOn(offsetPlane(XY, offset = frameWidth / 2))
|
||||
latchPlateShape = startProfile(
|
||||
latchPlatePlane,
|
||||
at = [
|
||||
-latchPlateLength / 2,
|
||||
-latchPlateWidth / 2
|
||||
],
|
||||
)
|
||||
|> yLine(length = latchPlateWidth)
|
||||
|> xLine(length = latchPlateLength)
|
||||
|> yLine(length = -latchPlateWidth)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
latchPlateBody = extrude(latchPlateShape, length = latchPlateThickness)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = -frameDepth / 4,
|
||||
z = 0,
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = 0,
|
||||
z = sashTravelDistance,
|
||||
) // open / close
|
||||
|
||||
// Create latch cylinder
|
||||
latchCylinderHeight = 5
|
||||
latchCylinderPlane = startSketchOn(offsetPlane(latchPlatePlane, offset = latchPlateThickness))
|
||||
latchCylinderShape = startProfile(latchCylinderPlane, at = [40, -1])
|
||||
|> xLine(length = -35)
|
||||
|> arc(interiorAbsolute = [-5, 0], endAbsolute = [5, 1])
|
||||
|> xLine(length = 35)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
latchCylinderBody = extrude(latchCylinderShape, length = latchCylinderHeight)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = -frameDepth / 4,
|
||||
z = 0,
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = 0,
|
||||
z = sashTravelDistance,
|
||||
) // open / close
|
||||
|> rotate(
|
||||
%,
|
||||
roll = 0,
|
||||
pitch = 0,
|
||||
yaw = -90 * windowState,
|
||||
)
|
||||
|
||||
// Create top fixed sash
|
||||
topSash = panelFn(
|
||||
plane = XZ,
|
||||
offset = -(frameDepth / 2 - sashDepth) / 2 - sashDepth,
|
||||
width = sashWidth,
|
||||
height = sashHeight,
|
||||
depth = sashDepth,
|
||||
perimeter = frameWidth,
|
||||
divisionThickness = 10,
|
||||
openingCountHorizontal = sashOpeningCountHorizontal,
|
||||
openingCountVertical = sashOpeningCountVertical,
|
||||
)
|
||||
|> translate(
|
||||
%,
|
||||
x = 0,
|
||||
y = 0,
|
||||
z = sashHeight / 2 - (frameWidth / 2),
|
||||
)
|
||||
|
||||
// Create latch nut on the top sash
|
||||
latchNutPlane = startSketchOn(XZ)
|
||||
latchNutShape = startProfile(
|
||||
latchNutPlane,
|
||||
at = [
|
||||
-latchPlateLength / 2,
|
||||
-latchPlateWidth / 2
|
||||
],
|
||||
)
|
||||
|> yLine(length = latchPlateWidth)
|
||||
|> xLine(length = latchPlateLength)
|
||||
|> yLine(length = -latchPlateWidth)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
latchNutPlateBody = extrude(latchNutShape, length = latchPlateThickness)
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
Before Width: | Height: | Size: 81 KiB |
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 216 KiB |
Before Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 100 KiB |
Before Width: | Height: | Size: 178 KiB |
Before Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 78 KiB |
Before Width: | Height: | Size: 68 KiB |
@ -1,89 +0,0 @@
|
||||
// Shepherd’s Hook Bolt
|
||||
// A bent bolt with a curved hook, typically used for hanging or anchoring loads. The threaded end allows secure attachment to surfaces or materials, while the curved hook resists pull-out under tension.
|
||||
|
||||
// Set units in millimeters (mm)
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Define bolt geometry parameters
|
||||
boltDiameter = 5
|
||||
hookRadius = 12
|
||||
shankLength = 5
|
||||
threadedEndLength = 30
|
||||
nutDistance = 20
|
||||
hookStartAngle = 290
|
||||
hookEndAngle = 150
|
||||
|
||||
approximatePitch = boltDiameter * 0.15
|
||||
threadDepth = 0.6134 * approximatePitch
|
||||
innerRadius = boltDiameter / 2 - threadDepth
|
||||
boltNumberOfRevolutions = threadedEndLength / approximatePitch
|
||||
|
||||
// Helper values for computing geometry transitions between straight shaft and hook arc
|
||||
hypotenuse = hookRadius / cos(hookStartAngle - 270)
|
||||
side = sqrt(pow(hypotenuse, exp = 2) - pow(hookRadius, exp = 2))
|
||||
shankOffset = hypotenuse + side
|
||||
|
||||
// Converts polar coordinates to cartesian points for drawing arcs
|
||||
fn polarToCartesian(radius, angle) {
|
||||
x = radius * cos(angle)
|
||||
y = radius * sin(angle)
|
||||
return [x, y]
|
||||
}
|
||||
|
||||
// Create the hook and shank profile path
|
||||
// Includes straight segment and two connected arcs forming the hook
|
||||
hookProfilePlane = startSketchOn(XZ)
|
||||
hookProfileShape = startProfile(hookProfilePlane, at = [0, -shankOffset - shankLength])
|
||||
|> line(endAbsolute = [0, -shankOffset])
|
||||
|> tangentialArc(endAbsolute = polarToCartesian(radius = hookRadius, angle = hookStartAngle))
|
||||
|> tangentialArc(endAbsolute = polarToCartesian(radius = hookRadius, angle = hookEndAngle), tag = $hook)
|
||||
|
||||
// Create the circular cross-section used for sweeping along the hook path
|
||||
hookSectionPlane = offsetPlane(XY, offset = -shankOffset - shankLength)
|
||||
hookSectionShape = circle(hookSectionPlane, center = [0, 0], radius = boltDiameter / 2)
|
||||
|
||||
// Sweep the section along the hook profile to form the main body of the hook bolt
|
||||
hookBody = sweep(hookSectionShape, path = hookProfileShape, sectional = true)
|
||||
|
||||
// Add a cylindrical tip at the hook end
|
||||
tipPlane = startSketchOn(hookBody, face = END)
|
||||
tipShape = circle(
|
||||
tipPlane,
|
||||
center = [hookRadius, 0],
|
||||
radius = boltDiameter / 2,
|
||||
tag = $seg01,
|
||||
)
|
||||
tipBody = extrude(
|
||||
tipShape,
|
||||
length = hookRadius * 0.5,
|
||||
tagStart = $startTag,
|
||||
tagEnd = $capEnd001,
|
||||
)
|
||||
|> fillet(
|
||||
radius = boltDiameter / 4,
|
||||
tags = [
|
||||
getCommonEdge(faces = [seg01, capEnd001])
|
||||
],
|
||||
)
|
||||
|
||||
// Create the threaded end of the bolt
|
||||
|
||||
// Construct the triangular profile for thread cutting
|
||||
boltThreadSectionPlane = startSketchOn(XZ)
|
||||
boltThreadSectionShapeForRevolve = startProfile(
|
||||
boltThreadSectionPlane,
|
||||
at = [
|
||||
innerRadius,
|
||||
-shankOffset - shankLength - threadedEndLength
|
||||
],
|
||||
)
|
||||
|> line(end = [threadDepth, approximatePitch / 2])
|
||||
|> line(end = [-threadDepth, approximatePitch / 2])
|
||||
|> patternLinear2d(axis = [0, 1], instances = boltNumberOfRevolutions, distance = approximatePitch)
|
||||
|> xLine(length = -innerRadius * 0.9)
|
||||
|> yLine(length = -threadedEndLength)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
// Create a revolved solid representing the thread geometry by repeating and revolving the profile around the shaft
|
||||
boltThreadRevolve = revolve(boltThreadSectionShapeForRevolve, angle = 360, axis = Y)
|
@ -1,93 +0,0 @@
|
||||
// Spinning Highrise Tower
|
||||
// A conceptual high-rise tower with a central core and rotating floor slabs, demonstrating dynamic form through vertical repetition and transformation
|
||||
|
||||
|
||||
|
||||
|
||||
@settings(defaultLengthUnit = m, kclVersion = 1.0)
|
||||
|
||||
// Define global parameters for floor geometry and building layout
|
||||
floorCount = 17
|
||||
floorHeight = 5
|
||||
slabWidth = 30
|
||||
slabThickness = 0.5
|
||||
rotationAngleStep = 5
|
||||
handrailHeight = 1.2
|
||||
handrailThickness = 0.3
|
||||
balconyDepth = 3
|
||||
|
||||
// Calculate facade and core geometry from parameters
|
||||
facadeWidth = slabWidth - (balconyDepth * 2)
|
||||
facadeHeight = floorHeight - slabThickness
|
||||
coreHeight = floorCount * floorHeight - slabThickness
|
||||
frameSide = 0.1
|
||||
windowTargetWidth = 6
|
||||
windowTargetCount = facadeWidth / windowTargetWidth
|
||||
windowCount = round(windowTargetCount)
|
||||
windowWidth = facadeWidth / windowCount
|
||||
|
||||
// Helper function: Creates a box from a center plane with given width and height
|
||||
fn boxFn(plane, width, height) {
|
||||
shape = startSketchOn(plane)
|
||||
|> startProfile(%, at = [-width / 2, -width / 2])
|
||||
|> line(%, end = [0, width])
|
||||
|> line(%, end = [width, 0])
|
||||
|> line(%, end = [0, -width])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close(%)
|
||||
body = extrude(shape, length = height)
|
||||
return body
|
||||
}
|
||||
|
||||
// Helper function: Defines transformation (translation and rotation) for each floor
|
||||
fn transformFn(@i) {
|
||||
return {
|
||||
translate = [0, 0, i * floorHeight],
|
||||
rotation = { angle = rotationAngleStep * i }
|
||||
}
|
||||
}
|
||||
|
||||
// Create building base
|
||||
baseThickness = 0.2
|
||||
baseSlab = boxFn(plane = XY, width = slabWidth, height = -baseThickness)
|
||||
|> appearance(%, color = "#dbd7d2")
|
||||
|
||||
// Create ground platform beneath the base
|
||||
groundSize = 50
|
||||
groundBody = boxFn(plane = offsetPlane(XY, offset = -baseThickness), width = groundSize, height = -5)
|
||||
|> appearance(%, color = "#3a3631")
|
||||
|
||||
// Create a single slab with handrail height to be reused with pattern
|
||||
slabAndHandrailGeometry = boxFn(plane = offsetPlane(XY, offset = floorHeight - slabThickness), width = slabWidth, height = slabThickness + handrailHeight)
|
||||
slabVoidStart = -slabWidth / 2 + handrailThickness
|
||||
slabVoidWidth = slabWidth - (handrailThickness * 2)
|
||||
slabVoidShape = startSketchOn(slabAndHandrailGeometry, face = END)
|
||||
|> startProfile(%, at = [slabVoidStart, slabVoidStart])
|
||||
|> line(%, end = [0, slabVoidWidth])
|
||||
|> line(%, end = [slabVoidWidth, 0])
|
||||
|> line(%, end = [0, -slabVoidWidth])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close(%)
|
||||
|
||||
// Generate and pattern slabs with voids across all floors
|
||||
slabBody = extrude(slabVoidShape, length = -handrailHeight)
|
||||
|> patternTransform(instances = floorCount, transform = transformFn)
|
||||
|> appearance(%, color = "#dbd7d2")
|
||||
|
||||
// Create structural core of the tower
|
||||
coreLength = 10
|
||||
coreWidth = 8
|
||||
core = startSketchOn(XY)
|
||||
|> startProfile(%, at = [-coreLength / 2, -coreWidth / 2])
|
||||
|> line(%, end = [0, coreWidth])
|
||||
|> line(%, end = [coreLength, 0])
|
||||
|> line(%, end = [-0.22, -coreWidth])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close(%)
|
||||
|> extrude(%, length = coreHeight)
|
||||
|
||||
// Create facade panels for each floor
|
||||
facadeStart = facadeWidth / 2
|
||||
facadeGeometry = boxFn(plane = XY, width = facadeWidth, height = facadeHeight)
|
||||
|> patternTransform(instances = floorCount, transform = transformFn)
|
||||
|> appearance(%, color = "#151819")
|
@ -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 °
|
@ -1,61 +0,0 @@
|
||||
// Thermal Block Insert
|
||||
// Interlocking insulation insert for masonry walls, designed with a tongue-and-groove profile for modular alignment and thermal efficiency
|
||||
|
||||
// Set units in millimeters (mm)
|
||||
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||
|
||||
// Define overall dimensions of the insert block
|
||||
insertLength = 400
|
||||
insertHeight = 200
|
||||
insertThickness = 50
|
||||
|
||||
// Define tongue-and-groove profile parameters for interlocking geometry
|
||||
setbackFactor = 0.25 // spacing between tongues
|
||||
tongueTargetCount = insertLength / 80
|
||||
tongueCount = round(tongueTargetCount)
|
||||
tongueLength = insertLength / (tongueCount * (1 + setbackFactor * 2) + 1)
|
||||
tongueGap = tongueLength * setbackFactor * 2
|
||||
tongueStep = tongueLength + tongueGap
|
||||
tongueDepth = tongueLength * 0.5
|
||||
tongueSetback = tongueLength * setbackFactor
|
||||
|
||||
// Function to create one side of the repeating tongue geometry along the block edge
|
||||
fn tongueBlockFn() {
|
||||
tongueSingleBlock = xLine(length = tongueLength)
|
||||
|> line(end = [-tongueSetback, tongueDepth])
|
||||
|> xLine(length = tongueLength)
|
||||
|> line(end = [-tongueSetback, -tongueDepth])
|
||||
|> patternLinear2d(
|
||||
%,
|
||||
instances = tongueCount,
|
||||
distance = tongueStep,
|
||||
axis = [1, 0],
|
||||
)
|
||||
|> xLine(length = tongueLength)
|
||||
return tongueSingleBlock
|
||||
}
|
||||
|
||||
// Create top-side profile with tongues
|
||||
tongueShape = startSketchOn(XY)
|
||||
|> startProfile(%, at = [-insertLength / 2, insertThickness / 2])
|
||||
|> tongueBlockFn()
|
||||
|> yLine(length = -insertThickness / 2)
|
||||
|> xLine(length = -insertLength)
|
||||
|> close(%)
|
||||
|
||||
// Create bottom-side profile with grooves (inverse of tongue)
|
||||
grooveShape = startSketchOn(XY)
|
||||
|> startProfile(
|
||||
%,
|
||||
at = [
|
||||
-insertLength / 2,
|
||||
-insertThickness / 2 - tongueDepth
|
||||
],
|
||||
)
|
||||
|> tongueBlockFn()
|
||||
|> yLine(length = insertThickness / 2 + tongueDepth)
|
||||
|> xLine(length = -insertLength)
|
||||
|> close(%)
|
||||
|
||||
// Extrude both tongue and groove profiles to form the final thermal insert block
|
||||
insertShape = extrude([tongueShape, grooveShape], length = insertHeight)
|
@ -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],
|
||||
)
|
46
public/kcma-logomark-dark.svg
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
public/kcma-logomark-outlined.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/kcma-logomark.png
Normal file
After Width: | Height: | Size: 8.1 KiB |
46
public/kcma-logomark.svg
Normal file
After Width: | Height: | Size: 16 KiB |
13
public/zma-logomark-dark.svg
Normal file
After Width: | Height: | Size: 13 KiB |