Compare commits

..

54 Commits

Author SHA1 Message Date
2204575466 Fix to not blow away files with comments 2025-03-21 16:54:19 -04:00
65f9bcc4ea Fix so that only comments doesn't format to empty 2025-03-21 16:54:19 -04:00
ced2072768 A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 20:40:04 +00:00
5e1fbccaec A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 20:25:24 +00:00
658700b533 A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 20:01:12 +00:00
670d95e692 Merge branch 'main' into jtran/units-indicator 2025-03-21 15:46:42 -04:00
744bb254e9 Fix yarn lint 2025-03-21 12:59:13 -04:00
b9a61c83d6 Possibly the last test that needs updated with an inline unit annotation 2025-03-21 12:41:11 -04:00
b5c25fe9e7 More test updates to use inline in setting 2025-03-21 12:32:45 -04:00
a3b6da03d3 Undo kclSamplesInputPath approach.
Instead, just add a second reliable executor input file to use.
2025-03-21 12:13:19 -04:00
e6c060c410 A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 16:00:35 +00:00
09dabd8fc2 I'm pretty sure our lighting is very affected by scale 2025-03-21 11:46:26 -04:00
dfac82d2aa Merge branch 'main' into jtran/units-indicator 2025-03-21 11:42:58 -04:00
cb32881cf2 Fix up more tests with setting annotations 2025-03-21 11:42:42 -04:00
a01498bf33 A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 15:15:40 +00:00
d1d3caad5c A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 15:02:02 +00:00
c62e0bfd64 Fix up point-and-click tests with unit setting lines 2025-03-21 10:48:01 -04:00
256b6b33f3 A snapshot a day keeps the bugs away! 📷🐛 2025-03-21 14:37:22 +00:00
35c80a78bb Merge branch 'main' into jtran/units-indicator 2025-03-21 10:23:42 -04:00
a5e682f9b0 Fix to reset to the default units when clearing the scene 2025-03-20 19:16:15 -04:00
c1319ca980 Add cache test 2025-03-20 19:16:15 -04:00
6e8fcdc088 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 22:52:01 +00:00
273b3b59e2 Merge branch 'main' into jtran/units-indicator 2025-03-20 18:38:37 -04:00
bac7ae4ff1 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 22:03:24 +00:00
9f5c2512a7 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 21:48:58 +00:00
485c5ab455 Fix chamfer e2e tests 2025-03-20 17:34:30 -04:00
d0cba2f080 Update output after new keyboard sample 2025-03-20 17:34:30 -04:00
7f1ed3b7cc A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
9e73987796 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
01e1589cd6 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
4b1901db7e A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
a892699b65 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
76dbb4a5d3 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:30 -04:00
d6cb471791 A snapshot a day keeps the bugs away! 📷🐛 2025-03-20 17:34:29 -04:00
ac4d68a812 Fix snapshot test to use mask 2025-03-20 17:34:12 -04:00
423ab5169f Add new assertion when creating project 2025-03-20 17:34:12 -04:00
6f013ec5fe Revert "Add arrow down to try to fix tests"
This reverts commit cde90b0e058e9fd4d4b68087c971195d3843d104.
2025-03-20 17:34:12 -04:00
8c3fc51d28 Change signature to reduce wasm round-trips 2025-03-20 17:34:12 -04:00
cffb777a7b Fix lint 2025-03-20 17:34:12 -04:00
0409b3159c Fix to consider only @settings to not be worth preserving 2025-03-20 17:34:12 -04:00
27c2c50508 Fix more tests 2025-03-20 17:34:12 -04:00
8f5eb9266b Fix formatting 2025-03-20 17:34:12 -04:00
041000fbb6 Trying to fix test assertions 2025-03-20 17:34:12 -04:00
15c3f21acc Add arrow down to try to fix tests 2025-03-20 17:34:12 -04:00
626dbf46f8 Fix tests 2025-03-20 17:34:12 -04:00
dc3a17149d Regenerate derive-docs 2025-03-20 17:34:11 -04:00
98238d040b Update output after setting units differently 2025-03-20 17:34:11 -04:00
3dff5b1c30 Remove units from settings struct 2025-03-20 17:34:11 -04:00
2f362e1774 Change tolerance to use the module units 2025-03-20 17:34:11 -04:00
fd45574652 Fix loading samples 2025-03-20 17:34:11 -04:00
1e23f37287 Change so that the new file in new projects get the units 2025-03-20 17:34:11 -04:00
8936a885c3 Move definition so that all units stuff is together and no cyclic imports 2025-03-20 17:34:09 -04:00
e94bdeb2ce Change so that new KCL files respect the length unit setting 2025-03-20 17:33:37 -04:00
494d8bdf4c Change lower-right controls units menu to be tied to only the per-file units 2025-03-20 17:33:36 -04:00
749 changed files with 47985 additions and 112198 deletions

2
.envrc
View File

@ -1,3 +1 @@
source_up_if_exists
use flake .

View File

@ -1,9 +1,7 @@
name: E2E Tests
on:
push:
branches:
- main
- all-e2e # this bypasses `fixme()` using `orRunWhenFullSuiteEnabled()`
branches: [ main ]
pull_request:
schedule:
- cron: 0 * * * * # hourly
@ -17,6 +15,7 @@ permissions:
pull-requests: write
actions: read
jobs:
conditions:
@ -68,16 +67,14 @@ jobs:
- name: Display conditions
shell: bash
run: |
# For debugging purposes
# For debugging purposes.
set -euo pipefail
echo "GITHUB_REF: $GITHUB_REF"
echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF"
echo "GITHUB_BASE_REF: $GITHUB_BASE_REF"
echo "significant: ${{ steps.path-changes.outputs.significant }}"
echo "should-run: ${{ steps.should-run.outputs.should-run }}"
prepare-wasm:
# separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
# seperate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
needs: conditions
steps:
@ -162,6 +159,7 @@ jobs:
name: prepared-wasm
path: |
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
snapshots:
name: playwright:snapshots:ubuntu
@ -245,7 +243,7 @@ jobs:
retention-days: 30
overwrite: true
- name: Check for changes
- name: check for changes
if: ${{ needs.conditions.outputs.should-run == 'true' && github.ref != 'refs/heads/main' }}
shell: bash
id: git-check
@ -257,8 +255,7 @@ jobs:
fi
- name: Commit changes, if any
# TODO: find a more reliable way to detect visual changes
if: ${{ false && needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
shell: bash
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
@ -344,7 +341,7 @@ jobs:
run: yarn tronb:vite:dev
- name: Install vector
if: ${{ needs.conditions.outputs.should-run == 'true' && contains(matrix.os, 'ubuntu') }}
if: contains(matrix.os, 'ubuntu')
shell: bash
run: |
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh

View File

@ -141,13 +141,13 @@ jobs:
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: yarn playwright install chromium --with-deps
- name: Run unit tests
- name: run unit tests
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: xvfb-run -a yarn test:unit
env:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- name: Check for changes
- name: check for changes
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
id: git-check
run: |

View File

@ -14,32 +14,16 @@ permissions:
jobs:
update-branch:
runs-on: ubuntu-latest
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- name: Sync with main
- shell: bash
run: |
# Create the branch
# checkout our branch
git checkout all-e2e || git checkout -b all-e2e
# Reset to main
# fetch origin
git fetch origin
# reset to main
git reset --hard origin/main
# Get a new SHA to prevent overwriting the commit status on main
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git commit --amend --message="[all-e2e] $(git log --max-count=1 --pretty=%B)"
# Overwrite the branch
git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}.git
# force push it
git push --force origin all-e2e

View File

@ -20,12 +20,8 @@ $(WASM_PACK):
###############################################################################
# BUILD
CARGO_SOURCES := rust/.cargo/config.toml $(wildcard rust/Cargo.*) $(wildcard rust/**/Cargo.*)
RUST_SOURCES := $(wildcard rust/**/*.rs)
REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx)
TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts)
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx)
RUST_SOURCES := $(wildcard rust/*) $(wildcard rust/**/*)
TYPESCRIPT_SOURCES := $(wildcard src/**/*.tsx) $(wildcard src/**/*.ts)
.PHONY: build
build: build-web build-desktop
@ -36,13 +32,13 @@ build-web: public/kcl_wasm_lib_bg.wasm build/index.html
.PHONY: build-desktop
build-desktop: public/kcl_wasm_lib_bg.wasm .vite/build/main.js
public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES)$(RUST_SOURCES)
public/kcl_wasm_lib_bg.wasm: $(RUST_SOURCES)
yarn build:wasm
build/index.html: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
build/index.html: $(TYPESCRIPT_SOURCES)
yarn build:local
.vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
.vite/build/main.js: $(TYPESCRIPT_SOURCES)
yarn tronb:vite:dev
###############################################################################
@ -77,29 +73,19 @@ run-desktop: install build-desktop ## Start the desktop app
###############################################################################
# TEST
E2E_WORKERS ?= 1
E2E_FAILURES ?= 1
E2E_GREP ?= ""
GREP ?= ""
.PHONY: test
test: test-unit test-e2e
.PHONY: test-unit
test-unit: install ## Run the unit tests
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
@ nc -z localhost 3000 || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
yarn test:unit
.PHONY: test-e2e
test-e2e: test-e2e-desktop
.PHONY: test-e2e-web
test-e2e-web: install build-web ## Run the web e2e tests
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
yarn chrome:test --headed --workers=$(E2E_WORKERS) --max-failures=$(E2E_FAILURES) --grep=$(E2E_GREP)
.PHONY: test-e2e-desktop
test-e2e-desktop: install build-desktop ## Run the desktop e2e tests
yarn test:playwright:electron --workers=$(E2E_WORKERS) --max-failures=$(E2E_FAILURES) --grep=$(E2E_GREP)
test-e2e: install build-desktop ## Run the e2e tests
yarn test:playwright:electron --workers=1 --grep=$(GREP)
###############################################################################
# CLEAN

60
docs/kcl/circle.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -10,10 +10,10 @@ Construct a circle derived from 3 points.
```js
circleThreePoint(
sketchSurfaceOrGroup: SketchOrSurface,
p1: [number],
p2: [number],
p3: [number],
sketchSurfaceOrGroup: SketchOrSurface,
tag?: TagDeclarator,
): Sketch
```
@ -23,10 +23,10 @@ circleThreePoint(
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes |
| `p1` | [`[number]`](/docs/kcl/types/number) | 1st point to derive the circle. | Yes |
| `p2` | [`[number]`](/docs/kcl/types/number) | 2nd point to derive the circle. | Yes |
| `p3` | [`[number]`](/docs/kcl/types/number) | 3rd point to derive the circle. | Yes |
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes |
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Identifier for the circle to reference elsewhere. | No |
### Returns

View File

@ -15,7 +15,7 @@ std::math::E: number = 2.71828182845904523536028747135266250_
### Examples
```js
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine({
angle = 30,

View File

@ -17,7 +17,7 @@ std::math::PI: number = 3.14159265358979323846264338327950288_
```js
circumference = 70
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> circle(center = [0, 0], radius = circumference/ (2 * PI))
example = extrude(exampleSketch, length = 5)

View File

@ -15,7 +15,7 @@ std::math::TAU: number = 6.28318530717958647692528676655900577_
### Examples
```js
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine({
angle = 50,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -61,6 +61,7 @@ layout: manual
* [`bezierCurve`](kcl/bezierCurve)
* [`ceil`](kcl/ceil)
* [`chamfer`](kcl/chamfer)
* [`circle`](kcl/circle)
* [`circleThreePoint`](kcl/circleThreePoint)
* [`close`](kcl/close)
* [`cm`](kcl/cm)
@ -73,6 +74,7 @@ layout: manual
* [`getOppositeEdge`](kcl/getOppositeEdge)
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
* [`helix`](kcl/helix)
* [`helixRevolutions`](kcl/helixRevolutions)
* [`hole`](kcl/hole)
* [`hollow`](kcl/hollow)
* [`inch`](kcl/inch)
@ -144,5 +146,3 @@ layout: manual
* [`cos`](kcl/std-math-cos)
* [`sin`](kcl/std-math-sin)
* [`tan`](kcl/std-math-tan)
* **std::sketch**
* [`circle`](kcl/std-sketch-circle)

View File

@ -9,7 +9,7 @@ Compute the cosine of a number (in radians).
```js
cos(@num: number(rad)): number(_)
cos(num: number(rad)): number(_)
```
@ -27,7 +27,7 @@ cos(@num: number(rad)): number(_)
### Examples
```js
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine({
angle = 30,

View File

@ -9,7 +9,7 @@ Compute the sine of a number (in radians).
```js
sin(@num: number(rad)): number(_)
sin(num: number(rad)): number(_)
```
@ -27,7 +27,7 @@ sin(@num: number(rad)): number(_)
### Examples
```js
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine({
angle = 50,

View File

@ -9,7 +9,7 @@ Compute the tangent of a number (in radians).
```js
tan(@num: number(rad)): number(_)
tan(num: number(rad)): number(_)
```
@ -27,7 +27,7 @@ tan(@num: number(rad)): number(_)
### Examples
```js
exampleSketch = startSketchOn(XZ)
exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine({
angle = 50,

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,6 @@ A helix.
| `revolutions` |[`number`](/docs/kcl/types/number)| Number of revolutions. | No |
| `angleStart` |[`number`](/docs/kcl/types/number)| Start angle (in degrees). | No |
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
| `cylinderId` |[`string`](/docs/kcl/types/string)| The cylinder the helix was created on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |

View File

@ -10,8 +10,8 @@ A point in two dimensional space.
type Point2d = [number; 2]
```
[`Point2d`](/docs/kcl/types/Point2d) is an alias for a two-element array of [number](/docs/kcl/types/number)s. To write a value
with type [`Point2d`](/docs/kcl/types/Point2d), use an array, e.g., `[0, 0]` or `[5.0, 3.14]`.
`Point2d` is an alias for a two-element array of [number](/docs/kcl/types/number)s. To write a value
with type `Point2d`, use an array, e.g., `[0, 0]` or `[5.0, 3.14]`.

View File

@ -10,8 +10,8 @@ A point in three dimensional space.
type Point3d = [number; 3]
```
[`Point3d`](/docs/kcl/types/Point3d) is an alias for a three-element array of [number](/docs/kcl/types/number)s. To write a value
with type [`Point3d`](/docs/kcl/types/Point3d), use an array, e.g., `[0, 0, 0]` or `[5.0, 3.14, 6.8]`.
`Point3d` is an alias for a three-element array of [number](/docs/kcl/types/number)s. To write a value
with type `Point3d`, use an array, e.g., `[0, 0, 0]` or `[5.0, 3.14, 6.8]`.

View File

@ -17,7 +17,7 @@ mySketch = startSketchOn('XY')
|> close()
```
The `mySketch` variable will be an executed [`Sketch`](/docs/kcl/types/Sketch) object. Executed being past
The `mySketch` variable will be an executed `Sketch` object. Executed being past
tense, because the engine has already executed the commands to create the sketch.
The previous sketch commands will never be executed again, in this case.

View File

@ -18,7 +18,7 @@ myPart = startSketchOn('XY')
|> extrude(length = 6)
```
The `myPart` variable will be an executed [`Solid`](/docs/kcl/types/Solid) object. Executed being past
The `myPart` variable will be an executed `Solid` object. Executed being past
tense, because the engine has already executed the commands to create the solid.
The previous solid commands will never be executed again, in this case.

View File

@ -85,7 +85,7 @@ async function doBasicSketch(
await page.mouse.click(startXPx, 500 - PUR * 20)
if (openPanes.includes('code')) {
await expect(u.codeLocator)
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(length = ${commonPoints.num1})
@ -145,7 +145,7 @@ async function doBasicSketch(
// Open the code pane.
await u.openKclCodePanel()
await expect(u.codeLocator)
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(length = ${commonPoints.num1}, tag = $seg01)

View File

@ -46,7 +46,7 @@ test.describe(
},
}
const code = `sketch001 = startSketchOn(${plane})profile001 = startProfileAt([0.91, -1.22], sketch001)`
const code = `@settings(defaultLengthUnit = in)sketch001 = startSketchOn('${plane}')profile001 = startProfileAt([0.91, -1.22], sketch001)`
await u.openDebugPanel()

View File

@ -21,15 +21,14 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
await page.addInitScript(() => {
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
// Extruded Triangle
sketch001 = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> line(end = [10, 0])
|> line(end = [-5, 10])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(sketch001, length = 5)`
`// Extruded Triangle
sketch001 = startSketchOn(XZ)
|> startProfileAt([0, 0], %)
|> line(end = [10, 0])
|> line(end = [-5, 10])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(sketch001, length = 5)`
)
})
@ -251,11 +250,11 @@ test(
])
await Promise.all([
fsp.copyFile(
executorInputPath('router-template-slate.kcl'),
executorInputPath('cylinder-inches.kcl'),
join(routerTemplateDir, 'main.kcl')
),
fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
join(bracketDir, 'main.kcl')
),
])

View File

@ -513,8 +513,7 @@ c = 3 + a`
await homePage.openProject(projectName)
// TODO: you probably shouldn't need an engine connection to add a parameter,
// but you do because all modeling commands have that requirement
// Don't use scene.settled here
await expect(scene.startEditSketchBtn).toBeEnabled({ timeout: 15_000 })
await scene.settled(cmdBar)
await test.step(`Create a parameter via command bar`, async () => {
await cmdBar.cmdBarOpenBtn.click()
@ -543,12 +542,7 @@ c = 3 + a`
)
const newValue = `2 * b + a`
await test.step(`Edit the parameter via command bar`, async () => {
// TODO: make the command palette command registration more static, and the enabled state more dynamic
// so that we can just open the command palette and know all commands will be there.
await expect(scene.startEditSketchBtn).toBeEnabled()
await cmdBar.cmdBarOpenBtn.click()
await cmdBar.chooseCommand('edit parameter')
await cmdBar.expectState({

View File

@ -20,11 +20,11 @@ test(
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
await Promise.all([
fsp.copyFile(
executorInputPath('router-template-slate.kcl'),
executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'other.kcl')
),
fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
path.join(bracketDir, 'main.kcl')
),
])
@ -107,7 +107,7 @@ test(
},
{ timeout: 15_000 }
)
.toBeGreaterThan(300_000)
.toBeGreaterThan(30_000)
})
})
@ -187,7 +187,7 @@ test(
},
{ timeout: 15_000 }
)
.toBeGreaterThan(70_000)
.toBeGreaterThan(50_000)
})
})
}

View File

@ -32,26 +32,30 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('/')
await page.keyboard.up('ControlOrMeta')
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XY)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
|> line(end = [-20, 0])
// |> close()`)
// |> close()`.replaceAll('\n', '')
)
// uncomment the code
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('/')
await page.keyboard.up('ControlOrMeta')
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XY)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
|> line(end = [-20, 0])
|> close()`)
|> close()`.replaceAll('\n', '')
)
})
test('ensure we use the cache, and do not re-execute', async ({
@ -178,13 +182,15 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await page.locator('#code-pane button:first-child').click()
await page.locator('button:has-text("Format code")').click()
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XY)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
|> line(end = [-20, 0])
|> close()`)
|> close()`.replaceAll('\n', '')
)
})
test('if you click the format button it formats your code and executes so lints are still there', async ({
@ -227,13 +233,15 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
await expect(page.locator('.cm-content'))
.toHaveText(`sketch_001 = startSketchOn(XY)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch_001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
|> line(end = [-20, 0])
|> close()`)
|> close()`.replaceAll('\n', '')
)
// error in guter
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
@ -815,10 +823,12 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
// there shouldn't be any auto complete options for 'lin' in the comment
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn('XZ')
|> startProfileAt([3.14, 12], %)
|> xLine(%, length = 5) // lin`)
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
)
// expect there to be no KCL errors
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
@ -888,10 +898,12 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
// there shouldn't be any auto complete options for 'lin' in the comment
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
await expect(page.locator('.cm-content')).toHaveText(
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn('XZ')
|> startProfileAt([3.14, 12], %)
|> xLine(%, length = 5) // lin`)
|> xLine(%, length = 5) // lin`.replaceAll('\n', '')
)
})
})
test('Can undo a click and point extrude with ctrl+z', async ({

View File

@ -6,7 +6,6 @@ import {
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
runningOnWindows,
} from './test-utils'
import { join } from 'path'
import { FILE_EXT } from 'lib/constants'
@ -16,9 +15,6 @@ test.describe('integrations tests', () => {
'Creating a new file or switching file while in sketchMode should exit sketchMode',
{ tag: '@electron' },
async ({ page, context, homePage, scene, editor, toolbar, cmdBar }) => {
if (runningOnWindows()) {
test.fixme(orRunWhenFullSuiteEnabled())
}
await context.folderSetupFn(async (dir) => {
const bracketDir = join(dir, 'test-sample')
await fsp.mkdir(bracketDir, { recursive: true })

View File

@ -310,7 +310,9 @@ export async function expectPixelColor(
.toBeTruthy()
.catch((cause) => {
throw new Error(
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
`ExpectPixelColor: point ${JSON.stringify(
coords
)} was expecting ${colour} but got ${finalValue}`,
{ cause }
)
})

View File

@ -11,7 +11,7 @@ test(
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('cylinder-inches.kcl'),
join(bracketDir, 'main.kcl')
)
})
@ -51,7 +51,7 @@ test(
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('cylinder-inches.kcl'),
join(bracketDir, 'main.kcl')
)
})

View File

@ -1,292 +0,0 @@
import { test, expect } from './zoo-test'
import { PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
import * as fsp from 'fs/promises'
import { join } from 'path'
import {
createProject,
tomlToPerProjectSettings,
perProjectsettingsToToml,
} from './test-utils'
import { NamedView } from '@rust/kcl-lib/bindings/NamedView'
// Helper function to determine if the file path on disk exists
// Specifically this is used to check if project.toml exists on disk
const fileExists = async (path: string) => {
return !!(await fsp
.stat(path)
.then((_) => true)
.catch((_) => false))
}
// Here are a few uuids.
// When created named views rust will auto generate uuids and they will
// never match the snapshots. Overwrite them in memory to these
// values to have them match the snapshots.
const uuid1: string = '0656fb1a-9640-473e-b334-591dc70c0138'
const uuid2: string = 'c810cf04-c6cc-4a4a-8b11-17bf445dcab7'
const uuid3: string = 'cfecbfee-48a6-4561-b96d-ffbe5678bb7d'
// Look up the named view by name and then rewrite it with the same uuid each time
const nameToUuid: Map<string, string> = new Map()
nameToUuid.set('uuid1', uuid1)
nameToUuid.set('uuid2', uuid2)
nameToUuid.set('uuid3', uuid3)
/**
* Given the project.toml string, overwrite the named views to be the constant uuid
* values to match the snapshots. The uuids are randomly generated
*/
function tomlStringOverWriteNamedViewUuids(toml: string): string {
const settings = tomlToPerProjectSettings(toml)
const namedViews = settings.settings?.app?.named_views
if (namedViews) {
const entries = Object.entries(namedViews)
const remappedNamedViews: { [key: string]: NamedView } = {}
entries.forEach(([_, value]) => {
if (value) {
// {name:'uuid1'} -> uuid1 lookup
const staticUuid = nameToUuid.get(value.name)
if (staticUuid) {
remappedNamedViews[staticUuid] = value
}
}
})
if (settings && settings.settings && settings.settings.app) {
settings.settings.app.named_views = remappedNamedViews
}
}
return perProjectsettingsToToml(settings)
}
test.describe('Named view tests', () => {
test('Verify project.toml is not created', async ({ page }, testInfo) => {
// Create project and load it
const projectName = 'named-views'
await createProject({ name: projectName, page })
// Generate file paths for project.toml
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// project.toml should not exist on initial project creation
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(false)
})
test('Verify named view gets created', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView = 'uuid1'
// Create and load project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create named view
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView)
await cmdBar.progressCmdBar(false)
// Generate paths for the project.toml
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Expect project.toml to be generated on disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
})
test('Verify named view gets deleted', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView1 = 'uuid1'
const myNamedView2 = 'uuid2'
// Create project and go into the project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create a new named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView1)
await cmdBar.progressCmdBar(false)
// Generate file paths for project.toml
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Except the project.toml to be written to disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
// Delete a named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('delete named view')
cmdBar.selectOption({ name: myNamedView2 })
await cmdBar.progressCmdBar(false)
// Read project.toml into memory again since we deleted a named view
tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// // Write the entire tomlString to a snapshot.
// // There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-deleted')
})
test('Verify named view gets loaded', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView = 'uuid1'
// Create project and go into the project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create a new named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView)
await cmdBar.progressCmdBar(false)
// Generate file paths for project.toml
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Except the project.toml to be written to disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-named-view-gets-created')
// Create a load a named view
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('load named view')
await cmdBar.argumentInput.fill(myNamedView)
await cmdBar.progressCmdBar(false)
// Check the toast appeared
await expect(
page.getByText(`Named view ${myNamedView} loaded.`)
).toBeVisible()
})
test('Verify two named views get created', async ({
cmdBar,
scene,
page,
}, testInfo) => {
const projectName = 'named-views'
const myNamedView1 = 'uuid1'
const myNamedView2 = 'uuid2'
// Create and load project
await createProject({ name: projectName, page })
await scene.waitForExecutionDone()
// Create named view
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView1)
await cmdBar.progressCmdBar(false)
await page.waitForTimeout(1000)
const orbitMouseStart = { x: 800, y: 130 }
const orbitMouseEnd = { x: 0, y: 130 }
await page.mouse.move(orbitMouseStart.x, orbitMouseStart.y)
await page.mouse.down({ button: 'middle' })
await page.mouse.move(orbitMouseEnd.x, orbitMouseEnd.y, {
steps: 3,
})
await page.mouse.up({ button: 'middle' })
await page.waitForTimeout(1000)
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('create named view')
await cmdBar.argumentInput.fill(myNamedView2)
await cmdBar.progressCmdBar(false)
// Wait a moment for the project.toml to get written to disk with the new view point
await page.waitForTimeout(1000)
// Generate paths for the project.toml
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
// Expect project.toml to be generated on disk since a named view was created
await expect(async () => {
let exists = await fileExists(tempProjectSettingsFilePath)
expect(exists).toBe(true)
}).toPass()
// Read project.toml into memory
let tomlString = await fsp.readFile(tempProjectSettingsFilePath, 'utf-8')
// Rewrite the uuids in the named views to match snapshot otherwise they will be randomly generated from rust and break
tomlString = tomlStringOverWriteNamedViewUuids(tomlString)
// Write the entire tomlString to a snapshot.
// There are many key/value pairs to check this is a safer match.
expect(tomlString).toMatchSnapshot('verify-two-named-view-gets-created')
})
})

View File

@ -1,16 +0,0 @@
[settings]
modeling = { }
text_editor = { }
command_bar = { }
[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138]
name = "uuid1"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 0, 0, 0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1

View File

@ -1,16 +0,0 @@
[settings]
modeling = { }
text_editor = { }
command_bar = { }
[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138]
name = "uuid1"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 0, 0, 0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1

View File

@ -1,28 +0,0 @@
[settings]
modeling = { }
text_editor = { }
command_bar = { }
[settings.app.named_views.0656fb1a-9640-473e-b334-591dc70c0138]
name = "uuid1"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 0, 0, 0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1
[settings.app.named_views.c810cf04-c6cc-4a4a-8b11-17bf445dcab7]
name = "uuid2"
eye_offset = 1_378.0059
fov_y = 45
is_ortho = false
ortho_scale_enabled = true
ortho_scale_factor = 1.6
pivot_position = [ 1_826.5239, 0.0, 0.0 ]
pivot_rotation = [ 0.5380994, 0.0, 0.0, 0.8428814 ]
world_coord_system = "right_handed_up_z"
version = 1

View File

@ -1,312 +0,0 @@
import { test, expect } from './zoo-test'
/**
* Not all menu actions are tested. Some are default electron menu actions.
* Test file menu actions that trigger something in the frontend
*/
test.describe('Native file menu', { tag: ['@electron'] }, () => {
test.describe('Home page', () => {
test.describe('File role', () => {
test('File.Create project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const newProject =
app.applicationMenu.getMenuItemById('File.New project')
if (!newProject) fail()
newProject.click()
})
// Check that the command bar is opened
await expect(cmdBar.cmdBarElement).toBeVisible()
// Check the placeholder project name exists
const actualArgument = await cmdBar.cmdBarElement
.getByTestId('cmd-bar-arg-value')
.inputValue()
const expectedArgument = 'project-$nnn'
expect(actualArgument).toBe(expectedArgument)
})
test('File.Open project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const openProject =
app.applicationMenu.getMenuItemById('File.Open project')
if (!openProject) fail()
openProject.click()
})
// Check that the command bar is opened
await expect(cmdBar.cmdBarElement).toBeVisible()
// Check the placeholder project name exists
const actual = await cmdBar.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = 'Open project'
expect(actual).toBe(expected)
})
test('File.Preferences.User settings', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const userSettings = app.applicationMenu.getMenuItemById(
'File.Preferences.User settings'
)
if (!userSettings) fail()
userSettings.click()
})
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the user tab
const actualText = settings.getByText(
'The overall appearance of the app'
)
await expect(actualText).toBeVisible()
})
test('File.Preferences.Keybindings', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const keybindings = app.applicationMenu.getMenuItemById(
'File.Preferences.Keybindings'
)
if (!keybindings) fail()
keybindings.click()
})
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the keybindings tab
const enterSketchMode = settings.locator('#enter-sketch-mode')
await expect(enterSketchMode).toBeVisible()
})
test('File.Preferences.User default units', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'File.Preferences.User default units'
)
if (!menu) fail()
menu.click()
})
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
const defaultUnit = settings.locator('#defaultUnit')
await expect(defaultUnit).toBeVisible()
})
test('File.Preferences.Theme', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'File.Preferences.Theme'
)
if (!menu) fail()
menu.click()
})
// Check that the command bar is opened
await expect(cmdBar.cmdBarElement).toBeVisible()
// Check the placeholder project name exists
const actual = await cmdBar.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = 'Settings · app · theme'
expect(actual).toBe(expected)
})
test('File.Preferences.Theme color', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'File.Preferences.Theme color'
)
if (!menu) fail()
menu.click()
})
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
const defaultUnit = settings.locator('#themeColor')
await expect(defaultUnit).toBeVisible()
})
test('File.Preferences.Sign out', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById('File.Sign out')
if (!menu) fail()
// FIXME: Add back when you can actually sign out
// menu.click()
})
// FIXME: When signing out during E2E the page is not bound correctly.
// It cannot find the button
// const signIn = page.getByTestId('sign-in-button')
// await expect(signIn).toBeVisible()
})
})
test.describe('Edit role', () => {
test('Edit.Rename project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Edit.Rename project'
)
if (!menu) fail()
menu.click()
})
// Check the placeholder project name exists
const actual = await cmdBar.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = 'Rename project'
expect(actual).toBe(expected)
})
test('Edit.Delete project', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Edit.Delete project'
)
if (!menu) fail()
menu.click()
})
// Check the placeholder project name exists
const actual = await cmdBar.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = 'Delete project'
expect(actual).toBe(expected)
})
test('Edit.Change project directory', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Edit.Change project directory'
)
if (!menu) fail()
menu.click()
})
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
const projectDirectory = settings.locator('#projectDirectory')
await expect(projectDirectory).toBeVisible()
})
})
test.describe('View role', () => {
test('View.Command Palette...', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'View.Command Palette...'
)
if (!menu) fail()
menu.click()
})
// Check the placeholder project name exists
const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search')
await expect(actual).toBeVisible()
})
})
test.describe('Help role', () => {
test('Help.Show all commands', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Help.Show all commands'
)
if (!menu) fail()
menu.click()
})
// Check the placeholder project name exists
const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search')
await expect(actual).toBeVisible()
})
test('Help.KCL code samples', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Help.KCL code samples'
)
if (!menu) fail()
})
})
test('Help.Refresh and report a bug', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Help.Refresh and report a bug'
)
if (!menu) fail()
menu.click()
})
// Core dump and refresh magic number timeout
await page.waitForTimeout(7000)
const actual = page.getByText(
'No Projects found, ready to make your first one?'
)
await expect(actual).toBeVisible()
})
test('Help.Reset onboarding', async ({ tronApp, cmdBar, page }) => {
if (!tronApp) fail()
// Run electron snippet to find the Menu!
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) fail()
const menu = app.applicationMenu.getMenuItemById(
'Help.Reset onboarding'
)
if (!menu) fail()
menu.click()
})
const actual = page.getByText(
`This is a hardware design tool that lets you edit visually, with code, or both. It's powered by the KittyCAD Design API, the first API created for anyone to build hardware design tools.`
)
await expect(actual).toBeVisible()
})
})
})
})

View File

@ -137,7 +137,7 @@ test.describe('Point-and-click tests', () => {
await scene.moveCameraTo(cameraPos, cameraTarget)
await test.step('check chamfer selection changes cursor positon', async () => {
await test.step('check chamfer selection changes cursor position', async () => {
await expect(async () => {
// sometimes initial click doesn't register
await clickChamfer()
@ -173,7 +173,7 @@ test.describe('Point-and-click tests', () => {
})
await test.step('Check there is no errors after code created in previous steps executes', async () => {
await editor.expectState({
activeLines: ['sketch001 = startSketchOn(XZ)'],
activeLines: ['@settings(defaultLengthUnit = in)'],
highlightedCode: '',
diagnostics: [],
})
@ -299,7 +299,8 @@ test.describe('Point-and-click tests', () => {
await test.step('verify at the end of the test that final code is what is expected', async () => {
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|> angledLine([
@ -369,7 +370,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
})
})
test('Works on chamfers that are non in a pipeExpression can break up multi edges in a chamfer array', async ({
test('Works on chamfers that are not in a pipeExpression can break up multi edges in a chamfer array', async ({
context,
page,
homePage,
@ -418,7 +419,8 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|>close()`,
})
await editor.expectEditor.toContain(
`sketch001 = startSketchOn(XZ)
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([75.8, 317.2], %)
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|> angledLine([
@ -1071,7 +1073,7 @@ openSketch = startSketchOn(XY)
})
})
test('Helix point-and-click on default axis', async ({
test('Helix point-and-click', async ({
context,
page,
homePage,
@ -1082,26 +1084,29 @@ openSketch = startSketchOn(XY)
}) => {
// One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 }
const expectedOutput = `helix001 = helix( revolutions = 1, angleStart = 360, ccw = false, radius = 5, axis = 'X', length = 5,)`
const expectedOutput = `helix001 = helix( revolutions = 1, angleStart = 360, counterClockWise = false, radius = 5, axis = 'X', length = 5,)`
const expectedLine = `revolutions=1,`
await homePage.goToModelingScene()
// await test.step(`Look for the red of the default plane`, async () => {
// await scene.expectPixelColor([96, 52, 52], testPoint, 15)
// })
await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'axisOrEdge',
currentArgValue: '',
currentArgKey: 'revolutions',
currentArgValue: '1',
headerArguments: {
AngleStart: '',
AxisOrEdge: '',
Axis: '',
CounterClockWise: '',
Length: '',
Radius: '',
Revolutions: '',
},
highlightedHeaderArg: 'axisOrEdge',
highlightedHeaderArg: 'revolutions',
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
@ -1111,7 +1116,6 @@ openSketch = startSketchOn(XY)
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
@ -1177,165 +1181,6 @@ openSketch = startSketchOn(XY)
})
})
const helixCases = [
{
selectionType: 'segment',
testPoint: { x: 513, y: 221 },
expectedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = seg01, length = 100,)`,
expectedEditedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = seg01, length = 50,)`,
},
{
selectionType: 'sweepEdge',
testPoint: { x: 564, y: 364 },
expectedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = getOppositeEdge(seg01), length = 100,)`,
expectedEditedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = getOppositeEdge(seg01), length = 50,)`,
},
]
helixCases.map(
({ selectionType, testPoint, expectedOutput, expectedEditedOutput }) => {
test(`Helix point-and-click around ${selectionType}`, async ({
context,
page,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
page.on('console', console.log)
const initialCode = `sketch001 = startSketchOn('XZ')
profile001 = startProfileAt([0, 0], sketch001)
|> yLine(length = 100)
|> line(endAbsolute = [100, 0])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(profile001, length = 100)`
// One dumb hardcoded screen pixel value
const [clickOnEdge] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await test.step(`Go through the command bar flow`, async () => {
await toolbar.closePane('code')
await toolbar.helixButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'axisOrEdge',
currentArgValue: '',
headerArguments: {
AngleStart: '',
AxisOrEdge: '',
CounterClockWise: '',
Length: '',
Radius: '',
Revolutions: '',
},
highlightedHeaderArg: 'axisOrEdge',
commandName: 'Helix',
})
await cmdBar.selectOption({ name: 'Edge' }).click()
await clickOnEdge()
await cmdBar.progressCmdBar()
await cmdBar.argumentInput.focus()
await page.keyboard.insertText('20')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('0')
await cmdBar.progressCmdBar()
await cmdBar.selectOption({ name: 'True' }).click()
await page.keyboard.insertText('1')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('100')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '0',
AxisOrEdge: 'Edge',
Edge: `1 ${selectionType}`,
CounterClockWise: '',
Length: '100',
Radius: '1',
Revolutions: '20',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await toolbar.openPane('code')
await editor.expectEditor.toContain(expectedOutput)
await toolbar.closePane('code')
})
await test.step(`Edit helix through the feature tree`, async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Helix',
0
)
await operationButton.dblclick()
const initialInput = '100'
const newInput = '50'
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
currentArgKey: 'length',
currentArgValue: initialInput,
headerArguments: {
AngleStart: '0',
CounterClockWise: '',
Length: initialInput,
Radius: '1',
Revolutions: '20',
},
highlightedHeaderArg: 'length',
})
await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput
.locator('.cm-content')
.fill(newInput)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
AngleStart: '0',
CounterClockWise: '',
Length: newInput,
Radius: '1',
Revolutions: '20',
},
commandName: 'Helix',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
await toolbar.openPane('code')
await editor.expectEditor.toContain(expectedEditedOutput)
await toolbar.closePane('code')
})
await test.step('Delete helix via feature tree selection', async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Helix',
0
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await editor.expectEditor.not.toContain(expectedEditedOutput)
await expect(
await toolbar.getFeatureTreeOperation('Helix', 0)
).not.toBeVisible()
})
})
}
)
const loftPointAndClickCases = [
{ shouldPreselect: true },
{ shouldPreselect: false },
@ -1497,7 +1342,8 @@ loft001 = loft([sketch001, sketch002])
{
targetType: 'circle',
testPoint: { x: 700, y: 250 },
initialCode: `sketch001 = startSketchOn('YZ')
initialCode: `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn('YZ')
profile001 = circle(sketch001, center = [0, 0], radius = 500)
sketch002 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
@ -1507,7 +1353,8 @@ sketch002 = startSketchOn('XZ')
{
targetType: 'rectangle',
testPoint: { x: 710, y: 255 },
initialCode: `sketch001 = startSketchOn('YZ')
initialCode: `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn('YZ')
profile001 = startProfileAt([-400, -400], sketch001)
|> angledLine([0, 800], %, $rectangleSegmentA001)
|> angledLine([
@ -1664,7 +1511,8 @@ sketch002 = startSketchOn('XZ')
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn(YZ)
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(YZ)
|> circle(
center = [0, 0],
radius = 500
@ -1773,7 +1621,7 @@ extrude001 = extrude(sketch001, length = -12)
const filletColor: [number, number, number] = [127, 127, 127]
const backgroundColor: [number, number, number] = [30, 30, 30]
const lowTolerance = 20
const highTolerance = 70 // TODO: understand why I needed that for edgeColorYellow on macos (local)
const highTolerance = 40
// Setup
await test.step(`Initial test setup`, async () => {
@ -1860,54 +1708,6 @@ extrude001 = extrude(sketch001, length = -12)
await scene.expectPixelColor(filletColor, firstEdgeLocation, lowTolerance)
})
// Test 1.1: Edit fillet (segment type)
async function editFillet(
featureTreeIndex: number,
oldValue: string,
newValue: string
) {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Fillet',
featureTreeIndex
)
await operationButton.dblclick({ button: 'left' })
await cmdBar.expectState({
commandName: 'Fillet',
currentArgKey: 'radius',
currentArgValue: oldValue,
headerArguments: {
Radius: oldValue,
},
highlightedHeaderArg: 'radius',
stage: 'arguments',
})
await page.keyboard.insertText(newValue)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Radius: newValue,
},
commandName: 'Fillet',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
}
await test.step('Edit fillet via feature tree selection works', async () => {
const firstFilletFeatureTreeIndex = 0
const editedRadius = '1'
await editFillet(firstFilletFeatureTreeIndex, '5', editedRadius)
await editor.expectEditor.toContain(
firstFilletDeclaration.replace('radius = 5', 'radius = ' + editedRadius)
)
// Edit back to original radius
await editFillet(firstFilletFeatureTreeIndex, editedRadius, '5')
await editor.expectEditor.toContain(firstFilletDeclaration)
})
// Test 2: Command bar flow without preselected edges
await test.step(`Open fillet UI without selecting edges`, async () => {
await page.waitForTimeout(100)
@ -1992,23 +1792,6 @@ extrude001 = extrude(sketch001, length = -12)
)
})
// Test 2.1: Edit fillet (edgeSweep type)
await test.step('Edit fillet via feature tree selection works', async () => {
const secondFilletFeatureTreeIndex = 1
const editedRadius = '2'
await editFillet(secondFilletFeatureTreeIndex, '5', editedRadius)
await editor.expectEditor.toContain(
secondFilletDeclaration.replace(
'radius = 5',
'radius = ' + editedRadius
)
)
// Edit back to original radius
await editFillet(secondFilletFeatureTreeIndex, editedRadius, '5')
await editor.expectEditor.toContain(secondFilletDeclaration)
})
// Test 3: Delete fillets
await test.step('Delete fillet via feature tree selection', async () => {
await test.step('Open Feature Tree Pane', async () => {
@ -2031,43 +1814,6 @@ extrude001 = extrude(sketch001, length = -12)
})
})
test(`Fillet point-and-click edit rejected when not in pipe`, async ({
context,
page,
homePage,
scene,
toolbar,
}) => {
const initialCode = `sketch001 = startSketchOn(XY)
profile001 = circle(
sketch001,
center = [0, 0],
radius = 100,
tag = $seg01,
)
extrude001 = extrude(profile001, length = 100)
fillet001 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])
`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
await test.step('Double-click in feature tree and expect error toast', async () => {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation('Fillet', 0)
await operationButton.dblclick({ button: 'left' })
await expect(
page.getByText(
'Only chamfer and fillet in pipe expressions are supported for edits'
)
).toBeVisible()
await page.waitForTimeout(1000)
})
})
test(`Fillet point-and-click delete`, async ({
context,
page,
@ -2333,7 +2079,8 @@ extrude001 = extrude(profile001, length = 5)
cmdBar,
}) => {
// Code samples
const initialCode = `sketch001 = startSketchOn(XY)
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
|> startProfileAt([-12, -6], %)
|> line(end = [0, 12])
|> line(end = [24, 0])
@ -2364,7 +2111,7 @@ extrude001 = extrude(sketch001, length = -12)
const chamferColor: [number, number, number] = [168, 168, 168]
const backgroundColor: [number, number, number] = [30, 30, 30]
const lowTolerance = 20
const highTolerance = 70 // TODO: understand why I needed that for edgeColorYellow on macos (local)
const highTolerance = 40
// Setup
await test.step(`Initial test setup`, async () => {
@ -2446,57 +2193,6 @@ extrude001 = extrude(sketch001, length = -12)
)
})
// Test 1.1: Edit sweep
async function editChamfer(
featureTreeIndex: number,
oldValue: string,
newValue: string
) {
await toolbar.openPane('feature-tree')
const operationButton = await toolbar.getFeatureTreeOperation(
'Chamfer',
featureTreeIndex
)
await operationButton.dblclick({ button: 'left' })
await cmdBar.expectState({
commandName: 'Chamfer',
currentArgKey: 'length',
currentArgValue: oldValue,
headerArguments: {
Length: oldValue,
},
highlightedHeaderArg: 'length',
stage: 'arguments',
})
await page.keyboard.insertText(newValue)
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Length: newValue,
},
commandName: 'Chamfer',
})
await cmdBar.progressCmdBar()
await toolbar.closePane('feature-tree')
}
await test.step('Edit chamfer via feature tree selection works', async () => {
const firstChamferFeatureTreeIndex = 0
const editedLength = '1'
await editChamfer(firstChamferFeatureTreeIndex, '5', editedLength)
await editor.expectEditor.toContain(
firstChamferDeclaration.replace(
'length = 5',
'length = ' + editedLength
)
)
// Edit back to original radius
await editChamfer(firstChamferFeatureTreeIndex, editedLength, '5')
await editor.expectEditor.toContain(firstChamferDeclaration)
})
// Test 2: Command bar flow without preselected edges
await test.step(`Open chamfer UI without selecting edges`, async () => {
await page.waitForTimeout(100)
@ -2581,23 +2277,6 @@ extrude001 = extrude(sketch001, length = -12)
)
})
// Test 2.1: Edit chamfer (edgeSweep type)
await test.step('Edit chamfer via feature tree selection works', async () => {
const secondChamferFeatureTreeIndex = 1
const editedLength = '2'
await editChamfer(secondChamferFeatureTreeIndex, '5', editedLength)
await editor.expectEditor.toContain(
secondChamferDeclaration.replace(
'length = 5',
'length = ' + editedLength
)
)
// Edit back to original length
await editChamfer(secondChamferFeatureTreeIndex, editedLength, '5')
await editor.expectEditor.toContain(secondChamferDeclaration)
})
// Test 3: Delete chamfer via feature tree selection
await test.step('Open Feature Tree Pane', async () => {
await toolbar.openPane('feature-tree')
@ -2625,7 +2304,8 @@ extrude001 = extrude(sketch001, length = -12)
toolbar,
}) => {
// Code samples
const initialCode = `sketch001 = startSketchOn(XY)
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
|> startProfileAt([-12, -6], %)
|> line(end = [0, 12])
|> line(end = [24, 0], tag = $seg02)
@ -2779,7 +2459,8 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn(XZ)
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> circle(center = [0, 0], radius = 30)
extrude001 = extrude(sketch001, length = 30)
`
@ -2914,7 +2595,8 @@ extrude001 = extrude(sketch001, length = 30)
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn(XY)
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XY)
|> startProfileAt([-20, 20], %)
|> xLine(length = 40)
|> yLine(length = -60)
@ -3032,7 +2714,8 @@ extrude001 = extrude(sketch001, length = 40)
})
const shellSketchOnFacesCases = [
`sketch001 = startSketchOn(XZ)
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> circle(center = [0, 0], radius = 100)
|> extrude(length = 100)
@ -3040,7 +2723,8 @@ sketch002 = startSketchOn(sketch001, 'END')
|> circle(center = [0, 0], radius = 50)
|> extrude(length = 50)
`,
`sketch001 = startSketchOn(XZ)
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> circle(center = [0, 0], radius = 100)
extrude001 = extrude(sketch001, length = 100)
@ -3436,7 +3120,8 @@ radius = 8.69
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn(XZ)
const initialCode = `@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
profile001 = circle(
sketch001,
center = [0, 0],

View File

@ -8,7 +8,6 @@ import {
createProject,
getPlaywrightDownloadDir,
orRunWhenFullSuiteEnabled,
runningOnWindows,
} from './test-utils'
import fsp from 'fs/promises'
import fs from 'fs'
@ -87,7 +86,7 @@ test(
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl')
)
})
@ -124,7 +123,7 @@ test(
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl')
)
const errorDir = path.join(dir, 'broken-code')
@ -192,7 +191,7 @@ test(
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText = `The arg tag was given, but it was the wrong type`
const crypticErrorText = `Expected a tag declarator`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared.
@ -213,7 +212,7 @@ test(
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl')
)
const emptyDir = path.join(dir, 'empty')
@ -290,7 +289,7 @@ test(
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl')
)
@ -352,14 +351,11 @@ test(
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
if (runningOnWindows()) {
test.fixme(orRunWhenFullSuiteEnabled())
}
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl')
)
await fsp.copyFile(
@ -413,7 +409,7 @@ test(
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText = `The arg tag was given, but it was the wrong type`
const crypticErrorText = `Expected a tag declarator`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// black pixel means the scene has been cleared.
@ -457,7 +453,7 @@ test(
// error text on hover
await page.hover('.cm-lint-marker-error')
const crypticErrorText = `The arg tag was given, but it was the wrong type`
const crypticErrorText = `Expected a tag declarator`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
}
)
@ -478,7 +474,7 @@ test.describe('Can export from electron app', () => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl')
)
})
@ -1332,9 +1328,6 @@ test(
'Can load a file with CRLF line endings',
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
if (runningOnWindows()) {
test.fixme(orRunWhenFullSuiteEnabled())
}
await context.folderSetupFn(async (dir) => {
const routerTemplateDir = path.join(dir, 'router-template-slate')
await fsp.mkdir(routerTemplateDir, { recursive: true })
@ -1504,7 +1497,12 @@ test(
await u.waitForPageLoad()
await page.locator('.cm-content').fill(`sketch001 = startSketchOn(XZ)
// The file should be prepopulated with the user's unit settings.
await expect(page.locator('.cm-content')).toHaveText(
'@settings(defaultLengthUnit = in)'
)
await page.locator('.cm-content').fill(`sketch001 = startSketchOn('XZ')
|> startProfileAt([-87.4, 282.92], %)
|> line(end = [324.07, 27.199], tag = $seg01)
|> line(end = [118.328, -291.754])

View File

@ -4,9 +4,9 @@ import path from 'path'
import * as fsp from 'fs/promises'
import {
getUtils,
executorInputPath,
TEST_COLORS,
TestColor,
executorInputPath,
orRunWhenFullSuiteEnabled,
} from './test-utils'
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from './storageStates'
@ -582,7 +582,7 @@ extrude002 = extrude(profile002, length = 150)
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('cylinder-inches.kcl'),
path.join(bracketDir, 'main.kcl')
)
})
@ -632,12 +632,13 @@ extrude002 = extrude(profile002, length = 150)
await test.step(`Load an empty file`, async () => {
await page.addInitScript(async () => {
localStorage.setItem('persistCode', '')
localStorage.setItem('persistCode', '@settings(defaultLengthUnit = in)')
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await u.waitForPageLoad()
await u.closeKclCodePanel()
await page.waitForTimeout(20)
})
await test.step(`Zoom out until you can't see the default planes`, async () => {
@ -646,13 +647,14 @@ extrude002 = extrude(profile002, length = 150)
timeout: 5000,
message: 'Plane color is visible',
})
.toBeLessThanOrEqual(15)
.toBeLessThanOrEqual(20)
let maxZoomOuts = 10
let middlePixelIsBackgroundColor =
(await middlePixelIsColor(bgColor)) < 10
while (!middlePixelIsBackgroundColor && maxZoomOuts > 0) {
await page.keyboard.down('Control')
await page.waitForTimeout(20)
await page.mouse.move(600, 460)
await page.mouse.down({ button: 'right' })
await page.mouse.move(600, 50, { steps: 20 })
@ -660,7 +662,7 @@ extrude002 = extrude(profile002, length = 150)
await page.keyboard.up('Control')
await page.waitForTimeout(100)
maxZoomOuts--
middlePixelIsBackgroundColor = (await middlePixelIsColor(bgColor)) < 10
middlePixelIsBackgroundColor = (await middlePixelIsColor(bgColor)) < 15
}
expect(middlePixelIsBackgroundColor, {
@ -678,13 +680,12 @@ extrude002 = extrude(profile002, length = 150)
homePage,
scene,
toolbar,
viewport,
}) => {
await context.folderSetupFn(async (dir) => {
const legoDir = path.join(dir, 'lego')
await fsp.mkdir(legoDir, { recursive: true })
await fsp.copyFile(
executorInputPath('lego.kcl'),
executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
path.join(legoDir, 'main.kcl')
)
})
@ -697,11 +698,8 @@ extrude002 = extrude(profile002, length = 150)
await scene.loadingIndicator.waitFor({ state: 'detached' })
})
await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => {
await scene.expectPixelColor(
[143, 143, 143],
{ x: (viewport?.width ?? 1200) / 2, y: (viewport?.height ?? 500) / 2 },
15
)
// TODO: use the viewport size to pick the center point, but the `viewport` fixutre's values were wrong.
await scene.expectPixelColor([143, 143, 143], { x: 500, y: 250 }, 15)
})
})

View File

@ -113,7 +113,8 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`sketch001 = startSketchOn(XZ)
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([2.61, -4.01], %)
|> xLine(length = 8.73)
|> tangentialArcTo([8.33, -1.31], %)`
@ -159,7 +160,10 @@ test.describe('Sketch tests', { tag: ['@skipWin'] }, () => {
await page.mouse.click(700, 200)
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
.toBe(`sketch002 = startSketchOn(XZ)
.toBe(`@settings(defaultLengthUnit = in)
sketch002 = startSketchOn(XZ)
sketch001 = startProfileAt([12.34, -12.34], sketch002)
|> yLine(length = 12.34)
@ -789,7 +793,8 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
200
)
let codeStr = 'sketch001 = startSketchOn(XY)'
let codeStr =
'@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XY)'
await page.mouse.click(center.x, viewportSize.height * 0.55)
await expect(u.codeLocator).toHaveText(codeStr)
@ -1426,7 +1431,8 @@ test.describe(`Sketching with offset planes`, () => {
await context.addInitScript(() => {
localStorage.setItem(
'persistCode',
`offsetPlane001 = offsetPlane(XY, offset = 10)`
`@settings(defaultLengthUnit = in)
offsetPlane001 = offsetPlane(XY, offset = 10)`
)
})
@ -1440,7 +1446,7 @@ test.describe(`Sketching with offset planes`, () => {
await test.step(`Hovering should highlight code`, async () => {
await planeHover()
await editor.expectState({
activeLines: [`offsetPlane001=offsetPlane(XY,offset=10)`],
activeLines: [`@settings(defaultLengthUnit = in)`],
diagnostics: [],
highlightedCode: 'offsetPlane(XY, offset = 10)',
})
@ -1453,7 +1459,7 @@ test.describe(`Sketching with offset planes`, () => {
await expect(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
await editor.expectState({
activeLines: [`offsetPlane001=offsetPlane(XY,offset=10)`],
activeLines: [`@settings(defaultLengthUnit = in)`],
diagnostics: [],
highlightedCode: '',
})
@ -2486,7 +2492,11 @@ extrude001 = extrude(profile003, length = 5)
page,
}) => {
await page.addInitScript(async () => {
localStorage.setItem('persistCode', `myVar = 5`)
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
myVar = 5`
)
})
await page.setBodyDimensions({ width: 1000, height: 500 })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

View File

@ -1,99 +0,0 @@
import {
runningOnLinux,
runningOnMac,
runningOnWindows,
orRunWhenFullSuiteEnabled,
} from './test-utils'
describe('platform detection utilities', () => {
const originalPlatform = process.platform
afterAll(() => {
Object.defineProperty(process, 'platform', {
value: originalPlatform,
})
})
describe('runningOnLinux', () => {
it('returns true on Linux', () => {
Object.defineProperty(process, 'platform', {
value: 'linux',
})
expect(runningOnLinux()).toBe(true)
})
it('returns false on other platforms', () => {
Object.defineProperty(process, 'platform', {
value: 'darwin',
})
expect(runningOnLinux()).toBe(false)
})
})
describe('runningOnMac', () => {
it('returns true on Mac', () => {
Object.defineProperty(process, 'platform', {
value: 'darwin',
})
expect(runningOnMac()).toBe(true)
})
it('returns false on other platforms', () => {
Object.defineProperty(process, 'platform', {
value: 'linux',
})
expect(runningOnMac()).toBe(false)
})
})
describe('runningOnWindows', () => {
it('returns true on Windows', () => {
Object.defineProperty(process, 'platform', {
value: 'win32',
})
expect(runningOnWindows()).toBe(true)
})
it('returns false on other platforms', () => {
Object.defineProperty(process, 'platform', {
value: 'linux',
})
expect(runningOnWindows()).toBe(false)
})
})
})
describe('utility to bypass unreliable tests', () => {
const originalEnv = { ...process.env }
afterAll(() => {
process.env = { ...originalEnv }
})
it('always runs them on dedicated branch', () => {
process.env.GITHUB_EVENT_NAME = 'push'
process.env.GITHUB_REF = 'refs/heads/all-e2e'
process.env.GITHUB_HEAD_REF = ''
process.env.GITHUB_BASE_REF = ''
const shouldSkip = orRunWhenFullSuiteEnabled()
expect(shouldSkip).toBe(false)
})
it('skips them on the main branch', () => {
process.env.GITHUB_EVENT_NAME = 'push'
process.env.GITHUB_REF = 'refs/heads/main'
process.env.GITHUB_HEAD_REF = ''
process.env.GITHUB_BASE_REF = ''
const shouldSkip = orRunWhenFullSuiteEnabled()
expect(shouldSkip).toBe(true)
})
it('skips them on pull requests', () => {
process.env.GITHUB_EVENT_NAME = 'pull_request'
process.env.GITHUB_REF = 'refs/pull/5883/merge'
process.env.GITHUB_HEAD_REF = 'my-branch'
process.env.GITHUB_BASE_REF = 'main'
const shouldSkip = orRunWhenFullSuiteEnabled()
expect(shouldSkip).toBe(true)
})
})

View File

@ -26,7 +26,6 @@ import { isArray } from 'lib/utils'
import { reportRejection } from 'lib/trap'
import { DeepPartial } from 'lib/types'
import { Configuration } from 'lang/wasm'
import { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
const toNormalizedCode = (text: string) => {
return text.replace(/\s+/g, '')
@ -56,21 +55,8 @@ export const commonPoints = {
export const editorSelector = '[role="textbox"][data-language="kcl"]'
type PaneId = 'variables' | 'code' | 'files' | 'logs'
export function runningOnLinux() {
return process.platform === 'linux'
}
export function runningOnMac() {
return process.platform === 'darwin'
}
export function runningOnWindows() {
return process.platform === 'win32'
}
export function orRunWhenFullSuiteEnabled() {
const branch = process.env.GITHUB_REF?.replace('refs/heads/', '')
return branch !== 'all-e2e'
return process.env.GITHUB_HEAD_REF !== 'all-e2e'
}
async function waitForPageLoadWithRetry(page: Page) {
@ -762,7 +748,7 @@ export interface Paths {
}
export const doExport = async (
output: Models['OutputFormat3d_type'],
output: Models['OutputFormat_type'],
rootDir: string,
page: Page,
exportFrom: 'dropdown' | 'sidebarButton' | 'commandBar' = 'dropdown'
@ -1126,15 +1112,3 @@ export function settingsToToml(settings: DeepPartial<Configuration>) {
export function tomlToSettings(toml: string): DeepPartial<Configuration> {
return TOML.parse(toml)
}
export function tomlToPerProjectSettings(
toml: string
): DeepPartial<ProjectConfiguration> {
return TOML.parse(toml)
}
export function perProjectsettingsToToml(
settings: DeepPartial<ProjectConfiguration>
) {
return TOML.stringify(settings as any)
}

View File

@ -4,7 +4,6 @@ import {
getUtils,
TEST_COLORS,
pollEditorLinesSelectedLength,
executorInputPath,
orRunWhenFullSuiteEnabled,
} from './test-utils'
import { XOR } from 'lib/utils'
@ -1117,9 +1116,19 @@ test.describe('Electron constraint tests', () => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'test-sample')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('angled_line.kcl'),
path.join(bracketDir, 'main.kcl')
await fsp.writeFile(
path.join(bracketDir, 'main.kcl'),
`@settings(defaultLengthUnit = in)
const part001 = startSketchOn(XY)
|> startProfileAt([4.83, 12.56], %)
|> line(end = [15.1, 2.48])
|> line(end = [3.15, -9.85], tag = $seg01)
|> line(end = [-15.17, -4.1])
|> angledLine([segAng(seg01), 12.35], %)
|> line(end = [-13.02, 10.03])
|> close()
|> extrude(length = 4)`,
'utf-8'
)
})

View File

@ -255,7 +255,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
await context.addInitScript(() => {
localStorage.setItem(
'persistCode',
`
`@settings(defaultLengthUnit = in)
const sketch002 = startSketchOn(XZ)
|> startProfileAt([-108.83, -57.48], %)
|> angledLine([0, 105.13], %, $rectangleSegmentA001)

View File

@ -271,7 +271,7 @@ test.describe('Testing settings', () => {
const bracketDir = join(dir, projectName)
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
executorInputPath('cylinder-inches.kcl'),
join(bracketDir, 'main.kcl')
)
}
@ -746,7 +746,6 @@ test.describe('Testing settings', () => {
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 15_000 })
await scene.settled(cmdBar)
await page.waitForTimeout(1000)
@ -951,9 +950,9 @@ test.describe('Testing settings', () => {
)
})
await test.step(`Initial units from settings`, async () => {
await test.step(`Initial units from settings are ignored`, async () => {
await homePage.openProject('project-000')
await expect(unitsIndicator).toHaveText('Current units are: in')
await expect(unitsIndicator).toHaveText('Current units are: mm')
})
await test.step(`Manually write inline settings`, async () => {

View File

@ -483,7 +483,8 @@ test('Sketch on face', async ({ page, homePage, scene, cmdBar, toolbar }) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`sketch001 = startSketchOn(XZ)
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
|> startProfileAt([3.29, 7.86], %)
|> line(end = [2.48, 2.44])
|> line(end = [2.66, 1.17])

23
interface.d.ts vendored
View File

@ -3,18 +3,6 @@ import fsSync from 'node:fs'
import path from 'path'
import { dialog, shell } from 'electron'
import { MachinesListing } from 'components/MachineManagerProvider'
import type { Channel } from 'src/menu/channels'
import { Menu, WebContents } from 'electron'
import { ZooLabel, ZooMenuEvents } from 'menu/roles'
import type { MenuActionIPC } from 'menu/rules'
import type { WebContentSendPayload } from 'menu/channels'
// Extend the interface with additional custom properties
declare module 'electron' {
interface Menu {
label?: ZooLabel
}
}
type EnvFn = (value?: string) => string
@ -56,9 +44,6 @@ export interface IElectronAPI {
rm: typeof fs.rm
stat: (path: string) => ReturnType<fs.stat>
statIsDirectory: (path: string) => Promise<boolean>
canReadWriteDirectory: (
path: string
) => Promise<{ value: boolean; error: unknown }>
path: typeof path
mkdir: typeof fs.mkdir
join: typeof path.join
@ -106,14 +91,6 @@ export interface IElectronAPI {
appCheckForUpdates: () => Promise<unknown>
getArgvParsed: () => any
getAppTestProperty: (propertyName: string) => any
// Helper functions to create application Menus
createHomePageMenu: () => Promise<any>
createModelingPageMenu: () => Promise<any>
createFallbackMenu: () => Promise<any>
enableMenu(menuId: string): Promise<any>
disableMenu(menuId: string): Promise<any>
menuOn: (callback: (payload: WebContentSendPayload) => void) => any
}
declare global {

View File

@ -26,7 +26,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.19",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "2.0.21",
"@kittycad/lib": "2.0.17",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.1",
"@react-hook/resize-observer": "^2.0.1",

View File

@ -75,14 +75,17 @@ LabeledArgument { ArgumentLabel Equals expression }
ArgumentList { "(" commaSep<LabeledArgument | expression> ")" }
type[@isGroup=Type] {
PrimitiveType { identifier } |
@specialize[@name=PrimitiveType]<
identifier,
"bool" | "number" | "string" | "tag" | "Sketch" | "SketchSurface" | "Solid" | "Plane"
> |
ArrayType { "[" type !member (";" Number "+"?)? "]" } |
ObjectType { "{" commaSep<ObjectProperty { PropertyName ":" type }> "}" }
}
VariableDefinition { identifier }
VariableName { identifier ("::" identifier)*}
VariableName { identifier }
ArgumentLabel { identifier }
@ -134,7 +137,7 @@ commaSep1NoTrailingComma<term> { term ("," term)* }
"(" ")"
"{" "}"
"[" "]"
"," "?" ":" "." ".." ";" "::"
"," "?" ":" "." ".." ";"
}
@external propSource kclHighlight from "./highlight"

View File

@ -18,9 +18,7 @@ const config = defineConfig({
environment: 'node',
reporters: process.env.GITHUB_ACTIONS
? ['dot', 'github-actions']
: // Gotcha: 'hanging-process' is very noisey, turn off by default on localhost
// : ['verbose', 'hanging-process'],
['verbose'],
: ['verbose', 'hanging-process'],
testTimeout: 1000,
hookTimeout: 1000,
teardownTimeout: 1000,

View File

@ -29,7 +29,7 @@
"vscode-uri": "^3.1.0"
},
"devDependencies": {
"@types/node": "^22.13.13",
"@types/node": "^22.13.10",
"ts-node": "^10.9.2"
}
}

View File

@ -116,10 +116,10 @@
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
"@types/node@^22.13.13":
version "22.13.13"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.13.tgz#5e7d110fb509b0d4a43fbf48fa9d6e0f83e1b1e7"
integrity sha512-ClsL5nMwKaBRwPcCvH8E7+nU4GxHVx1axNvMZTFHMEfNI7oahimt26P5zjVCRrjiIWj6YFXfE1v3dEp94wLcGQ==
"@types/node@^22.13.10":
version "22.13.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4"
integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==
dependencies:
undici-types "~6.20.0"

View File

@ -12,7 +12,6 @@ import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
timeout: 120_000, // override the default 30s timeout
testDir: './e2e/playwright',
testIgnore: '*.test.ts', // ignore unit tests
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */

View File

@ -7,7 +7,6 @@ import { platform } from 'os'
export default defineConfig({
timeout: 120_000, // override the default 30s timeout
testDir: './e2e/playwright',
testIgnore: '*.test.ts', // ignore unit tests
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */

View File

@ -7,7 +7,7 @@
// Define function
fn rail8020(originStart, railHeight, railLength) {
// Sketch side 1 of profile
sketch001 = startSketchOn(-XZ)
sketch001 = startSketchOn('-XZ')
|> startProfileAt([
originStart[0],
0.1 * railHeight + originStart[1]
@ -194,7 +194,7 @@ fn rail8020(originStart, railHeight, railLength) {
.5 * railHeight + originStart[0],
.5 * railHeight + originStart[1]
],
radius = .205 * railHeight / 2,
radius = .205 * railHeight / 2
), %)
|> extrude(length = railLength)
|> fillet(
@ -216,7 +216,7 @@ fn rail8020(originStart, railHeight, railLength) {
getNextAdjacentEdge(edge28),
getNextAdjacentEdge(edge29),
getNextAdjacentEdge(edge30)
],
]
)
|> fillet(
radius = 0.03,
@ -237,7 +237,7 @@ fn rail8020(originStart, railHeight, railLength) {
getNextAdjacentEdge(edge26),
getNextAdjacentEdge(edge31),
getNextAdjacentEdge(edge32)
],
]
)
return sketch001
}

View File

@ -15,37 +15,57 @@ padding = 1.5
bearingDia = 3
// (Needs to be updated). Sketch the block and extrude up to where the counterbore diameter starts.
extrude001 = startSketchOn(XY)
extrude001 = startSketchOn('XY')
|> startProfileAt([-width / 2, -length / 2], %)
|> line(endAbsolute = [width / 2, -length / 2])
|> line(endAbsolute = [width / 2, length / 2])
|> line(endAbsolute = [-width / 2, length / 2])
|> close()
|> extrude(length = height)
extrude002 = startSketchOn(extrude001, 'end')
|> circle(
center = [
center = [
-(width / 2 - (padding / 2)),
-(length / 2 - (padding / 2))
],
radius = cbDia / 2,
],
radius = cbDia / 2,
)
|> patternLinear2d(instances = 2, distance = length - padding, axis = [0, 1])
|> patternLinear2d(instances = 2, distance = width - padding, axis = [1, 0])
|> patternLinear2d(
instances = 2,
distance = length - padding,
axis = [0, 1],
)
|> patternLinear2d(
instances = 2,
distance = width - padding,
axis = [1, 0],
)
|> extrude(%, length = -cbDepth)
extrude003 = startSketchOn(extrude001, 'start')
|> circle(
center = [
-(width / 2 - (padding / 2)),
-(length / 2 - (padding / 2))
],
radius = holeDia / 2,
center = [
-(width / 2 - (padding / 2)),
-(length / 2 - (padding / 2))
],
radius = holeDia / 2,
)
|> patternLinear2d(instances = 2, distance = length - padding, axis = [0, 1])
|> patternLinear2d(instances = 2, distance = width - padding, axis = [1, 0])
|> patternLinear2d(
instances = 2,
distance = length - padding,
axis = [0, 1],
)
|> patternLinear2d(
instances = 2,
distance = width - padding,
axis = [1, 0],
)
|> extrude(length = -height + cbDepth)
extrude004 = startSketchOn(extrude001, 'end')
|> circle(center = [0, 0], radius = bearingDia / 2)
|> extrude(length = -height)
|> circle(
center = [0, 0],
radius = bearingDia/2,
)
|> extrude(length = -height)

View File

@ -17,15 +17,21 @@ chainThickness = sphereDia / 8
linkDiameter = sphereDia / 4
// Sketch the inside bearing piece
insideWallSketch = startSketchOn(offsetPlane(XY, offset = -overallThickness / 2))
|> circle(center = [0, 0], radius = shaftDia / 2 + wallThickness)
|> hole(circle(center = [0, 0], radius = shaftDia / 2), %)
insideWallSketch = startSketchOn(offsetPlane("XY", offset = -overallThickness / 2))
|> circle(
center = [0, 0],
radius = shaftDia / 2 + wallThickness
)
|> hole(circle(
center = [0, 0],
radius = shaftDia / 2
), %)
// Extrude the inside bearing piece
insideWall = extrude(insideWallSketch, length = overallThickness)
// Create the sketch of one of the balls
ballsSketch = startSketchOn(XY)
ballsSketch = startSketchOn("XY")
|> startProfileAt([shaftDia / 2 + wallThickness, 0.001], %)
|> arc({
angleEnd = 0,
@ -41,11 +47,11 @@ balls = revolve(ballsSketch, axis = "X")
axis = [0, 0, 1],
center = [0, 0, 0],
instances = nBalls,
rotateDuplicates = true,
rotateDuplicates = true
)
// Create the sketch for the chain around the balls
chainSketch = startSketchOn(XY)
chainSketch = startSketchOn("XY")
|> startProfileAt([
shaftDia / 2 + wallThickness + sphereDia / 2 - (chainWidth / 2),
0.125 * sin(toRadians(60))
@ -66,17 +72,17 @@ chainHead = revolve(chainSketch, axis = "X")
axis = [0, 0, 1],
center = [0, 0, 0],
instances = nBalls,
rotateDuplicates = true,
rotateDuplicates = true
)
// Create the sketch for the links in between the chains
linkSketch = startSketchOn(XZ)
linkSketch = startSketchOn("XZ")
|> circle(
center = [
shaftDia / 2 + wallThickness + sphereDia / 2,
0
],
radius = linkDiameter / 2,
radius = linkDiameter / 2
)
// Revolve the link sketch
@ -86,13 +92,19 @@ linkRevolve = revolve(linkSketch, axis = 'Y', angle = 360 / nBalls)
axis = [0, 0, 1],
center = [0, 0, 0],
instances = nBalls,
rotateDuplicates = true,
rotateDuplicates = true
)
// Create the sketch for the outside walls
outsideWallSketch = startSketchOn(offsetPlane(XY, offset = -overallThickness / 2))
|> circle(center = [0, 0], radius = outsideDiameter / 2)
|> hole(circle(center = [0, 0], radius = shaftDia / 2 + wallThickness + sphereDia), %)
outsideWallSketch = startSketchOn(offsetPlane("XY", offset = -overallThickness / 2))
|> circle(
center = [0, 0],
radius = outsideDiameter / 2
)
|> hole(circle(
center = [0, 0],
radius = shaftDia / 2 + wallThickness + sphereDia
), %)
outsideWall = extrude(outsideWallSketch, length = overallThickness)

View File

@ -130,7 +130,7 @@ fn armRestProfile(plane, offset) {
export fn armRest(plane, offset) {
path = armRestPath( offsetPlane(plane, offset = offset))
profile = armRestProfile( offsetPlane(-XZ, offset = 20), offset)
profile = armRestProfile( offsetPlane("-XZ", offset = 20), offset)
sweep(profile, path = path)
return 0
}

View File

@ -16,19 +16,19 @@ import backSlats from "bench-parts.kcl"
import armRest from "bench-parts.kcl"
// Create the dividers, these hold the seat and back slats
divider(YZ)
divider(offsetPlane(-YZ, offset = benchLength / 2))
divider(offsetPlane(YZ, offset = benchLength / 2))
divider("YZ")
divider(offsetPlane("-YZ", offset = benchLength / 2))
divider(offsetPlane("YZ", offset = benchLength / 2))
// Create the connectors to join the dividers
connector(offsetPlane(YZ, offset = -benchLength / 2), benchLength)
connector(offsetPlane("YZ", offset = -benchLength / 2), benchLength)
// Create the seat slats
seatSlats(offsetPlane(YZ, offset = -benchLength / 2 - (dividerThickness / 2)), benchLength + dividerThickness)
seatSlats(offsetPlane("YZ", offset = -benchLength / 2 - dividerThickness / 2), benchLength + dividerThickness)
// Create the back slats
backSlats(offsetPlane(YZ, offset = -benchLength / 2 - (dividerThickness / 2)), benchLength + dividerThickness)
backSlats(offsetPlane("YZ", offset = -benchLength / 2 - dividerThickness / 2), benchLength + dividerThickness)
// Create the arm rests
armRest(-YZ, benchLength / 2)
armRest(-YZ, -benchLength / 2)
armRest("-YZ", benchLength / 2)
armRest("-YZ", -benchLength / 2)

View File

@ -1,6 +1,7 @@
// Shelf Bracket
// This is a bracket that holds a shelf. It is made of aluminum and is designed to hold a force of 300 lbs. The bracket is 6 inches wide and the force is applied at the end of the shelf, 12 inches from the wall. The bracket has a factor of safety of 1.2. The legs of the bracket are 5 inches and 2 inches long. The thickness of the bracket is calculated from the constraints provided.
// Define constants
sigmaAllow = 35000 // psi (6061-T6 aluminum)
width = 6 // inch
@ -11,16 +12,14 @@ wallMountL = 2 // inches
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
// Calculate required thickness of bracket
thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
filletRadius = .25
extFilletRadius = filletRadius + thickness
mountingHoleDiameter = 0.5
sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLine(length = shelfMountL - thickness, tag = $seg01)
|> yLine(length = thickness, tag = $seg02)
@ -30,18 +29,48 @@ sketch001 = startSketchOn(XZ)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg06)
|> close()
|> extrude(%, length = width)
|> fillet(radius = extFilletRadius, tags = [getNextAdjacentEdge(seg03)])
|> fillet(radius = filletRadius, tags = [getNextAdjacentEdge(seg06)])
|> fillet(radius = filletRadius, tags = [seg02, getOppositeEdge(seg02)])
|> fillet(radius = filletRadius, tags = [seg05, getOppositeEdge(seg05)])
|> fillet(
radius = extFilletRadius,
tags = [getNextAdjacentEdge(seg03)],
)
|> fillet(
radius = filletRadius,
tags = [getNextAdjacentEdge(seg06)],
)
|> fillet(
radius = filletRadius,
tags = [seg02, getOppositeEdge(seg02)],
)
|> fillet(
radius = filletRadius,
tags = [seg05, getOppositeEdge(seg05)],
)
sketch002 = startSketchOn(sketch001, seg03)
|> circle(center = [-1.25, 1], radius = mountingHoleDiameter / 2)
|> patternLinear2d(instances = 2, distance = 2.5, axis = [-1, 0])
|> patternLinear2d(instances = 2, distance = 4, axis = [0, 1])
|> extrude(%, length = -thickness - .01)
|> circle(
center = [-1.25, 1],
radius = mountingHoleDiameter / 2,
)
|> patternLinear2d(
instances = 2,
distance = 2.5,
axis = [-1, 0],
)
|> patternLinear2d(
instances = 2,
distance = 4,
axis = [0, 1],
)
|> extrude(%, length = -thickness-.01)
sketch003 = startSketchOn(sketch001, seg04)
|> circle(center = [1, -1], radius = mountingHoleDiameter / 2)
|> patternLinear2d(instances = 2, distance = 4, axis = [1, 0])
|> extrude(%, length = -thickness - 0.1)
|> circle(
center = [1, -1],
radius = mountingHoleDiameter / 2,
)
|> patternLinear2d(
instances = 2,
distance = 4,
axis = [1, 0],
)
|> extrude(%, length = -thickness-0.1)

View File

@ -1,14 +1,16 @@
// Brake Caliper
// Brake calipers are used to squeeze the brake pads against the rotor, causing larger and larger amounts of friction depending on how hard the brakes are pressed.
// Set units
@settings(defaultLengthUnit = in)
// Import Constants
import caliperTolerance, caliperPadLength, caliperThickness, caliperOuterEdgeRadius, caliperInnerEdgeRadius, rotorDiameter, rotorTotalThickness, yAxisOffset from "globals.kcl"
// Sketch the brake caliper profile
brakeCaliperSketch = startSketchOn(XY)
brakeCaliperSketch = startSketchOn('XY')
|> startProfileAt([
rotorDiameter / 2 + caliperTolerance,
0

View File

@ -1,28 +1,39 @@
// Wheel rotor
// A component of a disc brake system. It provides a surface for brake pads to press against, generating the friction needed to slow or stop the vehicle.
// Set units
@settings(defaultLengthUnit = in)
// Import Constants
import rotorDiameter, rotorInnerDiameter, rotorSinglePlateThickness, rotorInnerDiameterThickness, lugHolePatternDia, lugSpacing, rotorTotalThickness, spacerPatternDiameter, spacerDiameter, spacerLength, spacerCount, wheelDiameter, lugCount, yAxisOffset, drillAndSlotCount from "globals.kcl"
rotorSketch = startSketchOn(XZ)
|> circle(center = [0, 0], radius = rotorDiameter / 2)
rotorSketch = startSketchOn('XZ')
|> circle(
center = [0, 0],
radius = rotorDiameter / 2
)
rotor = extrude(rotorSketch, length = rotorSinglePlateThickness)
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
rotorBumpSketch = startSketchOn(rotor, 'end')
|> circle(center = [0, 0], radius = rotorInnerDiameter / 2)
|> circle(
center = [0, 0],
radius = rotorInnerDiameter / 2
)
rotorBump = extrude(rotorBumpSketch, length = rotorInnerDiameterThickness)
lugHoles = startSketchOn(rotorBump, 'end')
|> circle(center = [-lugSpacing / 2, 0], radius = 0.315)
|> circle(
center = [-lugSpacing / 2, 0],
radius = 0.315
)
|> patternCircular2d(
arcDegrees = 360,
center = [0, 0],
instances = lugCount,
rotateDuplicates = true,
rotateDuplicates = true
)
|> extrude(%, length = -(rotorInnerDiameterThickness + rotorSinglePlateThickness))
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
@ -33,26 +44,35 @@ centerSpacer = startSketchOn(rotor, 'start')
|> extrude(%, length = spacerLength)
secondaryRotorSketch = startSketchOn(centerSpacer, 'end')
|> circle(center = [0, 0], radius = rotorDiameter / 2)
|> circle(
center = [0, 0],
radius = rotorDiameter / 2
)
secondRotor = extrude(secondaryRotorSketch, length = rotorSinglePlateThickness)
lugHoles2 = startSketchOn(secondRotor, 'end')
|> circle(center = [-lugSpacing / 2, 0], radius = 0.315)
|> circle(
center = [-lugSpacing / 2, 0],
radius = 0.315
)
|> patternCircular2d(
arcDegrees = 360,
center = [0, 0],
instances = lugCount,
rotateDuplicates = true,
rotateDuplicates = true
)
|> extrude(length = -rotorSinglePlateThickness)
spacerSketch = startSketchOn(rotor, 'start')
|> circle(center = [spacerPatternDiameter / 2, 0], radius = spacerDiameter)
|> circle(
center = [spacerPatternDiameter / 2, 0],
radius = spacerDiameter
)
|> patternCircular2d(
arcDegrees = 360,
center = [0, 0],
instances = spacerCount,
rotateDuplicates = true,
rotateDuplicates = true
)
spacers = extrude(spacerSketch, length = spacerLength)
@ -67,7 +87,7 @@ rotorSlottedSketch = startSketchOn(rotor, 'START')
center = [0, 0],
instances = drillAndSlotCount,
arcDegrees = 360,
rotateDuplicates = true,
rotateDuplicates = true
)
rotorSlotted = extrude(rotorSlottedSketch, length = -rotorSinglePlateThickness / 2)
@ -82,7 +102,7 @@ secondRotorSlottedSketch = startSketchOn(secondRotor, 'END')
center = [0, 0],
instances = drillAndSlotCount,
arcDegrees = 360,
rotateDuplicates = true,
rotateDuplicates = true
)
extrude(secondRotorSlottedSketch, length = -rotorSinglePlateThickness / 2)

View File

@ -1,22 +1,21 @@
// Tire
// A tire is a critical component of a vehicle that provides the necessary traction and grip between the car and the road. It supports the vehicle's weight and absorbs shocks from road irregularities.
// Set units
@settings(defaultLengthUnit = in)
// Import Constants
import tireInnerDiameter, tireOuterDiameter, tireDepth, bendRadius, tireTreadWidth, tireTreadDepth, tireTreadOffset from "globals.kcl"
// Create the sketch of the tire
tireSketch = startSketchOn(XY)
tireSketch = startSketchOn("XY")
|> startProfileAt([tireInnerDiameter / 2, tireDepth / 2], %)
|> line(
endAbsolute = [
tireOuterDiameter / 2 - bendRadius,
tireDepth / 2
],
tag = $edge1,
)
|> line(endAbsolute = [
tireOuterDiameter / 2 - bendRadius,
tireDepth / 2
], tag = $edge1)
|> tangentialArc({ offset = -90, radius = bendRadius }, %)
|> line(endAbsolute = [
tireOuterDiameter / 2,

View File

@ -1,49 +1,69 @@
// Car Wheel
// A sports car wheel with a circular lug pattern and spokes.
// Set units
@settings(defaultLengthUnit = in)
// Import Constants
import lugCount, lugSpacing, offset, backSpacing, wheelWidth, wheelDiameter, spokeCount, spokeGap, spokeAngle, spokeThickness from "globals.kcl"
// Create the wheel center
lugBase = startSketchOn(XZ)
|> circle(center = [0, 0], radius = (lugSpacing + 1.5) / 2)
|> hole(circle(center = [0, 0], radius = (lugSpacing - 1.5) / 2), %)
lugBase = startSketchOn('XZ')
|> circle(
center = [0, 0],
radius = (lugSpacing + 1.5) / 2
)
|> hole(circle(
center = [0, 0],
radius = (lugSpacing - 1.5) / 2
), %)
|> extrude(length = wheelWidth / 20)
// Extend the wheel center and bore holes to accomidate the lug heads
lugExtrusion = startSketchOn(lugBase, 'END')
|> circle(center = [0, 0], radius = (lugSpacing + 1.5) / 2)
|> hole(circle(center = [0, 0], radius = (lugSpacing - 1.5) / 2), %)
|> circle(
center = [0, 0],
radius = (lugSpacing + 1.5) / 2
)
|> hole(circle(
center = [0, 0],
radius = (lugSpacing - 1.5) / 2
), %)
|> extrude(length = wheelWidth / 10)
// Create the circular pattern for the lugs
lugClearance = startSketchOn(lugExtrusion, 'END')
|> circle(center = [lugSpacing / 2, 0], radius = 1.2 / 2)
|> circle(
center = [lugSpacing / 2, 0],
radius = 1.2 / 2
)
|> patternCircular2d(
arcDegrees = 360,
center = [0, 0],
instances = lugCount,
rotateDuplicates = true,
rotateDuplicates = true
)
|> extrude(length = -wheelWidth / 10)
// Create the circular pattern for the lug holes
lugHoles = startSketchOn(lugBase, 'END')
|> circle(center = [lugSpacing / 2, 0], radius = 16 * mm() / 2)
|> circle(
center = [lugSpacing / 2, 0],
radius = 16 * mm() / 2
)
|> patternCircular2d(
arcDegrees = 360,
center = [0, 0],
instances = lugCount,
rotateDuplicates = true,
rotateDuplicates = true
)
|> extrude(length = -wheelWidth / 20)
|> appearance(color = "#ffffff", metalness = 0, roughness = 0)
// Add detail to the wheel center by revolving curved edge profiles
wheelCenterInner = startSketchOn(XY)
wheelCenterInner = startSketchOn('XY')
|> startProfileAt([(lugSpacing - 1.5) / 2, 0], %)
|> yLine(length = -wheelWidth / 10 - (wheelWidth / 20))
|> bezierCurve({
@ -57,7 +77,7 @@ wheelCenterInner = startSketchOn(XY)
|> revolve(axis = 'y')
|> appearance(color = "#ffffff", metalness = 0, roughness = 0)
wheelCenterOuter = startSketchOn(XY)
wheelCenterOuter = startSketchOn('XY')
|> startProfileAt([(lugSpacing + 1.5) / 2, 0], %)
|> yLine(length = -wheelWidth / 10 - (wheelWidth / 20))
|> bezierCurve({
@ -125,7 +145,7 @@ fn spoke(spokeGap, spokeAngle, spokeThickness) {
center = [0, -2000, 0],
instances = spokeCount,
arcDegrees = 360,
rotateDuplicates = true,
rotateDuplicates = true
)
|> appearance(color = "#ffffff", metalness = 0, roughness = 0)
return spokePattern
@ -135,7 +155,7 @@ spoke(spokeGap, spokeAngle, spokeThickness)
spoke(-spokeGap, -spokeAngle, -spokeThickness)
// Define and revolve wheel exterior
startSketchOn(XY)
startSketchOn('XY')
|> startProfileAt([
wheelDiameter / 2,
-wheelWidth + backSpacing + offset

View File

@ -20,7 +20,7 @@ export lugDiameter = 24 * mm()
export lugHeadLength = lugDiameter * .5
export lugThreadDiameter = lugDiameter / 2 * .85
export lugLength = 30 * mm()
export lugThreadDepth = lugLength - (12.7 * mm())
export lugThreadDepth = lugLength - 12.7 * mm()
// Car Rotor
export rotorDiameter = 12
@ -50,4 +50,4 @@ export caliperTolerance = 0.050
export caliperPadLength = 1.6
export caliperThickness = 0.39
export caliperOuterEdgeRadius = 0.39
export caliperInnerEdgeRadius = 0.12
export caliperInnerEdgeRadius = 0.12

View File

@ -1,9 +1,11 @@
// Lug Nut
// lug Nuts are essential components used to create secure connections, whether for electrical purposes, like terminating wires or grounding, or for mechanical purposes, such as providing mounting points or reinforcing structural joints.
// Set units
@settings(defaultLengthUnit = in)
// Import Constants
import lugDiameter, lugHeadLength, lugThreadDiameter, lugLength, lugThreadDepth, lugSpacing from "globals.kcl"

View File

@ -4,24 +4,24 @@
// Set units
@settings(defaultLengthUnit = in)
import "car-wheel.kcl" as carWheel
import "car-rotor.kcl" as carRotor
import 'car-wheel.kcl' as carWheel
import 'car-rotor.kcl' as carRotor
import "brake-caliper.kcl" as brakeCaliper
import "lug-nut.kcl" as lugNut
import "car-tire.kcl" as carTire
import lugCount from "globals.kcl"
import 'lug-nut.kcl' as lugNut
import 'car-tire.kcl' as carTire
import lugCount from 'globals.kcl'
carRotor
|> translate(translate = [0, 0.5, 0])
carWheel
lugNut
|> patternCircular3d(
arcDegrees = 360,
axis = [0, 1, 0],
center = [0, 0, 0],
instances = lugCount,
rotateDuplicates = false,
)
arcDegrees = 360,
axis = [0, 1, 0],
center = [0, 0, 0],
instances = lugCount,
rotateDuplicates = false
)
brakeCaliper
|> translate(translate = [0, 0.5, 0])
carTire

View File

@ -6,42 +6,42 @@
// Globals referenced in drawRectangle
size = 100
halfSize = size / 2
halfSize = size/2
extrudeLength = 1.0
metalConstant = 50
roughnessConstant = 50
// Create planes for 6 sides of a cube
bluePlane = offsetPlane(XY, offset = halfSize)
yellowPlane = offsetPlane(XY, offset = -halfSize)
greenPlane = offsetPlane(XZ, offset = -halfSize)
purplePlane = offsetPlane(-XZ, offset = -halfSize)
redPlane = offsetPlane(YZ, offset = halfSize - extrudeLength)
tealPlane = offsetPlane(YZ, offset = -halfSize)
bluePlane = offsetPlane('XY', offset = halfSize)
yellowPlane = offsetPlane('XY', offset = -halfSize)
greenPlane = offsetPlane('XZ', offset = -halfSize)
purplePlane = offsetPlane('-XZ', offset = -halfSize)
redPlane = offsetPlane('YZ', offset = halfSize - extrudeLength)
tealPlane = offsetPlane('YZ', offset = -halfSize)
// Sketch a rectangle centered at the origin of the profile
fn sketchRectangle(profile, color) {
fn sketchRectangle (profile, color) {
return profile
|> startProfileAt([-halfSize, halfSize], %)
|> angledLine([0, size], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
size
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> extrude(%, length = extrudeLength)
|> appearance(color = color, metalness = metalConstant, roughness = roughnessConstant)
|> startProfileAt([-halfSize, halfSize], %)
|> angledLine([0, size], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
size
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> extrude(%, length = extrudeLength)
|> appearance(color = color, metalness = metalConstant, roughness = roughnessConstant)
}
// Sketch each side of the cube
sketchRectangle(bluePlane, '#0000FF')
sketchRectangle(yellowPlane, '#FFFF00')
sketchRectangle(greenPlane, '#00FF00')
sketchRectangle(redPlane, '#FF0000')
sketchRectangle(tealPlane, '#00FFFF')
sketchRectangle(purplePlane, '#FF00FF')
sketchRectangle(bluePlane,'#0000FF')
sketchRectangle(yellowPlane,'#FFFF00')
sketchRectangle(greenPlane,'#00FF00')
sketchRectangle(redPlane,'#FF0000')
sketchRectangle(tealPlane,'#00FFFF')
sketchRectangle(purplePlane,'#FF00FF')

View File

@ -4,11 +4,12 @@
// Set Units
@settings(defaultLengthUnit = in)
fn cycloidalGear(gearPitch, gearHeight, holeDiameter, helixAngle) {
// Create a function to draw the gear profile as a sketch. Rotate each profile about the gear's axis by an helix angle proportional to the total gear height
fn gearSketch(gHeight) {
helixAngleP = helixAngle * gHeight / gearHeight
gearProfile = startSketchOn(offsetPlane(XY, offset = gHeight))
gearProfile = startSketchOn(offsetPlane("XY", offset = gHeight))
|> startProfileAt([
gearPitch * 1.55 * cos(toRadians(helixAngleP)) + gearPitch * sin(toRadians(-helixAngleP)),
gearPitch * 1.55 * sin(toRadians(helixAngleP)) + gearPitch * cos(toRadians(-helixAngleP))
@ -30,7 +31,10 @@ fn cycloidalGear(gearPitch, gearHeight, holeDiameter, helixAngle) {
|> tangentialArc({ radius = gearPitch, offset = -180 }, %)
|> tangentialArcTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> hole(circle(center = [0, 0], radius = holeDiameter / 2), %)
|> hole(circle(
center = [0, 0],
radius = holeDiameter / 2
), %)
return gearProfile
}

View File

@ -39,7 +39,7 @@ plane = {
}
// Create a regular pentagon inscribed in a circle of radius pentR
bottomFace = startSketchOn(XY)
bottomFace = startSketchOn('XY')
|> polygon({
radius = pentR,
numSides = 5,
@ -66,7 +66,7 @@ bottomBowl = patternCircular3d(
axis = [0, 0, 1],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = true,
rotateDuplicates = true
)
// pattern the bottom to create the top face
@ -76,7 +76,7 @@ patternCircular3d(
axis = [0, 1, 0],
center = [0, 0, inscR],
arcDegrees = 360,
rotateDuplicates = true,
rotateDuplicates = true
)
// pattern the bottom angled faces to create the top
@ -86,5 +86,5 @@ patternCircular3d(
axis = [0, 1, 0],
center = [0, 0, inscR],
arcDegrees = 360,
rotateDuplicates = true,
rotateDuplicates = true
)

View File

@ -11,7 +11,7 @@ wallThickness = 3
holeDia = 4
// Model a box with base enclosure dimensions
sketch001 = startSketchOn(XY)
sketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> angledLine([0, width], %, $rectangleSegmentA001)
|> angledLine([
@ -32,11 +32,14 @@ extrude001 = extrude(sketch001, length = height)
getNextAdjacentEdge(rectangleSegmentB001),
getNextAdjacentEdge(rectangleSegmentC001),
getNextAdjacentEdge(rectangleSegmentD001)
],
]
)
// Apply a shell to the enclosure base to create the internal storage
|> shell(faces = ["end"], thickness = wallThickness)
|> shell(
faces = ["end"],
thickness = wallThickness
)
// Define a function to create the internal structure to secure a fastener at each corner
fn function001(originStart) {
@ -52,8 +55,14 @@ fn function001(originStart) {
// Create a pillar with a fasterner hole at the center
sketch002 = startSketchOn(plane001)
|> circle(center = [originStart[0], originStart[1]], radius = holeDia + wallThickness)
|> hole(circle(center = [originStart[0], originStart[1]], radius = holeDia), %)
|> circle(
center = [originStart[0], originStart[1]],
radius = holeDia + wallThickness
)
|> hole(circle(
center = [originStart[0], originStart[1]],
radius = holeDia
), %)
extrude002 = extrude(sketch002, length = height - wallThickness)
return extrude002
@ -78,7 +87,7 @@ function001([
])
// Define lid position and outer surface
sketch003 = startSketchOn(XY)
sketch003 = startSketchOn('XY')
|> startProfileAt([width * 1.2, 0], %)
|> angledLine([0, width], %, $rectangleSegmentA002)
|> angledLine([
@ -96,28 +105,28 @@ sketch003 = startSketchOn(XY)
width * 1.2 + wallThickness * 3 + holeDia,
wallThickness * 3 + holeDia
],
radius = holeDia,
radius = holeDia
), %)
|> hole(circle(
center = [
width * 1.2 + wallThickness * 3 + holeDia,
length - (wallThickness * 3 + holeDia)
],
radius = holeDia,
radius = holeDia
), %)
|> hole(circle(
center = [
width * 2.2 - (wallThickness * 3 + holeDia),
wallThickness * 3 + holeDia
],
radius = holeDia,
radius = holeDia
), %)
|> hole(circle(
center = [
width * 2.2 - (wallThickness * 3 + holeDia),
length - (wallThickness * 3 + holeDia)
],
radius = holeDia,
radius = holeDia
), %)
extrude003 = extrude(sketch003, length = wallThickness)
|> fillet(
@ -127,7 +136,7 @@ extrude003 = extrude(sketch003, length = wallThickness)
getNextAdjacentEdge(rectangleSegmentB002),
getNextAdjacentEdge(rectangleSegmentC002),
getNextAdjacentEdge(rectangleSegmentD002)
],
]
)
// Define lid inner and sealing surfaces
@ -152,28 +161,28 @@ sketch004 = startSketchOn(extrude003, 'END')
width * 1.2 + wallThickness * 3 + holeDia,
wallThickness * 3 + holeDia
],
radius = holeDia + wallThickness,
radius = holeDia + wallThickness
), %)
|> hole(circle(
center = [
width * 1.2 + wallThickness * 3 + holeDia,
length - (wallThickness * 3 + holeDia)
],
radius = holeDia + wallThickness,
radius = holeDia + wallThickness
), %)
|> hole(circle(
center = [
width * 2.2 - (wallThickness * 3 + holeDia),
wallThickness * 3 + holeDia
],
radius = holeDia + wallThickness,
radius = holeDia + wallThickness
), %)
|> hole(circle(
center = [
width * 2.2 - (wallThickness * 3 + holeDia),
length - (wallThickness * 3 + holeDia)
],
radius = holeDia + wallThickness,
radius = holeDia + wallThickness
), %)
extrude004 = extrude(sketch004, length = wallThickness)
|> fillet(
@ -183,5 +192,5 @@ extrude004 = extrude(sketch004, length = wallThickness)
getNextAdjacentEdge(rectangleSegmentB003),
getNextAdjacentEdge(rectangleSegmentC003),
getNextAdjacentEdge(rectangleSegmentD003)
],
]
)

View File

@ -45,9 +45,15 @@ fn primaryTube(n, angle001, length001, length002, length003) {
}, %)
// Create the cross section of each tube and sweep them
sweepProfile = startSketchOn(XY)
|> circle(center = [pos001, 0], radius = primaryTubeDiameter / 2)
|> hole(circle(center = [pos001, 0], radius = primaryTubeDiameter / 2 - wallThickness), %)
sweepProfile = startSketchOn('XY')
|> circle(
center = [pos001, 0],
radius = primaryTubeDiameter / 2
)
|> hole(circle(
center = [pos001, 0],
radius = primaryTubeDiameter / 2 - wallThickness
), %)
|> sweep(path = sweepPath)
return { }
@ -60,7 +66,7 @@ primaryTube(2, 24.3, 5, 5, 3)
primaryTube(3, 25.2, 5, 5, 3)
// Create the mounting flange for the header
flangeSketch = startSketchOn(XY)
flangeSketch = startSketchOn('XY')
|> startProfileAt([3 + 1.3, -1.25], %)
|> xLine(length = -2.6, tag = $seg01)
|> tangentialArc({ radius = .3, offset = -40 }, %)
@ -81,10 +87,22 @@ flangeSketch = startSketchOn(XY)
|> close()
// Create openings in the flange to accommodate each tube
|> hole(circle(center = [0, 0], radius = primaryTubeDiameter / 2 - wallThickness), %)
|> hole(circle(center = [2, 0], radius = primaryTubeDiameter / 2 - wallThickness), %)
|> hole(circle(center = [4, 0], radius = primaryTubeDiameter / 2 - wallThickness), %)
|> hole(circle(center = [6, 0], radius = primaryTubeDiameter / 2 - wallThickness), %)
|> hole(circle(
center = [0, 0],
radius = primaryTubeDiameter / 2 - wallThickness
), %)
|> hole(circle(
center = [2, 0],
radius = primaryTubeDiameter / 2 - wallThickness
), %)
|> hole(circle(
center = [4, 0],
radius = primaryTubeDiameter / 2 - wallThickness
), %)
|> hole(circle(
center = [6, 0],
radius = primaryTubeDiameter / 2 - wallThickness
), %)
// Add mounting holes to the flange
|> hole(circle(
@ -92,28 +110,28 @@ flangeSketch = startSketchOn(XY)
-primaryTubeDiameter * .6,
-primaryTubeDiameter * .6
],
radius = 0.25 / 2,
radius = 0.25 / 2
), %)
|> hole(circle(
center = [
primaryTubeDiameter * .6,
primaryTubeDiameter * .6
],
radius = 0.25 / 2,
radius = 0.25 / 2
), %)
|> hole(circle(
center = [
3 * 2 - (primaryTubeDiameter * .6),
primaryTubeDiameter * .6
],
radius = 0.25 / 2,
radius = 0.25 / 2
), %)
|> hole(circle(
center = [
3 * 2 + primaryTubeDiameter * .6,
-primaryTubeDiameter * .6
],
radius = 0.25 / 2,
radius = 0.25 / 2
), %)
// Extrude the flange and fillet the edges
@ -123,12 +141,12 @@ flangeSketch = startSketchOn(XY)
tags = [
getNextAdjacentEdge(seg04),
getNextAdjacentEdge(seg07)
],
]
)
|> fillet(
radius = .25,
tags = [
getNextAdjacentEdge(seg03),
getNextAdjacentEdge(seg08)
],
]
)

View File

@ -21,32 +21,44 @@ nHoles = 4
assertGreaterThan(nHoles, 1, "nHoles must be greater than 1")
// Create the circular pattern for the mounting holes
circles = startSketchOn(XY)
|> circle(center = [mountingHolePlacementDiameter / 2, 0], radius = mountingHoleDia / 2)
circles = startSketchOn('XY')
|> circle(
center = [mountingHolePlacementDiameter / 2, 0],
radius = mountingHoleDia / 2
)
|> patternCircular2d(
arcDegrees = 360,
center = [0, 0],
instances = nHoles,
rotateDuplicates = true,
rotateDuplicates = true
)
// Create the base of the flange and add the mounting holes
flangeBase = startSketchOn(XY)
|> circle(center = [0, 0], radius = baseDia / 2)
flangeBase = startSketchOn('XY')
|> circle(
center = [0, 0],
radius = baseDia / 2
)
|> hole(circles, %)
|> extrude(length = baseThickness)
// Create the extrusion on the top of the flange base
topExtrusion = startSketchOn(flangeBase, 'end')
|> circle(center = [0, 0], radius = topTotalDiameter / 2)
|> circle(
center = [0, 0],
radius = topTotalDiameter / 2
)
|> extrude(length = topTotalThickness)
// Create the extrusion on the bottom of the flange base
bottomExtrusion = startSketchOn(flangeBase, 'start')
|> circle(center = [0, 0], radius = bottomTotalDiameter / 2)
|> circle(
center = [0, 0],
radius = bottomTotalDiameter / 2
)
|> extrude(length = bottomThickness)
// Cut a hole through the entire body
pipeHole = startSketchOn(topExtrusion, 'end')
|> circle(center = [0, 0], radius = pipeDia / 2)
|> circle(center = [0, 0], radius = pipeDia/2)
|> extrude(%, length = -(topTotalThickness + baseThickness + bottomThickness))

View File

@ -62,7 +62,7 @@ bracketBody = bs
getPreviousAdjacentEdge(bs.tags.edge2),
getPreviousAdjacentEdge(bs.tags.edge3),
getPreviousAdjacentEdge(bs.tags.edge6)
],
]
)
// define the tab plane
@ -87,7 +87,7 @@ tabsR = startSketchOn(tabPlane)
width / 2 + thk + tabWidth / 2,
length / 2 + thk - (tabLength / (3 / 2))
],
radius = holeDiam / 2,
radius = holeDiam / 2
), %)
|> extrude(length = -tabThk)
|> fillet(
@ -95,9 +95,13 @@ tabsR = startSketchOn(tabPlane)
tags = [
getNextAdjacentEdge(edge11),
getNextAdjacentEdge(edge12)
],
]
)
|> patternLinear3d(
axis = [0, -1, 0],
instances = 2,
distance = length + 2 * thk - (tabLength * 4 / 3)
)
|> patternLinear3d(axis = [0, -1, 0], instances = 2, distance = length + 2 * thk - (tabLength * 4 / 3))
// build the tabs of the mounting bracket (left side)
tabsL = startSketchOn(tabPlane)
@ -111,7 +115,7 @@ tabsL = startSketchOn(tabPlane)
-width / 2 - thk - (tabWidth / 2),
length / 2 + thk - (tabLength / (3 / 2))
],
radius = holeDiam / 2,
radius = holeDiam / 2
), %)
|> extrude(length = -tabThk)
|> fillet(
@ -119,9 +123,13 @@ tabsL = startSketchOn(tabPlane)
tags = [
getNextAdjacentEdge(edge21),
getNextAdjacentEdge(edge22)
],
]
)
|> patternLinear3d(
axis = [0, -1, 0],
instances = 2,
distance = length + 2 * thk - (tabLength * 4 / 3)
)
|> patternLinear3d(axis = [0, -1, 0], instances = 2, distance = length + 2 * thk - (tabLength * 4 / 3))
// define a plane for retention bumps
retPlane = {

View File

@ -44,7 +44,7 @@ fn slot(sketch1, start, end, width) {
}
// create a sketch on the "XY" plane
sketch000 = startSketchOn(XY)
sketch000 = startSketchOn('XY')
// create a profile of the flipper
flipperProfile = startProfileAt([-flipperLength, -32.0], sketch000)
@ -83,11 +83,11 @@ fillet(
tags = [
getNextAdjacentEdge(backEdge),
getPreviousAdjacentEdge(backEdge)
],
]
)
// create a sketch on the "XZ" plane offset by half the thickness
sketch001 = startSketchOn(offsetPlane(XZ, offset = -handleWidth / 2))
sketch001 = startSketchOn(offsetPlane("XZ", offset = -handleWidth / 2))
// create a profile of the spatula handle
handleProfile = startProfileAt([0.0, flipperThickness], sketch001)
@ -109,7 +109,7 @@ fillet(
tags = [
getNextAdjacentEdge(handleBottomEdge),
getNextAdjacentEdge(handleTopEdge)
],
]
)
// define a plane which is at the end of the handle
@ -163,4 +163,4 @@ sketch003 = startSketchOn(grip, gripEdgeTop)
gripHoleProfile = slot(sketch003, [0, 200], [0, 210], gripSlotWidth)
// cut a hole in the grip
extrude(gripHoleProfile, length = -gripWidth - 20)
extrude(gripHoleProfile, length = -gripWidth-20)

View File

@ -10,7 +10,7 @@ carafeHeight = 7.32
handleThickness = 0.65
// Upper ring of the metal structure
sketch001 = startSketchOn(XZ)
sketch001 = startSketchOn('XZ')
|> startProfileAt([carafeDiameter / 2, 5.7], %)
|> angledLine([0, 0.1], %, $rectangleSegmentA001)
|> angledLine([
@ -91,12 +91,15 @@ sketch002 = startSketchOn(plane001)
center = [0, 0, 0],
instances = 4,
arcDegrees = 360,
rotateDuplicates = true,
rotateDuplicates = true
)
// Cross plate
sketch003 = startSketchOn(offsetPlane(XY, offset = 1))
|> circle(center = [0, 0], radius = carafeDiameter / 2 - 0.15)
sketch003 = startSketchOn(offsetPlane('XY', offset = 1))
|> circle(
center = [0, 0],
radius = carafeDiameter / 2 - 0.15
)
extrude001 = extrude(sketch003, length = 0.050)
@ -114,13 +117,13 @@ sketch004 = startSketchOn(extrude001, 'END')
center = [0, 0],
instances = 3,
arcDegrees = 360,
rotateDuplicates = true,
rotateDuplicates = true
)
extrude002 = extrude(sketch004, length = -0.050)
// Filter screen
sketch005 = startSketchOn(XZ)
sketch005 = startSketchOn('XZ')
|> startProfileAt([0.15, 1.11], %)
|> xLine(endAbsolute = carafeDiameter / 2 - 0.2)
|> angledLineToX({
@ -135,7 +138,7 @@ sketch005 = startSketchOn(XZ)
|> revolve(axis = 'y')
// Plunger and stem
sketch006 = startSketchOn(XZ)
sketch006 = startSketchOn('XZ')
|> startProfileAt([0.1, 1], %)
|> line(end = [0.1, 0])
|> angledLineToX({ angle = 10, to = 0.05 }, %)
@ -148,8 +151,11 @@ sketch006 = startSketchOn(XZ)
|> revolve(axis = 'y')
// Spiral plate
sketch007 = startSketchOn(offsetPlane(XY, offset = 1.12))
|> circle(center = [0, 0], radius = carafeDiameter / 2 - 0.24)
sketch007 = startSketchOn(offsetPlane('XY', offset = 1.12))
|> circle(
center = [0, 0],
radius = carafeDiameter / 2 - 0.24
)
|> hole(circle(center = [0, 0], radius = .15), %)
extrude003 = extrude(sketch007, length = 0.050)
@ -161,7 +167,7 @@ sketch008 = startSketchOn(extrude003, 'END')
center = [0, 0],
instances = 8,
arcDegrees = 360,
rotateDuplicates = true,
rotateDuplicates = true
)
extrude004 = extrude(sketch008, length = -0.050)
@ -173,21 +179,24 @@ sketch009 = startSketchOn(extrude003, 'END')
center = [0, 0],
instances = 4,
arcDegrees = 360,
rotateDuplicates = true,
rotateDuplicates = true
)
extrude005 = extrude(sketch009, length = -0.050)
// Extrude a glass carafe body
sketch010 = startSketchOn(XY)
|> circle(center = [0, 0], radius = carafeDiameter / 2)
sketch010 = startSketchOn("XY")
|> circle(
center = [0, 0],
radius = carafeDiameter / 2
)
// Perform a shell operation to hollow the carafe body with the top face removed
extrude006 = extrude(sketch010, length = carafeHeight)
|> shell(faces = ["end"], thickness = .07)
// Draw and revolve the lid
sketch011 = startSketchOn(XZ)
sketch011 = startSketchOn('XZ')
|> startProfileAt([0.2, carafeHeight - 0.7], %)
|> xLine(length = carafeDiameter / 2 - 0.3)
|> yLine(length = 0.7)
@ -204,7 +213,7 @@ sketch011 = startSketchOn(XZ)
|> revolve(axis = 'y')
// Draw and extrude handle
sketch012 = startSketchOn(offsetPlane(XZ, offset = handleThickness / 2))
sketch012 = startSketchOn(offsetPlane('XZ', offset = handleThickness / 2))
|> startProfileAt([2.3, 6.4], %)
|> line(end = [0.56, 0])
|> tangentialArcTo([4.1, 5.26], %)

View File

@ -12,7 +12,7 @@ height = 12
minHeight = 10.875
// Create the body of the rack
rackBody = startSketchOn(XY)
rackBody = startSketchOn('XY')
|> startProfileAt([-length / 2, 0], %)
|> line(end = [length, 0])
|> line(end = [0, minHeight])
@ -22,7 +22,7 @@ rackBody = startSketchOn(XY)
// Create a function for sketch of a single tooth
fn tooth() {
toothSketch = startSketchOn(XY)
toothSketch = startSketchOn('XY')
|> startProfileAt([-length / 2 + 0.567672, minHeight], %)
|> tangentialArcToRelative([0.157636, 0.110378], %)
|> line(end = [0.329118, 0.904244])
@ -38,10 +38,14 @@ fn tooth() {
// Pattern the single tooth over the length of the rack body
teeth = tooth()
|> patternLinear3d(axis = [10, 0, 0], distance = 1.570796, instances = 63)
|> patternLinear3d(
axis = [10, 0, 0],
distance = 1.570796,
instances = 63
)
// Sketch and extrude the first end cap. This is a partial tooth
endCapTooth = startSketchOn(XY)
endCapTooth = startSketchOn('XY')
|> startProfileAt([-length / 2, 11.849525], %)
|> line(end = [0.314524, -0.864147])
|> tangentialArcToRelative([0.157636, -0.110378], %)
@ -50,7 +54,7 @@ endCapTooth = startSketchOn(XY)
|> extrude(length = width)
// Sketch and extrude the second end cap. This is a partial tooth
endCapTooth2 = startSketchOn(XY)
endCapTooth2 = startSketchOn('XY')
|> startProfileAt([length / 2, 11.849525], %)
|> line(end = [-0.314524, -0.864147])
|> tangentialArcToRelative([-0.157636, -0.110378], %)

View File

@ -17,32 +17,35 @@ gearHeight = 3
// Interpolate points along the involute curve
cmo = 101
rs = map([0..cmo], fn(i) {
rs = map([0..cmo], fn (i) {
return baseDiameter / 2 + i / cmo * (tipDiameter - baseDiameter) / 2
})
// Calculate operating pressure angle
angles = map(rs, fn(r) {
angles = map(rs, fn (r) {
return toDegrees( acos(baseDiameter / 2 / r))
})
// Calculate the involute function
invas = map(angles, fn(a) {
invas = map(angles, fn (a) {
return tan(toRadians(a)) - toRadians(a)
})
// Map the involute curve
xs = map([0..cmo], fn(i) {
xs = map([0..cmo], fn (i) {
return rs[i] * cos(invas[i])
})
ys = map([0..cmo], fn(i) {
ys = map([0..cmo], fn (i) {
return rs[i] * sin(invas[i])
})
// Extrude the gear body
body = startSketchOn(XY)
|> circle(center = [0, 0], radius = baseDiameter / 2)
body = startSketchOn('XY')
|> circle(
center = [0, 0],
radius = baseDiameter / 2
)
|> extrude(length = gearHeight)
toothAngle = 360 / nTeeth / 1.5
@ -60,7 +63,7 @@ fn rightInvolute(i, sg) {
}
// Draw gear teeth
start = startSketchOn(XY)
start = startSketchOn('XY')
|> startProfileAt([xs[101], ys[101]], %)
teeth = reduce([0..100], start, leftInvolute)
|> arc({
@ -76,7 +79,7 @@ teeth = reduce([0..100], start, leftInvolute)
center = [0, 0, 0],
instances = nTeeth,
arcDegrees = 360,
rotateDuplicates = true,
rotateDuplicates = true
)
// Define the constants of the keyway and the bore hole

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