Compare commits

...

62 Commits

Author SHA1 Message Date
86ae7a989f initial go
Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

better

Signed-off-by: Jess Frazelle <github@jessfraz.com>

typo

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-14 19:09:38 -07:00
334145f0be fix sketch on face of union (#6949)
* fix sketch on face of union

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* rotate the model

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-14 23:25:12 +00:00
c24073b6ae Sensible parser error when using keyword as arg label (#6948)
Closes <https://github.com/KittyCAD/modeling-app/issues/6924>
2025-05-14 22:29:31 +00:00
078b7f3bf7 fix errors from the wasm side (#6939)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* playwright tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* change order of operations

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* lint

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* Revert "fixups"

This reverts commit c54fd71074.

* fixups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-14 13:20:46 -07:00
3d65676ccb Permit concurrent unit tests (#6938)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-15 07:43:03 +12:00
ce566fb6e5 Accept idents as KW args (#6644)
Support kw arg/local variable shorthand

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-15 07:42:48 +12:00
b23fc9f623 Make the Reset View button do the same view_isometric behavior as load (#6934)
* Make the Reset View button do the same view_isometric behavior as load

Just copying some logic from the EngineStream code to make that button
behave the same way: old initial camera position while in Playwright,
isometric view for normal users.

* Move duplicate code into shared `resetCameraPosition` function

* Fix lints
2025-05-14 19:01:45 +00:00
5c2dfb8e40 Support new sweep flag (#6932)
Closes https://github.com/KittyCAD/engine/issues/3115
2025-05-14 13:54:10 -05:00
0e341d7863 #6202 Save input value before closing settings dialogue (#6931)
* call blur on current input before closing settings dialogue to save value

* separate esc handling is not needed

* lint
2025-05-14 14:16:23 -04:00
6a03ff9596 Stop checking for intermediate export toasts (#6935) 2025-05-14 17:53:44 +00:00
d7bd0c937d Keep test toast messages around for longer (#6930)
* Keep test toast messages around for longer

* Check for at least two locators

I wasn't able to reproduce, but it's possible one stuck around from a previous test.
2025-05-14 12:24:38 -04:00
d3b2483f4f Clear errors when leaving file to avoid seeing previous files errors (#6928)
Clear errors when leaving file to avoid seeing previous files errors when opening new project

Co-authored-by: Jace Browning <jacebrowning@gmail.com>
2025-05-14 17:20:04 +02:00
7838b7c9fd Fix "include settings" setting to have an effect (#6917)
* Fix "include settings" setting to have an effect

I'm not sold on if we should have this setting, but this fixes it for
now. The issue is was that the new callback actor approach was using a
stale version of the settings every time it received an "update" event:
JS closure problems. Now it receives the new settings as an event
payload.

* Update src/machines/settingsMachine.ts

---------

Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-05-14 15:14:33 +00:00
130ecf1f88 Shard macOS and Windows e2e tests (#6926) 2025-05-14 14:33:53 +00:00
550d8b3753 #6300 Fix wrong Feature Tree when switching to kcl file with errors (#6922)
* reset lastSuccessfulOperation, variables when switching to new kcl file, add test

* use scene.settled instead of random delay in test
2025-05-14 14:17:22 +00:00
696222a070 Change KCL error messages to display principal type of values (#6906) 2025-05-14 10:04:51 -04:00
edb424988d Restore the native file menu tests (#6279)
* Restore the native file menu tests

* fix: saving off progress

* chore: making progress cleaning up these verbose tests and improving app logic for e2e

* chore: rewriting tests

* fix: reworking application logic for file menu in the scene and e2e scene file menu test

* chore: updating more e2e tests

* fix: updated all the tests, auto fixers

* fix: trying to improve tests within E2E, they aren't failing locally even with --repeat-each=10

* fix: application logic has a bug that you can navigate instantly but the scroll to view code will not trigger which breaks end to end tests

* fix: improving E2E tests

* fix: fixing clipboard typo

* fix: porting test() for each native file menu to a test.step to speed it up

* fix: auto fixes and console log helper function for playwright runtimes

* fix: more cleanup

* fix: trying to fix these...

* fix: got the tests working

* fix: addressing PR comments

* fix: trying to stablize the tests

* fix: auto fixes

* fix: trying to make it the command name and not arg? could be a source of race condition if the input is not written fast enough?

* fix: maybe because this close locator was running too quickly?

* fix: panic timeout, classic

* fix: these are gone

* fix: shorter waits

---------

Co-authored-by: Kevin Nadro <kevin@zoo.dev>
Co-authored-by: Kevin Nadro <nadr0@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-05-14 09:06:29 -04:00
1e487ef3bd Include NodePath in artifact graph mermaid charts as comments (#6884)
* Display NodePath in artifact graph mermaid charts

* Update output

* Change node path display to be only comments

* Update output

* Update output after rebase
2025-05-14 05:31:58 +00:00
811ef3e72d fix pw test i turned off (#6921)
updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-14 04:36:52 +00:00
980e3c4bc2 remove my sins (#6919)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-14 04:28:55 +00:00
78b6854c6b bump modeling-cmds, nuke slow world (#6753)
* bump modeling-cmds, nuke slow world

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* more stuffs

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* i mechanical engineered today

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* reverse uno your revolves

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* retry logic

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-14 04:07:24 +00:00
068b9129cf add a sleep for local engine disconnect/reconnect (#6918)
updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-13 16:12:37 -07:00
05fba3c50c more stable shards (#6916)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-13 15:27:18 -07:00
7944a4ce41 New single-file samples for the website (#6670)
* New single-file samples for the website

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Delete public/kcl-samples/piston directory

* More

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* move another test

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2025-05-13 15:05:57 -07:00
4640f1a3ad Teaching t2c how to counterbore, countersink, and counterdrill (#6833)
* Teaching t2c how to counterbore, countersink, and counterdrill

* Delete public/kcl-samples/parametric-bearing-pillow-block directory

* Update mounting-wire.kcl

* new artifiacts

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-05-13 14:11:50 -07:00
cd79059d97 Subtract tests (#6913)
* add subtract test and cleanup some other tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fmt

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-13 14:06:10 -07:00
2d95e19048 Gears DLC (free) (#6835)
* Gears DLC (free)

Using the involuteCircular fn to create a variety of cylindrical gearsets

* Delete public/kcl-samples/gear directory

* Update main.kcl

fixing gear meshing

* new samples

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add link

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2025-05-13 14:03:52 -07:00
d047587bc1 Avoid using full revolve for now (#6912)
Avoid using revolve for now

When we moved to concurrent execution of KCL modules, we begun to see an
error we never fully understood, and because it was pretty hard to
trigger, we wound up never being able to fix it. Today we were able to
track it down to the `revolve` call here.

Specifically, the problem is triggered when we're doing a "Full Revolve"
(e.g., `angle = 359.999999` passes, but *not* `angle = 360` or the
default, as it is in `main`), and concurrently executing modules will
see something weird happen with `getNextAdjacentEdge`.

From all the smoke I believe this happens only when we are doing a *full
revolve*, *AND* we're executing other modules which are calling
`getNextAdjacentEdge`.

When the `revolve` is present, we can lose the race in *either*
`talk-button.kcl` OR `case.kcl`.

If I move back to single-threaded execution OR I add imports to sequence
things carefully, I can get the tests to pass. If the revolve is an
`extrude` or not a full revolve, it works fine.

My best guess is that it seems like the world got flipped upside down or
something, such that "next edge" has a different orentation for two
calls. My even further guess is that inside `revolve` we mutate
something connection-global such that it alters the intepretation of
calls made during the revolve implementation's "critical section".
2025-05-13 20:20:48 +00:00
128e1093fb Update snaps (#6911)
* update pattern entity ids since that pr merged

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* artifacts

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-13 19:27:34 +00:00
afdc305e3e Update new onboarding after team feedback (#6903)
* Update onboarding following @jgomez720

* Fix up TODO's in desktop conclusion (ty Ian)
2025-05-13 14:52:19 -04:00
4bc5439996 Prevent concurrent run of build-apps on main (#6909) 2025-05-13 18:20:27 +00:00
be14022f97 Update samples PNG export (#6908) 2025-05-13 18:03:27 +00:00
eda78ef5ae Fix test by closing code pane after loading the scene (#6907) 2025-05-13 18:03:16 +00:00
0cc833e687 Keep toast message around longer when testing (#6902) 2025-05-13 13:59:52 -04:00
33e83747f3 Split up example tests into smaller batches and provide info on which example is failing (#6896)
* Give example info for failing std example tests

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Shard example tests into 10

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-13 10:50:54 -07:00
c3a8fc6d93 Remove unneeded check on the network health text label (#6905) 2025-05-13 13:46:09 -04:00
8ff84e269c Update bug icon to not use FontAwesome (#6904)
lol we had a custom bug icon in the codebase, no clue why I didn't
update the pane config.
2025-05-13 13:45:07 -04:00
99cb6a6179 [Fix] Created the safest navigate/load/open/please sir i want some more for a .kcl file (#6867)
* fix: clear scene and bust cache if rust panics

* Update onboarding following @jgomez720

* chore: hopefully made a safe navigate to kcl file to call executeAST without a race condition

* chore: hopefully made a safe navigate to kcl file to call executeAST without a race condition

* fix: clean up

* fix: FUCK

* fix: FUCK 2.0

* fix: oh boi

* fix: oh boi

* fix: idk man

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix: take main on this, do not need a single line from my testing code

* fix: more PR cleanup from all of the testing code

* fix: trying to clean up more, ope this has a lot of other code

* fix: PR clean up

* fix: trying to get a clean branch, I had multiple other branches in here ope

* fix: more cleanup

* fix: another one

* fix: fixed the comment to be accurate

* fix: removed confusing comment

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2025-05-13 15:35:29 +00:00
13dbfdfaa4 SystemIOMachine: Toast default error instead of empty string (#6898)
pierremtb/issue6820-add-error-instead-of-empty-string
2025-05-13 10:28:24 -05:00
8603f5c53a Default to the feature tree, code, and file panes open (#6901)
Only include the file pane on desktop. This should encourage users to
try point-and-click and feature tree workflows. After #6629 is
completed, we should default to the code pane being closed.
2025-05-13 15:25:27 +00:00
d5cc9e8386 Check both public and internal KCL samples (#6889) 2025-05-13 09:21:53 -04:00
533e17466b Update text-to-cad success toast to tell what file got made (#6882)
Closes #5820 and provides a bit of other polish to make this toast look
a bit better.
2025-05-13 12:26:29 +00:00
01c7b69f50 Function types (#6891)
* Change Fn to fn for function types

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Support args and return types in function types

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Use fancy function types in the docs

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-13 14:59:23 +12:00
47feae3bd9 Register modeling commands as disabled, enable without needing to re-open the command palette (#6886)
* Allow adding and removing commands from any command bar state

* Allow commands to be configured disabled in the combobox

* Set up modeling commands to toggle `disabled` based on network status, instead of filtering

* Fix tsc
2025-05-13 01:18:50 +00:00
e7ecd655c4 Add docs on arrays and ranges (#6890)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-13 13:06:07 +12:00
d9e538c6ea Fix docs for object properties (#6892)
Jon removed support for obj["a"] a while ago in #6529 aka
2e754f2a11
2025-05-13 00:56:50 +00:00
e297f8286f KCL: Fix incorrect error messages (#6883)
**Problem:**

KCL's xLine and yLine functions were telling users to supply either "end" or "endAbsolute" arguments. But "end" is not a valid argument for xLine/yLine, it's actually "length" instead.

**Cause:**

xLine/yLine were using a helper function designed for `line` KCL stdlib functions, which do use `end`. 

**Solution**

Add a new param to the helper function, indicating what the label should be for relative lines. "end" for `line` calls, `length` for x/yline
2025-05-13 00:26:43 +00:00
b3bc90bbe4 Fix lower right version background in dark mode and update snapshots (#6888)
* pierremtb/adhoc/fix-version-bg-dark-mode

* Update snapshots
2025-05-12 23:46:52 +00:00
95a02cbcd7 Check all nested KCL samples (#6880) 2025-05-12 18:43:51 -04:00
a049768f1c Move some more functions to be declared in KCL (#6856)
* Move the leg functions to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Move array functions to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Move clone to KCL

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Add a function type

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-13 08:29:38 +12:00
818d9a0d77 Release link and updater toast touchups on Nightly (#6881)
pierremtb/adhoc/clean-up-nightly-on-merge-more
2025-05-12 20:20:12 +00:00
1a8f80a7dc #6793 Fix file, folder renaming issues (#6869)
* fix going into rename mode for files with parents

* lastDirectoryClicked is not used

* fix file/folder rename bugs: renaming within folders

* Turn form into div to fix issue of child renaming continues into renaming the parent folder when hitting enter

* ContextMenu stopPropagation not needed anymore, maybe because of form refactor

* ContextMenu IS needed actually, with multiple nested folders

* make lint happy

* Update src/components/ContextMenu.tsx

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

* re-add <form> instead of <div> for file renaming

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2025-05-12 21:59:10 +02:00
566c9eaf10 Spell out shortcut in project search bar (#6872)
dumb little zero-thought bit of polish.
2025-05-12 19:36:05 +00:00
e6485c2da1 Clean up Nightly release version & notes (#6877)
* pierremtb/adhoc/clean-up-nightly-on-merge

* To revert: test to upload builds at test/nightly

* Remove + suffix trim that's not needed anymore

* Revert "To revert: test to upload builds at test/nightly"

This reverts commit b0549d426f.
2025-05-12 14:21:09 -04:00
0479edd36a Remove unused GitHub Actions workflow for old onboarding (#6873) 2025-05-12 17:30:51 +00:00
87c1e92134 Deploy Nightly on merge to main (#6871)
* Deploy Nightly on merge to main

* Forgot one block

* Remove .github/workflows/tag-nightly.yml

* Remove scripts/get-nightly-changelog.sh

* Update .github/workflows/build-apps.yml

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-12 12:30:01 -04:00
8f950ac1b0 Always run end-to-end tests (#6870) 2025-05-12 12:23:09 -04:00
78e4f43708 Fix kcl errors indicator not scaling with multi-digit count (#6864)
Fixes #6847
2025-05-12 11:36:09 -04:00
270436f404 Fix: clearSceneAndBustCache when rust panics because it can load a stale cache (#6809)
* fix: clear scene and bust cache if rust panics

* fix: pr clean up

* fix: huh? cargo fmt doesn't do this?
2025-05-12 10:08:54 -05:00
57e61632a9 Fix to remove extraneous from package.json (#6868) 2025-05-12 14:50:44 +00:00
884191b8bb Bump kcl (#6865) 2025-05-12 14:18:55 +00:00
21b92f5f13 fix cache multi-file (#6844)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* bump kittycad.rs i need this for cli

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* bump the version so i can fix cli

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* clippy

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-12 09:07:18 -05:00
733 changed files with 237571 additions and 534253 deletions

View File

@ -7,14 +7,13 @@ on:
- main
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'nightly-v[0-9]+.[0-9]+.[0-9]+'
env:
IS_RELEASE: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'v') }}
IS_NIGHTLY: ${{ github.ref_type == 'tag' && startsWith(github.ref_name, 'nightly-v') }}
IS_NIGHTLY: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
@ -95,7 +94,9 @@ jobs:
- name: Set nightly version, product name, release notes, and icons
if: ${{ env.IS_NIGHTLY == 'true' }}
run: |
export VERSION=${GITHUB_REF_NAME#nightly-v}
COMMIT=$(git rev-parse --short HEAD)
DATE=$(date +'%-y.%-m.%-d')
export VERSION=$DATE-main.$COMMIT
npm run files:set-version
npm run files:flip-to-nightly
@ -306,7 +307,8 @@ jobs:
runs-on: ubuntu-22.04
permissions:
contents: write
if: ${{ github.ref_type == 'tag' }}
# Equivalent to IS_RELEASE || IS_NIGHTLY (but we can't access those env vars here)
if: ${{ (github.ref_type == 'tag' && startsWith(github.ref_name, 'v')) || (github.event_name == 'push' && github.ref == 'refs/heads/main') }}
env:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
VERSION: ${{ format('v{0}', needs.prepare-files.outputs.version) }}
@ -412,17 +414,6 @@ jobs:
- name: List artifacts
run: "ls -R out"
- name: Set more complete nightly release notes
if: ${{ env.IS_NIGHTLY == 'true' }}
run: |
# Note: preferred going this way instead of a full clone in the checkout step,
# see https://github.com/actions/checkout/issues/1471
git fetch --prune --unshallow --tags
export TAG="nightly-${VERSION}"
export PREVIOUS_TAG=$(git tag --list --sort=-committerdate "nightly-v[0-9]*" | head -n2 | tail -n1)
export NOTES=$(./scripts/get-nightly-changelog.sh)
npm run files:set-notes
- name: Authenticate to Google Cloud
if: ${{ env.IS_NIGHTLY == 'true' }}
uses: 'google-github-actions/auth@v2.1.8'

View File

@ -1,47 +0,0 @@
name: Check Onboarding KCL
on:
pull_request:
types: [opened, synchronize]
paths:
- 'src/lib/exampleKcl.ts'
- 'public/kcl-samples/bracket/main.kcl'
permissions:
contents: read
issues: write
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Comment on PR
uses: actions/github-script@v7
with:
script: |
const message = '`public/kcl-samples/bracket/main.kcl` or `src/lib/exampleKcl.ts` has been updated in this PR, please review and update the `src/routes/onboarding`, if needed.';
const issue_number = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number
});
const commentExists = comments.some(comment => comment.body === message);
if (!commentExists) {
// Post a comment on the PR
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: message,
});
}

View File

@ -18,73 +18,13 @@ permissions:
jobs:
conditions:
runs-on: ubuntu-latest
outputs:
significant: ${{ steps.path-changes.outputs.significant }}
should-run: ${{ steps.should-run.outputs.should-run }}
steps:
- uses: actions/checkout@v4
- name: Fetch the base branch
if: ${{ github.event_name == 'pull_request' }}
run: git fetch origin ${{ github.base_ref }} --depth=1
- name: Check for path changes
id: path-changes
shell: bash
run: |
set -euo pipefail
# Manual runs or push should run all tests.
if [[ ${{ github.event_name }} != 'pull_request' ]]; then
echo "significant=true" >> $GITHUB_OUTPUT
exit 0
fi
changed_files=$(git diff --name-only origin/${{ github.base_ref }})
echo "$changed_files"
if grep -Evq '^README.md|^public/kcl-samples/|^rust/kcl-lib/tests/kcl_samples/' <<< "$changed_files" ; then
echo "significant=true" >> $GITHUB_OUTPUT
else
echo "significant=false" >> $GITHUB_OUTPUT
fi
- name: Should run
id: should-run
shell: bash
run: |
set -euo pipefail
# We should run when this is a scheduled run or if there are
# significant changes in the diff.
if [[ ${{ github.event_name }} == 'schedule' || ${{ steps.path-changes.outputs.significant }} == 'true' ]]; then
echo "should-run=true" >> $GITHUB_OUTPUT
else
echo "should-run=false" >> $GITHUB_OUTPUT
fi
- name: Display conditions
shell: bash
run: |
# For debugging purposes
set -euo pipefail
echo "GITHUB_REF: $GITHUB_REF"
echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF"
echo "GITHUB_BASE_REF: $GITHUB_BASE_REF"
echo "significant: ${{ steps.path-changes.outputs.significant }}"
echo "should-run: ${{ steps.should-run.outputs.should-run }}"
prepare-wasm:
# separate job on Ubuntu to build or fetch the wasm blob once on the fastest runner
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
needs: conditions
steps:
- uses: actions/checkout@v4
if: needs.conditions.outputs.should-run == 'true'
- id: filter
if: needs.conditions.outputs.should-run == 'true'
name: Check for Rust changes
uses: dorny/paths-filter@v3
with:
@ -93,18 +33,16 @@ jobs:
- 'rust/**'
- uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install dependencies
if: needs.conditions.outputs.should-run == 'true'
run: npm install
- name: Download Wasm Cache
id: download-wasm
if: ${{ needs.conditions.outputs.should-run == 'true' && github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
if: ${{ github.event_name != 'schedule' && steps.filter.outputs.rust == 'false' }}
uses: dawidd6/action-download-artifact@v7
continue-on-error: true
with:
@ -116,7 +54,6 @@ jobs:
- name: Build WASM condition
id: wasm
if: needs.conditions.outputs.should-run == 'true'
run: |
set -euox pipefail
# Build wasm if this is a scheduled run, there are Rust changes, or
@ -128,35 +65,34 @@ jobs:
fi
- name: Use correct Rust toolchain
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
cache: false # Configured below.
- uses: taiki-e/install-action@d4635f2de61c8b8104d59cd4aede2060638378cc
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
with:
tool: wasm-pack
- name: Rust Cache
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- name: Build Wasm
if: ${{ needs.conditions.outputs.should-run == 'true' && steps.wasm.outputs.should-build-wasm == 'true' }}
if: ${{ steps.wasm.outputs.should-build-wasm == 'true' }}
shell: bash
run: npm run build:wasm
- uses: actions/upload-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
with:
name: prepared-wasm
path: |
@ -165,10 +101,9 @@ jobs:
snapshots:
name: playwright:snapshots:ubuntu
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
needs: [conditions, prepare-wasm]
needs: [prepare-wasm]
steps:
- uses: actions/create-github-app-token@v1
if: needs.conditions.outputs.should-run == 'true'
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
@ -176,16 +111,13 @@ jobs:
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
if: needs.conditions.outputs.should-run == 'true'
with:
token: ${{ steps.app-token.outputs.token }}
- uses: actions/download-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
name: prepared-wasm
- name: Copy prepared wasm
if: needs.conditions.outputs.should-run == 'true'
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
@ -193,18 +125,15 @@ jobs:
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install dependencies
id: deps-install
if: needs.conditions.outputs.should-run == 'true'
run: npm install
- name: Cache Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
uses: actions/cache@v4
with:
path: |
@ -212,15 +141,12 @@ jobs:
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- name: Install Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
run: npm run playwright install --with-deps
- name: build web
if: needs.conditions.outputs.should-run == 'true'
run: npm run tronb:vite:dev
- name: Run ubuntu/chrome snapshots
if: needs.conditions.outputs.should-run == 'true'
uses: nick-fields/retry@v3.0.2
with:
shell: bash
@ -236,7 +162,7 @@ jobs:
TARGET: web
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
if: ${{ !cancelled() && (success() || failure()) }}
with:
name: playwright-report-ubuntu-snapshot-${{ github.sha }}
path: playwright-report/
@ -245,7 +171,7 @@ jobs:
overwrite: true
- name: Check for changes
if: ${{ needs.conditions.outputs.should-run == 'true' && github.ref != 'refs/heads/main' }}
if: ${{ github.ref != 'refs/heads/main' }}
shell: bash
id: git-check
run: |
@ -257,7 +183,7 @@ jobs:
- name: Commit changes, if any
# TODO: find a more reliable way to detect visual changes
if: ${{ false && needs.conditions.outputs.should-run == 'true' && steps.git-check.outputs.modified == 'true' }}
if: ${{ false && steps.git-check.outputs.modified == 'true' }}
shell: bash
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
@ -272,7 +198,7 @@ jobs:
git push origin ${{ github.head_ref }}
electron:
needs: [conditions, prepare-wasm]
needs: [prepare-wasm]
timeout-minutes: 60
env:
OS_NAME: ${{ contains(matrix.os, 'ubuntu') && 'ubuntu' || (contains(matrix.os, 'windows') && 'windows' || 'macos') }}
@ -308,21 +234,24 @@ jobs:
shardTotal: 8
- os: namespace-profile-macos-8-cores
shardIndex: 1
shardTotal: 1
shardTotal: 2
- os: namespace-profile-macos-8-cores
shardIndex: 2
shardTotal: 2
- os: windows-latest-8-cores
shardIndex: 1
shardTotal: 1
shardTotal: 2
- os: windows-latest-8-cores
shardIndex: 2
shardTotal: 2
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
if: needs.conditions.outputs.should-run == 'true'
- uses: actions/download-artifact@v4
if: needs.conditions.outputs.should-run == 'true'
name: prepared-wasm
- name: Copy prepared wasm
if: needs.conditions.outputs.should-run == 'true'
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
@ -330,18 +259,15 @@ jobs:
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- uses: actions/setup-node@v4
if: needs.conditions.outputs.should-run == 'true'
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install dependencies
id: deps-install
if: needs.conditions.outputs.should-run == 'true'
run: npm install
- name: Cache Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
uses: actions/cache@v4
with:
path: |
@ -349,22 +275,20 @@ jobs:
key: ${{ runner.os }}-playwright-${{ hashFiles('package-lock.json') }}
- name: Install Playwright Browsers
if: needs.conditions.outputs.should-run == 'true'
run: npm run playwright install --with-deps
- name: Build web
if: needs.conditions.outputs.should-run == 'true'
run: npm run tronb:vite:dev
- name: Start Vector
if: ${{ needs.conditions.outputs.should-run == 'true' && !contains(matrix.os, 'windows') }}
if: ${{ !contains(matrix.os, 'windows') }}
run: .github/ci-cd-scripts/start-vector-${{ env.OS_NAME }}.sh
env:
GH_ACTIONS_AXIOM_TOKEN: ${{ secrets.GH_ACTIONS_AXIOM_TOKEN }}
OS_NAME: ${{ env.OS_NAME }}
- uses: actions/download-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && (success() || failure()) }}
if: ${{ !cancelled() && (success() || failure()) }}
continue-on-error: true
with:
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
@ -372,7 +296,7 @@ jobs:
- name: Run playwright/electron flow (with retries)
id: retry
if: ${{ needs.conditions.outputs.should-run == 'true' && !cancelled() && steps.deps-install.outcome == 'success' }}
if: ${{ !cancelled() && steps.deps-install.outcome == 'success' }}
uses: nick-fields/retry@v3.0.2
with:
shell: bash
@ -389,7 +313,7 @@ jobs:
TARGET: desktop
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
if: always()
with:
name: test-results-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: test-results/
@ -398,7 +322,7 @@ jobs:
overwrite: true
- uses: actions/upload-artifact@v4
if: ${{ needs.conditions.outputs.should-run == 'true' && always() }}
if: always()
with:
name: playwright-report-${{ env.OS_NAME }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: playwright-report/

View File

@ -213,7 +213,13 @@ jobs:
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: npm run playwright install chromium --with-deps
- name: run unit tests for kcl samples
- name: Download internal KCL samples
run: git clone --depth=1 https://x-access-token:${{ secrets.GH_PAT_KCL_SAMPLES_INTERNAL }}@github.com/KittyCAD/kcl-samples-internal public/kcl-samples/internal
- name: Regenerate KCL samples manifest
run: cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest
- name: Check public and internal KCL samples
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
run: npm run test:unit:kcl-samples
env:

View File

@ -1,39 +0,0 @@
name: tag-nightly
permissions:
contents: write
on:
schedule:
- cron: '0 4 * * *'
# Daily at 04:00 AM UTC
# Will checkout the last commit from the default branch (main as of 2023-10-04)
jobs:
tag-nightly:
runs-on: ubuntu-22.04
steps:
- uses: actions/create-github-app-token@v1
id: app-token
with:
app-id: ${{ secrets.MODELING_APP_GH_APP_ID }}
private-key: ${{ secrets.MODELING_APP_GH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
with:
token: ${{ steps.app-token.outputs.token }}
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
- run: npm install
- name: Push tag
run: |
VERSION_NO_V=$(date +'%-y.%-m.%-d')
TAG="nightly-v$VERSION_NO_V"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git tag $TAG
git push origin tag $TAG

View File

@ -23,6 +23,7 @@ endif
install: node_modules/.package-lock.json $(CARGO) $(WASM_PACK) ## Install dependencies
node_modules/.package-lock.json: package.json package-lock.json
npm prune
npm install
$(CARGO):
@ -43,15 +44,15 @@ endif
# BUILD
CARGO_SOURCES := rust/.cargo/config.toml $(wildcard rust/Cargo.*) $(wildcard rust/**/Cargo.*)
KCL_SOURCES := $(wildcard public/kcl-samples/**/*.kcl)
RUST_SOURCES := $(wildcard rust/**/*.rs)
REACT_SOURCES := $(wildcard src/*.tsx) $(wildcard src/**/*.tsx)
TYPESCRIPT_SOURCES := tsconfig.* $(wildcard src/*.ts) $(wildcard src/**/*.ts)
VITE_SOURCES := $(wildcard vite.*) $(wildcard vite/**/*.tsx)
.PHONY: build
build: install public/kcl_wasm_lib_bg.wasm .vite/build/main.js
build: install public/kcl_wasm_lib_bg.wasm public/kcl-samples/manifest.json .vite/build/main.js
public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES) $(RUST_SOURCES)
ifdef WINDOWS
@ -60,6 +61,9 @@ else
npm run build:wasm:dev
endif
public/kcl-samples/manifest.json: $(KCL_SOURCES)
cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest
.vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
npm run tronb:vite:dev
@ -107,8 +111,10 @@ test: test-unit test-e2e
.PHONY: test-unit
test-unit: install ## Run the unit tests
npm run test:rust
npm run test:unit:components
@ curl -fs localhost:3000 >/dev/null || ( echo "Error: localhost:3000 not available, 'make run-web' first" && exit 1 )
npm run test:unit
npm run test:unit:kcl-samples
.PHONY: test-e2e
test-e2e: test-e2e-$(TARGET)

View File

@ -34,7 +34,7 @@ The 3D view in Design Studio is just a video stream from our hosted geometry eng
- WebSockets (via [KittyCAD TS client](https://github.com/KittyCAD/kittycad.ts))
- Code Editor
- [CodeMirror](https://codemirror.net/)
- Custom WASM LSP Server
- [Custom WASM LSP Server](https://github.com/KittyCAD/modeling-app/tree/main/rust/kcl-lib/src/lsp/kcl)
- Modeling
- [KittyCAD TypeScript client](https://github.com/KittyCAD/kittycad.ts)

32
docs/kcl-lang/arrays.md Normal file
View File

@ -0,0 +1,32 @@
---
title: "Arrays and ranges"
excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual
---
Arrays are sequences of values.
Arrays can be written out as *array literals* using a sequence of expressions surrounded by square brackets, e.g., `['hello', 'world']` is an array of strings, `[x, x + 1, x + 2]` is an array of numbers (assuming `x` is a number), `[]` is an empty array, and `['hello', 42, true]` is a mixed array.
A value in an array can be accessed by indexing using square brackets where the index is a number, for example, `arr[0]`, `arr[42]`, `arr[i]` (where `arr` is an array and `i` is a (whole) number).
There are some useful functions for working with arrays in the standard library, see [std::array](/docs/kcl-std/modules/std-array) for details.
## Array types
Arrays have their own types: `[T]` where `T` is the type of the elements of the array, for example, `[string]` means an array of `string`s and `[any]` means an array of any values.
Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; 1+]` denotes an array whose length is at least one (i.e., a non-empty array). E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types.
## Ranges
Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. Examples:
```kcl,norun
[0..3] // [0, 1, 2, 3]
[3..10] // [3, 4, 5, 6, 7, 8, 9, 10]
x = 2
[x..x+1] // [2, 3]
```
The units of the start and end numbers must be the same and the result inherits those units.

View File

@ -14,6 +14,7 @@ things in a more tutorial fashion. See also our documentation of the [standard l
* [Values and types](/docs/kcl-lang/types)
* [Numeric types and units](/docs/kcl-lang/numeric)
* [Functions](/docs/kcl-lang/functions)
* [Arrays and ranges](/docs/kcl-lang/arrays)
* [Projects and modules](/docs/kcl-lang/modules)
* [Attributes](/docs/kcl-lang/attributes)
* [Importing geometry from other CAD systems](/docs/kcl-lang/foreign-imports)

View File

@ -19,18 +19,6 @@ myBool = false
Currently you cannot redeclare a constant.
## Arrays
An array is defined with `[]` braces. What is inside the brackets can
be of any type. For example, the following is completely valid:
```
myArray = ["thing", 2, false]
```
If you want to get a value from an array you can use the index like so:
`myArray[0]`.
## Objects
@ -40,8 +28,8 @@ An object is defined with `{}` braces. Here is an example object:
myObj = { a = 0, b = "thing" }
```
We support two different ways of getting properties from objects, you can call
`myObj.a` or `myObj["a"]` both work.
To get the property of an object, you can call `myObj.a`, which in the above
example returns 0.
## `ImportedGeometry`

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,38 @@
---
title: "legAngX"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for x."
layout: manual
---
Compute the angle of the given leg for x.
```kcl
legAngX(
hypotenuse: number(Length),
leg: number(Length),
): number(deg)
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes |
| `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes |
### Returns
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngX(hypotenuse = 5, leg = 3)
```

View File

@ -0,0 +1,38 @@
---
title: "legAngY"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for y."
layout: manual
---
Compute the angle of the given leg for y.
```kcl
legAngY(
hypotenuse: number(Length),
leg: number(Length),
): number(deg)
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes |
| `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes |
### Returns
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngY(hypotenuse = 5, leg = 3)
```

View File

@ -0,0 +1,38 @@
---
title: "legLen"
subtitle: "Function in std::math"
excerpt: "Compute the length of the given leg."
layout: manual
---
Compute the length of the given leg.
```kcl
legLen(
hypotenuse: number(Length),
leg: number(Length),
): number(deg)
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse. | Yes |
| `leg` | [`number(Length)`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side). | Yes |
### Returns
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legLen(hypotenuse = 5, leg = 3)
```

View File

@ -12,15 +12,15 @@ layout: manual
* [`appearance`](/docs/kcl-std/appearance)
* [`assert`](/docs/kcl-std/assert)
* [`assertIs`](/docs/kcl-std/assertIs)
* [`clone`](/docs/kcl-std/clone)
* [`clone`](/docs/kcl-std/functions/std-clone)
* [`helix`](/docs/kcl-std/functions/std-helix)
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d)
* [**std::array**](/docs/kcl-std/modules/std-array)
* [`map`](/docs/kcl-std/map)
* [`pop`](/docs/kcl-std/pop)
* [`push`](/docs/kcl-std/push)
* [`reduce`](/docs/kcl-std/reduce)
* [`map`](/docs/kcl-std/functions/std-array-map)
* [`pop`](/docs/kcl-std/functions/std-array-pop)
* [`push`](/docs/kcl-std/functions/std-array-push)
* [`reduce`](/docs/kcl-std/functions/std-array-reduce)
* [**std::math**](/docs/kcl-std/modules/std-math)
* [`abs`](/docs/kcl-std/functions/std-math-abs)
* [`acos`](/docs/kcl-std/functions/std-math-acos)
@ -30,9 +30,9 @@ layout: manual
* [`ceil`](/docs/kcl-std/functions/std-math-ceil)
* [`cos`](/docs/kcl-std/functions/std-math-cos)
* [`floor`](/docs/kcl-std/functions/std-math-floor)
* [`legAngX`](/docs/kcl-std/legAngX)
* [`legAngY`](/docs/kcl-std/legAngY)
* [`legLen`](/docs/kcl-std/legLen)
* [`legAngX`](/docs/kcl-std/functions/std-math-legAngX)
* [`legAngY`](/docs/kcl-std/functions/std-math-legAngY)
* [`legLen`](/docs/kcl-std/functions/std-math-legLen)
* [`ln`](/docs/kcl-std/functions/std-math-ln)
* [`log`](/docs/kcl-std/functions/std-math-log)
* [`log10`](/docs/kcl-std/functions/std-math-log10)
@ -140,12 +140,13 @@ See also the [types overview](/docs/kcl-lang/types)
* [**Primitive types**](/docs/kcl-lang/types)
* [`End`](/docs/kcl-lang/types#End)
* [`ImportedGeometry`](/docs/kcl-lang/types#ImportedGeometry)
* [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry)
* [`Start`](/docs/kcl-lang/types#Start)
* [`TagDeclarator`](/docs/kcl-lang/types#TagDeclarator)
* [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier)
* [`any`](/docs/kcl-std/types/std-types-any)
* [`bool`](/docs/kcl-std/types/std-types-bool)
* [`fn`](/docs/kcl-std/types/std-types-fn)
* [`number`](/docs/kcl-std/types/std-types-number)
* [`string`](/docs/kcl-std/types/std-types-string)
* [`tag`](/docs/kcl-std/types/std-types-tag)

View File

@ -1,38 +0,0 @@
---
title: "legAngX"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for x."
layout: manual
---
Compute the angle of the given leg for x.
```kcl
legAngX(
hypotenuse: number,
leg: number,
): number
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngX(hypotenuse = 5, leg = 3)
```

View File

@ -1,38 +0,0 @@
---
title: "legAngY"
subtitle: "Function in std::math"
excerpt: "Compute the angle of the given leg for y."
layout: manual
---
Compute the angle of the given leg for y.
```kcl
legAngY(
hypotenuse: number,
leg: number,
): number
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legAngY(hypotenuse = 5, leg = 3)
```

View File

@ -1,38 +0,0 @@
---
title: "legLen"
subtitle: "Function in std::math"
excerpt: "Compute the length of the given leg."
layout: manual
---
Compute the length of the given leg.
```kcl
legLen(
hypotenuse: number,
leg: number,
): number
```
### Arguments
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl-std/types/std-types-number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl-std/types/std-types-number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
```kcl
legLen(hypotenuse = 5, leg = 3)
```

File diff suppressed because one or more lines are too long

View File

@ -12,8 +12,8 @@ Functions for manipulating arrays of values.
## Functions and constants
* [`map`](/docs/kcl-std/map)
* [`pop`](/docs/kcl-std/pop)
* [`push`](/docs/kcl-std/push)
* [`reduce`](/docs/kcl-std/reduce)
* [`map`](/docs/kcl-std/functions/std-array-map)
* [`pop`](/docs/kcl-std/functions/std-array-pop)
* [`push`](/docs/kcl-std/functions/std-array-push)
* [`reduce`](/docs/kcl-std/functions/std-array-reduce)

View File

@ -23,9 +23,9 @@ Functions for mathematical operations and some useful constants.
* [`ceil`](/docs/kcl-std/functions/std-math-ceil)
* [`cos`](/docs/kcl-std/functions/std-math-cos)
* [`floor`](/docs/kcl-std/functions/std-math-floor)
* [`legAngX`](/docs/kcl-std/legAngX)
* [`legAngY`](/docs/kcl-std/legAngY)
* [`legLen`](/docs/kcl-std/legLen)
* [`legAngX`](/docs/kcl-std/functions/std-math-legAngX)
* [`legAngY`](/docs/kcl-std/functions/std-math-legAngY)
* [`legLen`](/docs/kcl-std/functions/std-math-legLen)
* [`ln`](/docs/kcl-std/functions/std-math-ln)
* [`log`](/docs/kcl-std/functions/std-math-log)
* [`log10`](/docs/kcl-std/functions/std-math-log10)

View File

@ -18,6 +18,7 @@ Types can (optionally) be used to describe a function's arguments and returned v
* [`Edge`](/docs/kcl-std/types/std-types-Edge)
* [`Face`](/docs/kcl-std/types/std-types-Face)
* [`Helix`](/docs/kcl-std/types/std-types-Helix)
* [`ImportedGeometry`](/docs/kcl-std/types/std-types-ImportedGeometry)
* [`Plane`](/docs/kcl-std/types/std-types-Plane)
* [`Point2d`](/docs/kcl-std/types/std-types-Point2d)
* [`Point3d`](/docs/kcl-std/types/std-types-Point3d)
@ -25,6 +26,7 @@ Types can (optionally) be used to describe a function's arguments and returned v
* [`Solid`](/docs/kcl-std/types/std-types-Solid)
* [`any`](/docs/kcl-std/types/std-types-any)
* [`bool`](/docs/kcl-std/types/std-types-bool)
* [`fn`](/docs/kcl-std/types/std-types-fn)
* [`number`](/docs/kcl-std/types/std-types-number)
* [`string`](/docs/kcl-std/types/std-types-string)
* [`tag`](/docs/kcl-std/types/std-types-tag)

View File

@ -37,7 +37,7 @@ You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL gui
* [`appearance`](/docs/kcl-std/appearance)
* [`assert`](/docs/kcl-std/assert)
* [`assertIs`](/docs/kcl-std/assertIs)
* [`clone`](/docs/kcl-std/clone)
* [`clone`](/docs/kcl-std/functions/std-clone)
* [`helix`](/docs/kcl-std/functions/std-helix)
* [`offsetPlane`](/docs/kcl-std/functions/std-offsetPlane)
* [`patternLinear2d`](/docs/kcl-std/patternLinear2d)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,13 @@
---
title: "ImportedGeometry"
subtitle: "Type in std::types"
excerpt: "Represents geometry which is defined using some other CAD system and imported into KCL."
layout: manual
---
Represents geometry which is defined using some other CAD system and imported into KCL.

View File

@ -0,0 +1,13 @@
---
title: "fn"
subtitle: "Type in std::types"
excerpt: "The type of any function in KCL."
layout: manual
---
The type of any function in KCL.

View File

@ -158,10 +158,14 @@ extrude001 = extrude(sketch001, length = 5)`
await expect(
page
.getByText(
'Modeling command failed: [ApiError { error_code: InternalEngine, message: "Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis" }]'
'Solid3D revolve failed: sketch profile must lie entirely on one side of the revolution axis'
)
.first()
).toBeVisible()
// Make sure ApiError is not on the page.
// This ensures we didn't nest the json
await expect(page.getByText('ApiError')).not.toBeVisible()
})
test('When error is not in view WITH LINTS you can click the badge to scroll to it', async ({

View File

@ -58,12 +58,6 @@ test(
await expect(submitButton).toBeVisible()
await page.keyboard.press('Enter')
// Look out for the toast message
const exportingToastMessage = page.getByText(`Exporting...`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
await expect(exportingToastMessage).toBeVisible()
await expect(alreadyExportingToastMessage).not.toBeVisible()
// Expect it to succeed
const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`)
@ -72,7 +66,6 @@ test(
const successToastMessage = page.getByText(`Exported successfully`)
await expect(successToastMessage).toBeVisible()
await expect(exportingToastMessage).not.toBeVisible()
// Check for the exported file
const firstFileFullPath = path.resolve(

View File

@ -155,6 +155,12 @@ export class CmdBarFixture {
}
}
closeCmdBar = async () => {
const cmdBarCloseBtn = this.page.getByTestId('command-bar-close-button')
await cmdBarCloseBtn.click()
await expect(this.cmdBarElement).not.toBeVisible()
}
get cmdSearchInput() {
return this.page.getByTestId('cmd-bar-search')
}
@ -298,4 +304,27 @@ export class CmdBarFixture {
`Monitoring text-to-cad API requests. Output will be saved to: ${outputPath}`
)
}
async toBeOpened() {
// Check that the command bar is opened
await expect(this.cmdBarElement).toBeVisible({ timeout: 10_000 })
}
async expectArgValue(value: string) {
// Check the placeholder project name exists
const actualArgument = await this.cmdBarElement
.getByTestId('cmd-bar-arg-value')
.inputValue()
const expectedArgument = value
expect(actualArgument).toBe(expectedArgument)
}
async expectCommandName(value: string) {
// Check the placeholder project name exists
const actual = await this.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = value
expect(actual).toBe(expected)
}
}

View File

@ -24,6 +24,7 @@ export class HomePageFixture {
projectTextName!: Locator
sortByDateBtn!: Locator
sortByNameBtn!: Locator
appHeader!: Locator
tutorialBtn!: Locator
constructor(page: Page) {
@ -44,6 +45,7 @@ export class HomePageFixture {
this.sortByDateBtn = this.page.getByTestId('home-sort-by-modified')
this.sortByNameBtn = this.page.getByTestId('home-sort-by-name')
this.appHeader = this.page.getByTestId('app-header')
this.tutorialBtn = this.page.getByTestId('home-tutorial-button')
}
@ -125,4 +127,11 @@ export class HomePageFixture {
await this.createAndGoToProject(name)
}
isNativeFileMenuCreated = async () => {
await expect(this.appHeader).toHaveAttribute(
'data-native-file-menu',
'true'
)
}
}

View File

@ -46,6 +46,7 @@ export class SceneFixture {
public networkToggleConnected!: Locator
public engineConnectionsSpinner!: Locator
public startEditSketchBtn!: Locator
public appHeader!: Locator
constructor(page: Page) {
this.page = page
@ -57,6 +58,7 @@ export class SceneFixture {
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))
this.appHeader = this.page.getByTestId('app-header')
}
private _serialiseScene = async (): Promise<SceneSerialised> => {
const camera = await this.getCameraInfo()
@ -280,6 +282,13 @@ export class SceneFixture {
await expect(buttonToTest).toBeVisible()
await buttonToTest.click()
}
isNativeFileMenuCreated = async () => {
await expect(this.appHeader).toHaveAttribute(
'data-native-file-menu',
'true'
)
}
}
function isColourArray(

File diff suppressed because it is too large Load Diff

View File

@ -1818,7 +1818,6 @@ test(
'extrude-custom-plane.kcl',
'extrude-inside-fn-with-tags.kcl',
'fillet-and-shell.kcl',
'fillet_duplicate_tags.kcl',
'focusrite_scarlett_mounting_bracket.kcl',
'function_sketch.kcl',
'function_sketch_with_position.kcl',
@ -1826,7 +1825,6 @@ test(
'helix_defaults.kcl',
'helix_defaults_negative_extrude.kcl',
'helix_with_length.kcl',
'i_shape.kcl',
'kittycad_svg.kcl',
'lego.kcl',
'lsystem.kcl',

View File

@ -51,8 +51,11 @@ test.describe('Regression tests', () => {
// the close doesn't work
// when https://github.com/KittyCAD/modeling-app/issues/3268 is closed
// this test will need updating
const crypticErrorText = `ApiError`
const crypticErrorText = `Cannot close a path that is non-planar or with duplicate vertices.
Internal engine error on request`
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
// Ensure we didn't nest the json.
await expect(page.getByText('ApiError')).not.toBeVisible()
})
test('user should not have to press down twice in cmdbar', async ({
page,
@ -329,10 +332,10 @@ extrude002 = extrude(profile002, length = 150)
)
const websocketPromise = page.waitForEvent('websocket')
await toolbar.closePane('code')
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await toolbar.closePane('code')
const websocket = await websocketPromise
await scene.connectionEstablished()
@ -545,14 +548,15 @@ extrude002 = extrude(profile002, length = 150)
expect(alreadyExportingToastMessage).not.toBeVisible(),
])
await expect(successToastMessage).toHaveCount(2)
const count = await successToastMessage.count()
await expect(count).toBeGreaterThanOrEqual(2)
})
})
test(
`Network health indicator only appears in modeling view`,
{ tag: '@electron' },
async ({ context, page }, testInfo) => {
async ({ context, page }) => {
await context.folderSetupFn(async (dir) => {
const bracketDir = path.join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
@ -561,9 +565,7 @@ extrude002 = extrude(profile002, length = 150)
path.join(bracketDir, 'main.kcl')
)
})
await page.setBodyDimensions({ width: 1200, height: 500 })
const u = await getUtils(page)
// Locators
const projectsHeading = page.getByRole('heading', {
@ -583,10 +585,8 @@ extrude002 = extrude(profile002, length = 150)
})
await test.step('Check the modeling view', async () => {
await expect(projectsHeading).not.toBeVisible()
await expect(networkHealthIndicator).toBeVisible()
await expect(networkHealthIndicator).toContainText('Problem')
await u.waitForPageLoad()
await expect(networkHealthIndicator).toContainText('Connected')
})
}
)

View File

@ -3496,6 +3496,73 @@ profile001 = startProfile(sketch001, at = [-102.72, 237.44])
).toBeVisible()
})
// Ensure feature tree is not showing previous file's content when switching to a file with KCL errors.
test('Feature tree shows correct sketch count per file', async ({
context,
homePage,
scene,
toolbar,
cmdBar,
page,
}) => {
const u = await getUtils(page)
// Setup project with files.
const GOOD_KCL = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [220.81, 253.8])
|> line(end = [132.84, -151.31])
|> line(end = [25.51, 167.15])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch002 = startSketchOn(XZ)
profile002 = startProfile(sketch002, at = [158.35, -70.82])
|> line(end = [73.9, -152.19])
|> line(end = [85.33, 135.48])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`
const ERROR_KCL = `sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [127.56, 179.02])
|> line(end = [132.84, -112.6])
|> line(end = [85.33, 234.01])
|> line(enfd = [-137.23, -54.55])`
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, 'multi-file-sketch-test')
await fs.mkdir(projectDir, { recursive: true })
await Promise.all([
fs.writeFile(path.join(projectDir, 'good.kcl'), GOOD_KCL, 'utf-8'),
fs.writeFile(path.join(projectDir, 'error.kcl'), ERROR_KCL, 'utf-8'),
])
})
await page.setBodyDimensions({ width: 1000, height: 800 })
await homePage.openProject('multi-file-sketch-test')
await scene.connectionEstablished()
await scene.settled(cmdBar)
await u.closeDebugPanel()
await toolbar.openFeatureTreePane()
await toolbar.openPane('files')
await toolbar.openFile('good.kcl')
await expect(
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
).toHaveCount(2)
await toolbar.openFile('error.kcl')
// Ensure filetree is populated
await scene.settled(cmdBar)
await expect(
toolbar.featureTreePane.getByRole('button', { name: 'Sketch' })
).toHaveCount(0)
})
test('adding a syntax error, recovers after fixing', async ({
page,
homePage,

View File

@ -834,7 +834,13 @@ test('theme persists', async ({ page, context }) => {
})
test.describe('code color goober', { tag: '@snapshot' }, () => {
test('code color goober', async ({ page, context, scene, cmdBar }) => {
test('code color goober', async ({
page,
context,
scene,
cmdBar,
editor,
}) => {
const u = await getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
@ -879,6 +885,7 @@ sweepSketch = startSketchOn(XY)
context,
scene,
cmdBar,
editor,
}) => {
const u = await getUtils(page)
await context.addInitScript(async () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 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: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 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: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -21,6 +21,7 @@ export const token = process.env.token || ''
import type { ProjectConfiguration } from '@rust/kcl-lib/bindings/ProjectConfiguration'
import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup'
import { isErrorWhitelisted } from '@e2e/playwright/lib/console-error-whitelist'
import { TEST_SETTINGS, TEST_SETTINGS_KEY } from '@e2e/playwright/storageStates'
import { test } from '@e2e/playwright/zoo-test'
@ -809,8 +810,6 @@ export const doExport = async (
await page.getByRole('button', { name: 'Submit command' }).click()
// This usually happens immediately after. If we're too slow we don't
// catch it.
await expect(page.getByText('Exported successfully')).toBeVisible()
if (exportFrom === 'sidebarButton' || exportFrom === 'commandBar') {
@ -1151,3 +1150,77 @@ export function perProjectSettingsToToml(
// eslint-disable-next-line no-restricted-syntax
return TOML.stringify(settings as any)
}
export async function clickElectronNativeMenuById(
tronApp: ElectronZoo,
menuId: string
) {
const clickWasTriggered = await tronApp.electron.evaluate(
async ({ app }, menuId) => {
if (!app || !app.applicationMenu) {
return false
}
const menu = app.applicationMenu.getMenuItemById(menuId)
if (!menu) return false
menu.click()
return true
},
menuId
)
expect(clickWasTriggered).toBe(true)
}
export async function findElectronNativeMenuById(
tronApp: ElectronZoo,
menuId: string
) {
const found = await tronApp.electron.evaluate(async ({ app }, menuId) => {
if (!app || !app.applicationMenu) {
return false
}
const menu = app.applicationMenu.getMenuItemById(menuId)
if (!menu) return false
return true
}, menuId)
expect(found).toBe(true)
}
export async function openSettingsExpectText(page: Page, text: string) {
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the user tab
const actualText = settings.getByText(text)
await expect(actualText).toBeVisible()
}
export async function openSettingsExpectLocator(page: Page, selector: string) {
const settings = page.getByTestId('settings-dialog-panel')
await expect(settings).toBeVisible()
// You are viewing the keybindings tab
const settingsLocator = settings.locator(selector)
await expect(settingsLocator).toBeVisible()
}
/**
* A developer helper function to make playwright send all the console logs to stdout
* Call this within your E2E test and pass in the page or the tronApp to get as many
* logs piped to stdout for debugging
*/
export async function enableConsoleLogEverything({
page,
tronApp,
}: { page?: Page; tronApp?: ElectronZoo }) {
page?.on('console', (msg) => {
console.log(`[Page-log]: ${msg.text()}`)
})
tronApp?.electron.on('window', async (electronPage) => {
electronPage.on('console', (msg) => {
console.log(`[Renderer] ${msg.type()}: ${msg.text()}`)
})
})
tronApp?.electron.on('console', (msg) => {
console.log(`[Main] ${msg.type()}: ${msg.text()}`)
})
}

View File

@ -33,8 +33,8 @@ test.describe('Testing loading external models', () => {
// Locators and constants
const newSample = {
file: 'parametric-bearing-pillow-block' + FILE_EXT,
title: 'Parametric Bearing Pillow Block',
file: 'pillow-block-bearing' + FILE_EXT,
title: 'Pillow Block Bearing',
}
const commandBarButton = page.getByRole('button', { name: 'Commands' })
const samplesCommandOption = page.getByRole('option', {
@ -100,9 +100,9 @@ test.describe('Testing loading external models', () => {
// Locators and constants
const sampleOne = {
file: 'parametric-bearing-pillow-block' + FILE_EXT,
title: 'Parametric Bearing Pillow Block',
file1: 'parametric-bearing-pillow-block-1' + FILE_EXT,
file: 'ball-bearing' + FILE_EXT,
title: 'Ball Bearing',
file1: 'ball-bearing-1' + FILE_EXT,
}
const projectCard = page.getByRole('link', { name: 'bracket' })
const overwriteWarning = page.getByText(

View File

@ -905,7 +905,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('Enter')
// Go into the project that was created from Text to CAD
await page.getByText(projectName).click()
await homePage.openProject(projectName)
await expect(page.getByTestId('app-header-project-name')).toBeVisible()
await expect(page.getByTestId('app-header-project-name')).toContainText(
@ -951,7 +951,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('Enter')
// Go into the project that was created from Text to CAD
await page.getByText(projectName).click()
await homePage.openProject(projectName)
await page.getByRole('button', { name: 'Accept' }).click()

View File

@ -74,7 +74,7 @@
LIBCLANG_PATH = "${pkgs.libclang.lib}/lib";
ELECTRON_OVERRIDE_DIST_PATH =
if pkgs.stdenv.isDarwin
then "${pkgs.electron}/Applications"
then "${pkgs.electron}/Applications/Electron.app/Contents/MacOS/"
else "${pkgs.electron}/bin";
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS = true;
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH = "${pkgs.playwright-driver.browsers}/chromium-1091/chrome-linux/chrome";

View File

@ -107,7 +107,6 @@
"check": "biome check ./src ./e2e ./packages/codemirror-lsp-client/src ./rust/kcl-language-server/client/src",
"fetch:wasm": "./scripts/get-latest-wasm-bundle.sh",
"fetch:wasm:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/get-latest-wasm-bundle.ps1",
"fetch:samples": "rm -rf public/kcl-samples* && curl -L -o public/kcl-samples.zip https://github.com/KittyCAD/kcl-samples/archive/refs/heads/achalmers/kw-args-xylineto.zip && unzip -o public/kcl-samples.zip -d public && mv public/kcl-samples-* public/kcl-samples",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\" || echo \"sed for both mac and linux\"",
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src",
"lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src",
@ -123,7 +122,6 @@
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
"postinstall": "electron-rebuild",
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",
"tron:start": "electron-forge start",
"chrome:test": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert=@snapshot",
"tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development",

View File

@ -41,6 +41,14 @@ interface LSPRequestMap {
LSP.DefinitionParams,
LSP.Definition | LSP.DefinitionLink[] | null,
]
'textDocument/documentColor': [
LSP.DocumentColorParams,
LSP.ColorInformation[] | null,
]
'textDocument/colorPresentation': [
LSP.ColorPresentationParams,
LSP.ColorPresentation[] | null,
]
}
// Client to server
@ -229,6 +237,22 @@ export class LanguageServerClient {
return await this.request('textDocument/definition', params)
}
async textDocumentDocumentColor(params: LSP.DocumentColorParams) {
const serverCapabilities = this.getServerCapabilities()
if (!serverCapabilities.colorProvider) {
return null
}
return await this.request('textDocument/documentColor', params)
}
async textDocumentColorPresentation(params: LSP.ColorPresentationParams) {
const serverCapabilities = this.getServerCapabilities()
if (!serverCapabilities.colorProvider) {
return null
}
return await this.request('textDocument/colorPresentation', params)
}
attachPlugin(plugin: LanguageServerPlugin) {
this.plugins.push(plugin)
}

View File

@ -21,6 +21,7 @@ export {
lspRenameEvent,
lspSemanticTokensEvent,
lspCodeActionEvent,
lspColorUpdateEvent,
} from './plugin/annotation'
export {
LanguageServerPlugin,

View File

@ -6,6 +6,7 @@ export enum LspAnnotation {
Diagnostics = 'diagnostics',
Rename = 'rename',
CodeAction = 'code-action',
ColorUpdate = 'color-update',
}
const lspEvent = Annotation.define<LspAnnotation>()
@ -14,3 +15,4 @@ export const lspFormatCodeEvent = lspEvent.of(LspAnnotation.FormatCode)
export const lspDiagnosticsEvent = lspEvent.of(LspAnnotation.Diagnostics)
export const lspRenameEvent = lspEvent.of(LspAnnotation.Rename)
export const lspCodeActionEvent = lspEvent.of(LspAnnotation.CodeAction)
export const lspColorUpdateEvent = lspEvent.of(LspAnnotation.ColorUpdate)

View File

@ -0,0 +1,278 @@
import {
StateEffect,
StateField,
type Extension,
type Range,
} from '@codemirror/state'
import {
Decoration,
type DecorationSet,
EditorView,
ViewPlugin,
WidgetType,
type ViewUpdate,
} from '@codemirror/view'
import type { LanguageServerPlugin } from './lsp'
import { lspColorUpdateEvent } from './annotation'
import { isArray } from '../lib/utils'
import { offsetToPos, posToOffset, posToOffsetOrZero } from './util'
import type * as LSP from 'vscode-languageserver-protocol'
/* ------------------------------------------------------------------ */
/* ---------- original helpers / widget / color utilities ---------- */
/* ------------------------------------------------------------------ */
interface PickerState {
from: number
to: number
red: number
green: number
blue: number
alpha: number
}
export interface WidgetOptions extends PickerState {
color: string
}
export type ColorData = Omit<WidgetOptions, 'from' | 'to'>
const pickerState = new WeakMap<HTMLInputElement, PickerState>()
function rgbaToHex(color: LSP.Color): string {
return (
'#' +
[color.red, color.green, color.blue]
.map((c) =>
Math.round(c * 255)
.toString(16)
.padStart(2, '0')
)
.join('')
)
}
function hexToRGBComponents(hex: string): number[] {
const r = hex.slice(1, 3)
const g = hex.slice(3, 5)
const b = hex.slice(5, 7)
return [parseInt(r, 16) / 255, parseInt(g, 16) / 255, parseInt(b, 16) / 255]
}
async function discoverColorsViaLsp(
view: EditorView,
plugin: LanguageServerPlugin
): Promise<WidgetOptions | Array<WidgetOptions> | null> {
const responses = await plugin.requestDocumentColors()
if (!responses) return null
const colors: Array<WidgetOptions> = []
for (const color of responses) {
if (!color.range || !color.color) continue
const { start, end } = color.range
const from = posToOffset(view.state.doc, start)
const to = posToOffset(view.state.doc, end)
if (from == null || to == null) continue
colors.push({
color: rgbaToHex(color.color),
...color.color,
from,
to,
})
}
return colors
}
async function colorPickersDecorations(
view: EditorView,
plugin: LanguageServerPlugin
): Promise<DecorationSet> {
const widgets: Array<Range<Decoration>> = []
const maybe = await discoverColorsViaLsp(view, plugin)
if (!maybe) return Decoration.none
const optionsList = isArray(maybe) ? maybe : [maybe]
for (const wo of optionsList) {
widgets.push(
Decoration.widget({
widget: new ColorPickerWidget(wo),
side: 1,
}).range(wo.from)
)
}
return Decoration.set(widgets)
}
export const wrapperClassName = 'cm-css-color-picker-wrapper'
class ColorPickerWidget extends WidgetType {
private readonly state: PickerState
private readonly color: string
constructor({ color, ...state }: WidgetOptions) {
super()
this.state = state
this.color = color
}
eq(other: ColorPickerWidget) {
return (
other.color === this.color &&
other.state.from === this.state.from &&
other.state.to === this.state.to &&
other.state.alpha === this.state.alpha
)
}
toDOM() {
const picker = document.createElement('input')
pickerState.set(picker, this.state)
picker.type = 'color'
picker.value = this.color
const wrapper = document.createElement('span')
wrapper.appendChild(picker)
wrapper.className = wrapperClassName
return wrapper
}
ignoreEvent() {
return false
}
}
export const colorPickerTheme = EditorView.baseTheme({
[`.${wrapperClassName}`]: {
display: 'inline-block',
outline: '1px solid #eee',
marginRight: '0.6ch',
height: '1em',
width: '1em',
transform: 'translateY(1px)',
},
[`.${wrapperClassName} input[type="color"]`]: {
cursor: 'pointer',
height: '100%',
width: '100%',
padding: 0,
border: 'none',
'&::-webkit-color-swatch-wrapper': { padding: 0 },
'&::-webkit-color-swatch': { border: 'none' },
'&::-moz-color-swatch': { border: 'none' },
},
})
/* ------------------------------------------------------------------ */
/* ------------------- ✅ new state machinery -------------------- */
/* ------------------------------------------------------------------ */
// Effect that carries a fresh DecorationSet
const setColorDecorations = StateEffect.define<DecorationSet>()
// Field that stores the current DecorationSet
const colorDecorationsField = StateField.define<DecorationSet>({
create: () => Decoration.none,
update(value, tr) {
value = value.map(tr.changes)
for (const e of tr.effects) if (e.is(setColorDecorations)) value = e.value
return value
},
provide: (f) => EditorView.decorations.from(f),
})
/* ------------------------------------------------------------------ */
/* ------------------ original ViewPlugin, patched ---------------- */
/* ------------------------------------------------------------------ */
export const makeColorPicker = (plugin: ViewPlugin<LanguageServerPlugin>) =>
ViewPlugin.fromClass(
class ColorPickerViewPlugin {
plugin: LanguageServerPlugin | null
constructor(view: EditorView) {
this.plugin = view.plugin(plugin)
if (!this.plugin) return
// initial async load → dispatch decorations
// eslint-disable-next-line @typescript-eslint/no-floating-promises
colorPickersDecorations(view, this.plugin).then((deco) => {
view.dispatch({ effects: setColorDecorations.of(deco) })
})
}
async update(update: ViewUpdate) {
if (!this.plugin) return
if (!(update.docChanged || update.viewportChanged)) return
const deco = await colorPickersDecorations(update.view, this.plugin)
update.view.dispatch({ effects: setColorDecorations.of(deco) })
}
},
{
eventHandlers: {
change: (e: Event, view: EditorView) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
colorPickerChange(e, view, plugin)
},
},
}
)
/* ------------------------------------------------------------------ */
/* -------------------- unchanged event handler ------------------- */
/* ------------------------------------------------------------------ */
async function colorPickerChange(
e: Event,
view: EditorView,
plugin: ViewPlugin<LanguageServerPlugin>
): Promise<boolean> {
const value = view.plugin(plugin)
if (!value) return false
const target = e.target as HTMLInputElement
if (
target.nodeName !== 'INPUT' ||
!target.parentElement?.classList.contains(wrapperClassName)
)
return false
const data = pickerState.get(target)!
const converted = target.value + data.alpha
const [red, green, blue] = hexToRGBComponents(converted)
const responses = await value.requestColorPresentation(
{ red, green, blue, alpha: data.alpha },
{
start: offsetToPos(view.state.doc, data.from),
end: offsetToPos(view.state.doc, data.to),
}
)
if (!responses?.length) return false
for (const resp of responses) {
const changes = resp.textEdit
? {
from: posToOffsetOrZero(view.state.doc, resp.textEdit.range.start),
to: posToOffsetOrZero(view.state.doc, resp.textEdit.range.end),
insert: resp.textEdit.newText,
}
: { from: data.from, to: data.to, insert: resp.label }
view.dispatch({ changes, annotations: [lspColorUpdateEvent] })
}
return true
}
/* ------------------------------------------------------------------ */
/* ------------------------- public API --------------------------- */
/* ------------------------------------------------------------------ */
export default function lspColorsExt(
plugin: ViewPlugin<LanguageServerPlugin>
): Extension {
return [colorDecorationsField, makeColorPicker(plugin), colorPickerTheme]
}

View File

@ -48,6 +48,7 @@ import { isArray } from '../lib/utils'
import lspGoToDefinitionExt from './go-to-definition'
import lspRenameExt from './rename'
import lspSignatureHelpExt from './signature-help'
import lspColorsExt from './colors'
const useLast = (values: readonly any[]) => values.reduce((_, v) => v, '')
export const docPathFacet = Facet.define<string, string>({
@ -534,6 +535,37 @@ export class LanguageServerPlugin implements PluginValue {
})
}
async requestDocumentColors() {
if (
!(this.client.getServerCapabilities().colorProvider && this.client.ready)
) {
return
}
const result = await this.client.textDocumentDocumentColor({
textDocument: { uri: this.getDocUri() },
})
if (!result) return
return result
}
async requestColorPresentation(color: LSP.Color, range: LSP.Range) {
if (
!(this.client.getServerCapabilities().colorProvider && this.client.ready)
) {
return
}
const result = await this.client.textDocumentColorPresentation({
textDocument: { uri: this.getDocUri() },
color,
range,
})
if (!result) return
return result
}
async requestRename(
view: EditorView,
{ line, character }: { line: number; character: number }
@ -1318,6 +1350,7 @@ export class LanguageServerPluginSpec
return [
linter(null),
lspAutocompleteExt(plugin),
lspColorsExt(plugin),
lspFormatExt(plugin),
lspGoToDefinitionExt(plugin),
lspHoverExt(plugin),

View File

@ -31,14 +31,24 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![ball-bearing](screenshots/ball-bearing.png)](ball-bearing/main.kcl)
#### [bench](bench/main.kcl) ([screenshot](screenshots/bench.png))
[![bench](screenshots/bench.png)](bench/main.kcl)
#### [bone-plate](bone-plate/main.kcl) ([screenshot](screenshots/bone-plate.png))
[![bone-plate](screenshots/bone-plate.png)](bone-plate/main.kcl)
#### [bottle](bottle/main.kcl) ([screenshot](screenshots/bottle.png))
[![bottle](screenshots/bottle.png)](bottle/main.kcl)
#### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png))
[![bracket](screenshots/bracket.png)](bracket/main.kcl)
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png))
[![car-wheel-assembly](screenshots/car-wheel-assembly.png)](car-wheel-assembly/main.kcl)
#### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png))
[![cold-plate](screenshots/cold-plate.png)](cold-plate/main.kcl)
#### [color-cube](color-cube/main.kcl) ([screenshot](screenshots/color-cube.png))
[![color-cube](screenshots/color-cube.png)](color-cube/main.kcl)
#### [counterdrilled-weldment](counterdrilled-weldment/main.kcl) ([screenshot](screenshots/counterdrilled-weldment.png))
[![counterdrilled-weldment](screenshots/counterdrilled-weldment.png)](counterdrilled-weldment/main.kcl)
#### [countersunk-plate](countersunk-plate/main.kcl) ([screenshot](screenshots/countersunk-plate.png))
[![countersunk-plate](screenshots/countersunk-plate.png)](countersunk-plate/main.kcl)
#### [cpu-cooler](cpu-cooler/main.kcl) ([screenshot](screenshots/cpu-cooler.png))
[![cpu-cooler](screenshots/cpu-cooler.png)](cpu-cooler/main.kcl)
#### [cycloidal-gear](cycloidal-gear/main.kcl) ([screenshot](screenshots/cycloidal-gear.png))
[![cycloidal-gear](screenshots/cycloidal-gear.png)](cycloidal-gear/main.kcl)
#### [dodecahedron](dodecahedron/main.kcl) ([screenshot](screenshots/dodecahedron.png))
@ -55,8 +65,6 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![food-service-spatula](screenshots/food-service-spatula.png)](food-service-spatula/main.kcl)
#### [french-press](french-press/main.kcl) ([screenshot](screenshots/french-press.png))
[![french-press](screenshots/french-press.png)](french-press/main.kcl)
#### [gear](gear/main.kcl) ([screenshot](screenshots/gear.png))
[![gear](screenshots/gear.png)](gear/main.kcl)
#### [gear-rack](gear-rack/main.kcl) ([screenshot](screenshots/gear-rack.png))
[![gear-rack](screenshots/gear-rack.png)](gear-rack/main.kcl)
#### [gridfinity-baseplate](gridfinity-baseplate/main.kcl) ([screenshot](screenshots/gridfinity-baseplate.png))
@ -67,6 +75,18 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![gridfinity-bins](screenshots/gridfinity-bins.png)](gridfinity-bins/main.kcl)
#### [gridfinity-bins-stacking-lip](gridfinity-bins-stacking-lip/main.kcl) ([screenshot](screenshots/gridfinity-bins-stacking-lip.png))
[![gridfinity-bins-stacking-lip](screenshots/gridfinity-bins-stacking-lip.png)](gridfinity-bins-stacking-lip/main.kcl)
#### [hammer](hammer/main.kcl) ([screenshot](screenshots/hammer.png))
[![hammer](screenshots/hammer.png)](hammer/main.kcl)
#### [helical-gear](helical-gear/main.kcl) ([screenshot](screenshots/helical-gear.png))
[![helical-gear](screenshots/helical-gear.png)](helical-gear/main.kcl)
#### [helical-planetary-gearset](helical-planetary-gearset/main.kcl) ([screenshot](screenshots/helical-planetary-gearset.png))
[![helical-planetary-gearset](screenshots/helical-planetary-gearset.png)](helical-planetary-gearset/main.kcl)
#### [helium-tank](helium-tank/main.kcl) ([screenshot](screenshots/helium-tank.png))
[![helium-tank](screenshots/helium-tank.png)](helium-tank/main.kcl)
#### [herringbone-gear](herringbone-gear/main.kcl) ([screenshot](screenshots/herringbone-gear.png))
[![herringbone-gear](screenshots/herringbone-gear.png)](herringbone-gear/main.kcl)
#### [herringbone-planetary-gearset](herringbone-planetary-gearset/main.kcl) ([screenshot](screenshots/herringbone-planetary-gearset.png))
[![herringbone-planetary-gearset](screenshots/herringbone-planetary-gearset.png)](herringbone-planetary-gearset/main.kcl)
#### [hex-nut](hex-nut/main.kcl) ([screenshot](screenshots/hex-nut.png))
[![hex-nut](screenshots/hex-nut.png)](hex-nut/main.kcl)
#### [i-beam](i-beam/main.kcl) ([screenshot](screenshots/i-beam.png))
@ -83,8 +103,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![mounting-plate](screenshots/mounting-plate.png)](mounting-plate/main.kcl)
#### [multi-axis-robot](multi-axis-robot/main.kcl) ([screenshot](screenshots/multi-axis-robot.png))
[![multi-axis-robot](screenshots/multi-axis-robot.png)](multi-axis-robot/main.kcl)
#### [parametric-bearing-pillow-block](parametric-bearing-pillow-block/main.kcl) ([screenshot](screenshots/parametric-bearing-pillow-block.png))
[![parametric-bearing-pillow-block](screenshots/parametric-bearing-pillow-block.png)](parametric-bearing-pillow-block/main.kcl)
#### [pillow-block-bearing](pillow-block-bearing/main.kcl) ([screenshot](screenshots/pillow-block-bearing.png))
[![pillow-block-bearing](screenshots/pillow-block-bearing.png)](pillow-block-bearing/main.kcl)
#### [pipe](pipe/main.kcl) ([screenshot](screenshots/pipe.png))
[![pipe](screenshots/pipe.png)](pipe/main.kcl)
#### [pipe-flange-assembly](pipe-flange-assembly/main.kcl) ([screenshot](screenshots/pipe-flange-assembly.png))
@ -93,6 +113,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![pipe-with-bend](screenshots/pipe-with-bend.png)](pipe-with-bend/main.kcl)
#### [poopy-shoe](poopy-shoe/main.kcl) ([screenshot](screenshots/poopy-shoe.png))
[![poopy-shoe](screenshots/poopy-shoe.png)](poopy-shoe/main.kcl)
#### [prosthetic-hip](prosthetic-hip/main.kcl) ([screenshot](screenshots/prosthetic-hip.png))
[![prosthetic-hip](screenshots/prosthetic-hip.png)](prosthetic-hip/main.kcl)
#### [router-template-cross-bar](router-template-cross-bar/main.kcl) ([screenshot](screenshots/router-template-cross-bar.png))
[![router-template-cross-bar](screenshots/router-template-cross-bar.png)](router-template-cross-bar/main.kcl)
#### [router-template-slate](router-template-slate/main.kcl) ([screenshot](screenshots/router-template-slate.png))
@ -101,10 +123,20 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![sheet-metal-bracket](screenshots/sheet-metal-bracket.png)](sheet-metal-bracket/main.kcl)
#### [socket-head-cap-screw](socket-head-cap-screw/main.kcl) ([screenshot](screenshots/socket-head-cap-screw.png))
[![socket-head-cap-screw](screenshots/socket-head-cap-screw.png)](socket-head-cap-screw/main.kcl)
#### [spur-gear](spur-gear/main.kcl) ([screenshot](screenshots/spur-gear.png))
[![spur-gear](screenshots/spur-gear.png)](spur-gear/main.kcl)
#### [spur-reduction-gearset](spur-reduction-gearset/main.kcl) ([screenshot](screenshots/spur-reduction-gearset.png))
[![spur-reduction-gearset](screenshots/spur-reduction-gearset.png)](spur-reduction-gearset/main.kcl)
#### [surgical-drill-guide](surgical-drill-guide/main.kcl) ([screenshot](screenshots/surgical-drill-guide.png))
[![surgical-drill-guide](screenshots/surgical-drill-guide.png)](surgical-drill-guide/main.kcl)
#### [tooling-nest-block](tooling-nest-block/main.kcl) ([screenshot](screenshots/tooling-nest-block.png))
[![tooling-nest-block](screenshots/tooling-nest-block.png)](tooling-nest-block/main.kcl)
#### [utility-sink](utility-sink/main.kcl) ([screenshot](screenshots/utility-sink.png))
[![utility-sink](screenshots/utility-sink.png)](utility-sink/main.kcl)
#### [walkie-talkie](walkie-talkie/main.kcl) ([screenshot](screenshots/walkie-talkie.png))
[![walkie-talkie](screenshots/walkie-talkie.png)](walkie-talkie/main.kcl)
#### [washer](washer/main.kcl) ([screenshot](screenshots/washer.png))
[![washer](screenshots/washer.png)](washer/main.kcl)
#### [wing-spar](wing-spar/main.kcl) ([screenshot](screenshots/wing-spar.png))
[![wing-spar](screenshots/wing-spar.png)](wing-spar/main.kcl)

View File

@ -57,9 +57,9 @@ fn connectorSketch(@plane, start) {
export fn connector(@plane, length) {
connectorSketch(plane, start = [-12, 8])
|> extrude(length = length)
|> extrude(length)
connectorSketch(plane, start = [16, 8])
|> extrude(length = length)
|> extrude(length)
return 0
}
@ -79,7 +79,7 @@ fn seatSlatSketch(@plane) {
export fn seatSlats(@plane, length) {
seatSlatSketch(plane)
|> extrude(length = length)
|> extrude(length)
return 0
}
@ -99,7 +99,7 @@ fn backSlatsSketch(@plane) {
export fn backSlats(@plane, length) {
b = backSlatsSketch(plane)
|> extrude(length = length)
|> extrude(length)
return b
}

View File

@ -0,0 +1,55 @@
// Bone Plate
// A bone plate is a medical device used in orthopedics to stabilize and fix bone fractures during the healing process. They are typically made of stainless steel or titanium and are secured to the bone with screws. Bone plates come in various types, including locking, compression, and bridge plates, each with specific applications
// Set units
@settings(defaultLengthUnit = mm)
// Define parameters
boltSize = 4.5
// Revolve the profile of a compression plate designed to fit a bone
plateRevolve = startSketchOn(YZ)
|> startProfile(at = [22.9, 0])
|> arc(angleStart = 180, angleEnd = 176, radius = 120)
|> arc(angleStart = -60, angleEnd = 54, radius = 5)
|> arc(angleStart = 180, angleEnd = 176, radius = 120)
|> arc(angleStart = -60, angleEnd = 54, radius = 5)
|> arc(angleStart = 180, angleEnd = 176, radius = 120)
|> arc(angleStart = -60, angleEnd = 54, radius = 5)
|> arc(angleStart = 180, angleEnd = 174, radius = 170)
|> tangentialArc(endAbsolute = [41.8, 91.88])
|> tangentialArc(endAbsolute = [56.92, 117.08], tag = $seg01)
|> angledLine(angle = tangentToEnd(seg01), length = 23.16)
|> tangentialArc(endAbsolute = [60.93, 140.44], tag = $seg02)
|> angledLine(angle = tangentToEnd(seg02), length = 25.65)
|> tangentialArc(endAbsolute = [48.35, 85.53])
|> tangentialArc(endAbsolute = [35.2, 67.73], tag = $seg03)
|> angledLine(angle = tangentToEnd(seg03), length = 49.06)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> revolve(axis = Y, angle = 65, symmetric = true)
// Create a hole sketch with the size and location of each bolt hole
holeSketch = startSketchOn(XZ)
hole01 = circle(holeSketch, center = [0, 12.25], radius = boltSize / 2)
|> extrude(length = -100)
hole02 = circle(holeSketch, center = [0, 29.5], radius = boltSize / 2)
|> extrude(length = -100)
hole03 = circle(holeSketch, center = [0, 46.25], radius = boltSize / 2)
|> extrude(length = -100)
hole04 = circle(holeSketch, center = [0, 77], radius = boltSize / 2)
|> extrude(length = -100)
hole05 = circle(holeSketch, center = [0, 100], radius = boltSize / 2)
|> extrude(length = -100)
hole06 = circle(holeSketch, center = [0, 130], radius = boltSize / 2)
|> extrude(length = -100)
hole07 = circle(holeSketch, center = [-20, 130], radius = boltSize / 2)
|> extrude(length = -100)
hole08 = circle(holeSketch, center = [20, 130], radius = boltSize / 2)
|> extrude(length = -100)
// Cut each guiding clearance hole from the bone plate
solid001 = subtract([plateRevolve], tools = union([hole01, hole02]))
solid002 = subtract([solid001], tools = union([hole03, hole04]))
solid003 = subtract([solid002], tools = union([hole05, hole06]))
solid004 = subtract([solid003], tools = union([hole07, hole08]))

View File

@ -0,0 +1,67 @@
// Cold Plate
// A cold plate is a thermal management device used to remove heat from a device or component, typically by transferring heat to a liquid coolant that flows through the plate. It's a conductive cooling solution, commonly made of materials like aluminum or copper, with internal channels or tubes for the coolant
// Set units
@settings(defaultLengthUnit = in)
// Define parameters
tubeDiameter = 5 / 8
wallThickness = 0.080
bendRadius = 1
// Create the cold plate with indentions to secure each pass of the brazed copper tube
coldPlate = startSketchOn(YZ)
|> startProfile(at = [0, tubeDiameter * 2])
|> xLine(length = bendRadius - (tubeDiameter / 2))
|> yLine(length = -tubeDiameter)
|> tangentialArc(angle = 180, radius = tubeDiameter / 2)
|> yLine(length = tubeDiameter)
|> xLine(length = bendRadius * 2 - tubeDiameter, tag = $seg07)
|> yLine(length = -tubeDiameter, tag = $seg09)
|> tangentialArc(angle = 180, radius = tubeDiameter / 2)
|> yLine(length = tubeDiameter, tag = $seg08)
|> xLine(length = bendRadius - (tubeDiameter / 2))
|> angledLine(angle = -77, length = tubeDiameter / 3)
|> tangentialArc(angle = 77, radius = tubeDiameter, tag = $seg01)
|> angledLine(angle = tangentToEnd(seg01), length = 1)
|> yLine(endAbsolute = 0)
|> xLine(endAbsolute = 0)
|> mirror2d(axis = Y)
|> close()
|> extrude(length = 10, symmetric = true)
// Sketch the path for the copper tube to follow
copperTubePath = startSketchOn(offsetPlane(XY, offset = tubeDiameter))
|> startProfile(at = [-7.35, -bendRadius * 3])
|> xLine(length = 14.13, tag = $seg05)
|> tangentialArc(angle = 180, radius = bendRadius, tag = $seg02)
|> angledLine(angle = tangentToEnd(seg02), length = 13.02, tag = $seg06)
|> tangentialArc(angle = -180, radius = bendRadius, tag = $seg03)
|> angledLine(angle = tangentToEnd(seg03), length = segLen(seg06))
|> tangentialArc(angle = 180, radius = bendRadius, tag = $seg04)
|> angledLine(angle = tangentToEnd(seg04), length = segLen(seg05))
// Create the profile for the inner and outer diameter of the hollow copper tube
tubeWall = startSketchOn(offsetPlane(YZ, offset = -7.35))
|> circle(center = [-bendRadius * 3, tubeDiameter], radius = tubeDiameter / 2)
|> subtract2d(%, tool = circle(center = [-bendRadius * 3, tubeDiameter], radius = tubeDiameter / 2 - wallThickness))
|> sweep(path = copperTubePath)
|> appearance(color = "#b81b0a")
// Model a brazed cap to cover each tube. Constrain the caps using the walls of the plate
brazedCap = startSketchOn(YZ)
|> startProfile(at = segEnd(seg07))
|> arc(interiorAbsolute = [bendRadius * 3, tubeDiameter * 1.85], endAbsolute = segEnd(seg08))
|> yLine(endAbsolute = segStartY(seg08))
|> arc(
interiorAbsolute = [
bendRadius * 3,
segEndY(seg09) + tubeDiameter / 2
],
endAbsolute = segEnd(seg09),
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> extrude(length = 10, symmetric = true)
|> patternLinear3d(instances = 4, distance = bendRadius * 2, axis = [0, -1, 0])
|> appearance(color = "#6b261e")

View File

@ -0,0 +1,120 @@
// Counterdrilled Weldment
// A metal weldment consisting of a counterdrilled plate, a centrally mounted housing tube, and four structural support fins.
// Set units
@settings(defaultLengthUnit = in)
// Define parameters
boltSpacingX = 5
boltSpacingY = 3
boltDiameter = 1 / 4
counterdrillDiameter = 7 / 16
counterdrillDepth = 3 / 16
tubeInnerDiameter = 1 + 1 / 4
tubeThickness = 0.115
tubeHeight = 2
stockThickness = .5
// Calculate the dimensions of the block using the specified bolt spacing. The size of the block can be defined by adding a multiple of the counterdrill diameter to the bolt spacing
blockLength = boltSpacingX + boltDiameter * 6
blockWidth = boltSpacingY + boltDiameter * 6
// Draw the base plate
plateSketch = startSketchOn(XY)
|> startProfile(at = [-blockLength / 2, -blockWidth / 2])
|> angledLine(angle = 0, length = blockLength, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = blockWidth, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|> close()
|> subtract2d(tool = circle(center = [0, 0], radius = tubeInnerDiameter / 2))
plateBody = extrude(plateSketch, length = stockThickness)
|> chamfer(
length = boltDiameter * 2,
tags = [
getNextAdjacentEdge(rectangleSegmentB001),
getNextAdjacentEdge(rectangleSegmentA001),
getNextAdjacentEdge(rectangleSegmentC001),
getNextAdjacentEdge(rectangleSegmentD001)
],
)
// Define hole positions
holePositions = [
[-boltSpacingX / 2, -boltSpacingY / 2],
[-boltSpacingX / 2, boltSpacingY / 2],
[boltSpacingX / 2, -boltSpacingY / 2],
[boltSpacingX / 2, boltSpacingY / 2]
]
// Function to create a counterdrilled hole
fn counterdrill(@holePosition) {
cbdrill = startSketchOn(plateBody, face = END)
|> circle(center = holePosition, radius = counterdrillDiameter / 2)
|> extrude(length = -counterdrillDepth)
cbBolt = startSketchOn(cbdrill, face = START)
|> circle(center = holePosition, radius = boltDiameter / 2, tag = $hole01)
|> extrude(length = -stockThickness + counterdrillDepth)
// Use a chamfer to create a 90-degree counterdrill edge
|> chamfer(length = (counterdrillDiameter - boltDiameter) / 2 * sqrt(2), tags = [hole01])
return { }
}
// Place a counterdrilled hole at each bolt hole position
map(holePositions, f = counterdrill)
// Drill a small pin hole in the side of the tube
pinhole = startSketchOn(YZ)
|> circle(center = [0, 2.2], radius = 0.125)
|> extrude(length = -10)
// Model the central tube and subtract the pin hole
centralTube = startSketchOn(offsetPlane(XY, offset = stockThickness))
|> circle(center = [0, 0], radius = tubeInnerDiameter / 2 + tubeThickness)
|> subtract2d(tool = circle(center = [0, 0], radius = tubeInnerDiameter / 2))
|> extrude(length = tubeHeight)
|> subtract(tools = [pinhole])
// Create a function to create a fin which spans from the central tube to the bolt hole
fn fin(@i) {
diagPlane = {
origin = [0.0, 0.0, 0.0],
xAxis = [
boltSpacingX / 2 * i,
boltSpacingY / 2,
0.0
],
yAxis = [0.0, 0.0, 1.0]
}
finSketch = startSketchOn(diagPlane)
|> startProfile(at = [
tubeInnerDiameter / 2 + tubeThickness,
stockThickness
])
|> xLine(endAbsolute = sqrt((boltSpacingX / 2) ^ 2 + (boltSpacingY / 2) ^ 2) - counterdrillDiameter)
|> yLine(length = 0.15)
|> line(endAbsolute = [
profileStartX(%) + 0.15,
stockThickness + tubeHeight * .8
])
|> xLine(length = -0.15)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> extrude(length = tubeThickness, symmetric = true)
// Use a circular pattern to create an identical fin on the opposite side
otherFin = patternCircular3d(
finSketch,
instances = 2,
axis = [0, 0, 1],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
return { }
}
// Place a pair of support fins along each diagonal axis of the bolt pattern
fin(1)
fin(-1)

View File

@ -0,0 +1,50 @@
// Plate with countersunk holes
// A small mounting plate with a countersunk hole at each end
// Set units
@settings(defaultLengthUnit = in)
// Define parameters
boltSpacing = 5
boltDiameter = 1 / 4
centerHoleDiameter = 1 + 3 / 4
plateThickness = 0.375
// Check that the plate is thick enough to countersink a hole
// assertGreaterThan(plateThickness, boltDiameter, "This plate is not thick enough for the necessary countersink dimensions")
// A bit of math to calculate the tangent line between the two diameters
r1 = centerHoleDiameter / 2 * 1.5 + .35
r2 = boltDiameter * 2 + .25
d = boltSpacing / 2
tangentAngle = asin((r1 - r2) / d)
tangentLength = (r1 - r2) / tan(tangentAngle)
plateBody = startSketchOn(XY)
// Use polar coordinates to start the sketch at the tangent point of the larger radius
|> startProfile(at = polar(angle = 90 - tangentAngle, length = r1))
|> angledLine(angle = -tangentAngle, length = tangentLength)
|> tangentialArc(radius = r2, angle = (tangentAngle - 90) * 2)
|> angledLine(angle = tangentAngle, length = -tangentLength)
|> tangentialArc(radius = r1, angle = -tangentAngle * 2)
|> angledLine(angle = -tangentAngle, length = -tangentLength)
|> tangentialArc(radius = r2, angle = (tangentAngle - 90) * 2)
|> angledLine(angle = tangentAngle, length = tangentLength)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> subtract2d(tool = circle(center = [0, 0], radius = centerHoleDiameter / 2 * 1.5))
|> extrude(%, length = plateThickness)
// Function to create a countersunk hole
fn countersink(@holePosition) {
startSketchOn(plateBody, face = END)
|> circle(center = [holePosition, 0], radius = boltDiameter / 2, tag = $hole01)
|> extrude(length = -plateThickness)
// Use a chamfer to create a 90-degree countersink
|> chamfer(length = boltDiameter, tags = [hole01])
return { }
}
// Place a countersunk hole at each bolt hole position
countersink(-boltSpacing / 2)
countersink(boltSpacing / 2)

View File

@ -0,0 +1,153 @@
// Fan Housing
// The plastic housing that contains the fan and the motor
// Set units
@settings(defaultLengthUnit = mm)
// Import parameters
import * from "parameters.kcl"
// Model the housing which holds the motor, the fan, and the mounting provisions
// Bottom mounting face
bottomFaceSketch = startSketchOn(YZ)
|> startProfile(at = [-fanSize / 2, -fanSize / 2])
|> angledLine(angle = 0, length = fanSize, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = fanSize, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|> close()
|> subtract2d(tool = circle(center = [0, 0], radius = 4))
|> subtract2d(tool = circle(
center = [
mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
-mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
-mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> extrude(length = 4)
// Add large openings to the bottom face to allow airflow through the fan
airflowPattern = startSketchOn(bottomFaceSketch, face = END)
|> startProfile(at = [fanSize * 7 / 25, -fanSize * 9 / 25])
|> angledLine(angle = 140, length = fanSize * 12 / 25, tag = $seg01)
|> tangentialArc(radius = fanSize * 1 / 50, angle = 90)
|> angledLine(angle = -130, length = fanSize * 8 / 25)
|> tangentialArc(radius = fanSize * 1 / 50, angle = 90)
|> angledLine(angle = segAng(seg01) + 180, length = fanSize * 2 / 25)
|> tangentialArc(radius = fanSize * 8 / 25, angle = 40)
|> xLine(length = fanSize * 3 / 25)
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> patternCircular2d(
instances = 4,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
|> extrude(length = -4)
// Create the middle segment of the fan housing body
housingMiddleLength = fanSize / 3
housingMiddleRadius = fanSize / 3 - 1
bodyMiddle = startSketchOn(bottomFaceSketch, face = END)
|> startProfile(at = [
housingMiddleLength / 2,
-housingMiddleLength / 2 - housingMiddleRadius
])
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> yLine(length = housingMiddleLength)
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> xLine(length = -housingMiddleLength)
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> yLine(length = -housingMiddleLength)
|> tangentialArc(radius = housingMiddleRadius, angle = 90)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> extrude(length = fanHeight - 4 - 4)
// Cut a hole in the body to accommodate the fan
bodyFanHole = startSketchOn(bodyMiddle, face = END)
|> circle(center = [0, 0], radius = fanSize * 23 / 50)
|> extrude(length = -(fanHeight - 4 - 4))
// Top mounting face. Cut a hole in the face to accommodate the fan
topFaceSketch = startSketchOn(bodyMiddle, face = END)
topHoles = startProfile(topFaceSketch, at = [-fanSize / 2, -fanSize / 2])
|> angledLine(angle = 0, length = fanSize, tag = $rectangleSegmentA002)
|> angledLine(angle = segAng(rectangleSegmentA002) + 90, length = fanSize, tag = $rectangleSegmentB002)
|> angledLine(angle = segAng(rectangleSegmentA002), length = -segLen(rectangleSegmentA002), tag = $rectangleSegmentC002)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD002)
|> close()
|> subtract2d(tool = circle(center = [0, 0], radius = fanSize * 23 / 50))
|> subtract2d(tool = circle(
center = [
mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
-mountingHoleSpacing / 2,
mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> subtract2d(tool = circle(
center = [
-mountingHoleSpacing / 2,
-mountingHoleSpacing / 2
],
radius = mountingHoleSize / 2,
))
|> extrude(length = 4)
// Create a housing for the electric motor to sit
motorHousing = startSketchOn(bottomFaceSketch, face = END)
|> circle(center = [0, 0], radius = 11.2)
|> extrude(length = 16)
startSketchOn(motorHousing, face = END)
|> circle(center = [0, 0], radius = 10)
|> extrude(length = -16)
|> appearance(color = "#a55e2c")
|> fillet(
radius = abs(fanSize - mountingHoleSpacing) / 2,
tags = [
getNextAdjacentEdge(rectangleSegmentA001),
getNextAdjacentEdge(rectangleSegmentB001),
getNextAdjacentEdge(rectangleSegmentC001),
getNextAdjacentEdge(rectangleSegmentD001),
getNextAdjacentEdge(rectangleSegmentA002),
getNextAdjacentEdge(rectangleSegmentB002),
getNextAdjacentEdge(rectangleSegmentC002),
getNextAdjacentEdge(rectangleSegmentD002)
],
)

View File

@ -0,0 +1,71 @@
// Fan
// Spinning axial fan that moves airflow
// Set units
@settings(defaultLengthUnit = mm)
// Import parameters
import * from "parameters.kcl"
// Model the center of the fan
fanCenter = startSketchOn(YZ)
|> circle(center = [0, 0], radius = fanHeight / 2, tag = $centerBend)
|> extrude(%, length = fanHeight)
|> fillet(radius = 1.5, tags = [getOppositeEdge(centerBend)])
// Create a function for a lofted fan blade cross section that rotates about the center hub of the fan
fn fanBlade(offsetHeight, startAngle) {
fanBlade = startSketchOn(offsetPlane(YZ, offset = offsetHeight))
|> startProfile(at = [
15 * cos(startAngle),
15 * sin(startAngle)
])
|> arc(angleStart = startAngle, angleEnd = startAngle + 14, radius = 15)
|> arc(
endAbsolute = [
fanSize * 22 / 50 * cos(startAngle - 20),
fanSize * 22 / 50 * sin(startAngle - 20)
],
interiorAbsolute = [
fanSize * 11 / 50 * cos(startAngle + 3),
fanSize * 11 / 50 * sin(startAngle + 3)
],
)
|> arc(
endAbsolute = [
fanSize * 22 / 50 * cos(startAngle - 24),
fanSize * 22 / 50 * sin(startAngle - 24)
],
interiorAbsolute = [
fanSize * 22 / 50 * cos(startAngle - 22),
fanSize * 22 / 50 * sin(startAngle - 22)
],
)
|> arc(
endAbsolute = [profileStartX(%), profileStartY(%)],
interiorAbsolute = [
fanSize * 11 / 50 * cos(startAngle - 5),
fanSize * 11 / 50 * sin(startAngle - 5)
],
)
|> close()
return fanBlade
}
// Loft the fan blade cross sections into a single blade, then pattern them about the fan center
crossSections = [
fanBlade(offsetHeight = 4.5, startAngle = 50),
fanBlade(offsetHeight = (fanHeight - 2 - 4) / 2, startAngle = 30),
fanBlade(offsetHeight = fanHeight - 2, startAngle = 0)
]
bladeLoft = loft(crossSections)
|> patternCircular3d(
instances = 9,
axis = [1, 0, 0],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
[fanCenter, bladeLoft]
|> appearance(color = "#110803")

View File

@ -0,0 +1,171 @@
// Heat Sink
// Conductive metal device made from brazed tubes and fins
// Set units
@settings(defaultLengthUnit = mm)
// Import parameters
import * from "parameters.kcl"
// Draw the sweep path for the outermost tubes
endTubePath = startSketchOn(offsetPlane(YZ, offset = -20))
|> startProfile(at = [fanSize / 4, fanSize + 38])
|> yLine(endAbsolute = bendRadius + 10, tag = $seg01)
|> tangentialArc(radius = bendRadius, angle = -90)
|> xLine(endAbsolute = 0, tag = $seg02)
|> xLine(length = -segLen(seg02))
|> tangentialArc(radius = bendRadius, angle = -90)
|> yLine(length = segLen(seg01))
// Sweep and translate the outermost tube on each end
endTube = startSketchOn(offsetPlane(XY, offset = fanSize + 38))
|> circle(center = [-20, fanSize / 4], radius = 3)
|> subtract2d(tool = circle(center = [-20, fanSize / 4], radius = 2.5))
|> sweep(path = endTubePath)
|> patternCircular3d(
%,
instances = 2,
axis = [0, 0, 1],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = false,
)
// Draw the sweep path for the 4 interior tubes
centerTubePath = startSketchOn(offsetPlane(YZ, offset = -4))
|> startProfile(at = [fanSize / 2.67, fanSize + 38])
|> yLine(endAbsolute = bendRadius + 15 + 10)
|> tangentialArc(radius = bendRadius, angle = -45)
|> angledLine(angle = -135, lengthY = 15)
|> tangentialArc(radius = bendRadius, angle = -45)
|> xLine(endAbsolute = 0, tag = $seg03)
|> xLine(length = -segLen(seg03))
|> tangentialArc(radius = bendRadius, angle = -155)
|> tangentialArc(radius = bendRadius, angle = 65)
|> yLine(endAbsolute = fanSize + 38)
// Draw the profile and sweep the 4 interior tubes
centerTube = startSketchOn(offsetPlane(XY, offset = fanSize + 38))
|> circle(center = [-4, fanSize / 2.67], radius = 3)
|> subtract2d(tool = circle(center = [-4, fanSize / 2.67], radius = 2.5))
|> sweep(path = centerTubePath)
|> patternCircular3d(
%,
instances = 2,
axis = [0, 0, 1],
center = [-4 * 2, 0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
|> patternLinear3d(
%,
instances = 2,
distance = 4 * 4,
axis = [1, 0, 0],
)
// Draw a heat fin with built-in clips to secure the mounting wire. Pattern the fin upwards by the height of the fan
heatFins = startSketchOn(offsetPlane(XY, offset = 45))
|> startProfile(at = [0, -fanSize / 2])
|> xLine(length = 9)
|> angledLine(angle = -60, length = 2.5, tag = $seg04)
|> xLine(length = 0.75)
|> arc(interiorAbsolute = [lastSegX(%) + 1, lastSegY(%) + 1.2], endAbsolute = [lastSegX(%) + 2, lastSegY(%)])
|> xLine(length = 0.75)
|> angledLine(angle = 60, length = segLen(seg04))
|> xLine(endAbsolute = heatSinkDepth / 2 - 3)
|> tangentialArc(angle = 90, radius = 3)
|> yLine(endAbsolute = 0)
|> mirror2d(axis = X)
|> mirror2d(axis = Y)
|> close()
|> extrude(length = 1)
|> patternLinear3d(
%,
instances = 31,
distance = (fanSize - 10) / 30,
axis = [0, 0, 1],
)
// Create the mounting base for the CPU cooler. The base should consist of two pieces that secure around each of the tubes at the bottom
coolerBase = startSketchOn(-XZ)
baseLower = startProfile(coolerBase, at = [0, 10])
|> xLine(length = -0.9)
|> arc(angleStart = 0, angleEnd = -180, radius = 3.1)
|> xLine(length = -1.8)
|> arc(angleStart = 0, angleEnd = -180, radius = 3)
|> xLine(length = -1.8)
|> arc(angleStart = 0, angleEnd = -180, radius = 3)
|> xLine(length = -1.8)
|> xLine(length = -2)
|> yLine(length = -10)
|> xLine(endAbsolute = 0)
|> mirror2d(axis = Y)
|> extrude(length = 2 * segLen(seg02), symmetric = true)
baseUpper = startProfile(coolerBase, at = [0, 10])
|> xLine(length = -0.9)
|> arc(angleStart = 0, angleEnd = 180, radius = 3.1)
|> xLine(length = -1.8)
|> arc(angleStart = 0, angleEnd = 180, radius = 3)
|> xLine(length = -1.8)
|> arc(angleStart = 0, angleEnd = 180, radius = 3)
|> xLine(length = -1.8)
|> xLine(length = -1)
|> yLine(length = 4)
|> tangentialArc(angle = -90, radius = 2)
|> xLine(endAbsolute = 0)
|> mirror2d(axis = Y)
|> extrude(length = 2 * segLen(seg02) * 3 / 4, symmetric = true)
// Create a flexible mounting bracket to secure the heat sink to the motherboard once adhered
mountingBracket = startSketchOn(XZ)
|> startProfile(at = [-10, 16])
|> xLine(length = -20)
|> tangentialArc(angle = 20, radius = bendRadius)
|> angledLine(angle = -160, length = 14, tag = $seg09)
|> tangentialArc(angle = -30, radius = bendRadius + sheetThickness)
|> angledLine(angle = 170, length = 21.5, tag = $seg04Q)
|> angledLine(angle = 170 - 90, length = sheetThickness, tag = $seg08)
|> angledLine(angle = segAng(seg04Q) + 180, length = segLen(seg04Q), tag = $seg05E)
|> tangentialArc(angle = 30, radius = bendRadius)
|> angledLine(angle = segAng(seg09) + 180, length = segLen(seg09))
|> tangentialArc(angle = -20, radius = bendRadius + sheetThickness)
|> xLine(endAbsolute = profileStartX(%))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg07)
|> close()
|> extrude(
length = 16,
symmetric = true,
tagEnd = $capEnd001,
tagStart = $capStart001,
)
|> fillet(
radius = 2,
tags = [
getCommonEdge(faces = [seg07, capEnd001]),
getCommonEdge(faces = [seg08, capEnd001]),
getCommonEdge(faces = [seg08, capStart001]),
getCommonEdge(faces = [seg07, capStart001])
],
)
// Create a clearance hole in the bracket for an M3 screw and countersink the hole
thruHole = startSketchOn(mountingBracket, face = seg05E)
|> circle(center = [70, 0], radius = 3.4 / 2, tag = $seg06E)
|> extrude(length = -sheetThickness)
|> chamfer(
length = sheetThickness * 0.75,
tags = [
getCommonEdge(faces = [seg05E, seg06E])
],
)
// Duplicate the bracket to the other side of the heat sink base
|> patternCircular3d(
instances = 2,
axis = [0, 0, 1],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)

View File

@ -0,0 +1,51 @@
// CPU Cooler
// A CPU cooler is a device designed to dissipate heat generated by the CPU of a computer. They consist of a brazed heat sink made from aluminum or copper alloys, and one or two axial fans to move airflow across the heat sink.
// Set units
@settings(defaultLengthUnit = mm)
// Import all parts and parameters into assembly file
import * from "parameters.kcl"
import "fan-housing.kcl" as fanHousing
import "motor.kcl" as motor
import "fan.kcl" as fan
import "heat-sink.kcl" as heatSink
import "mounting-wire.kcl" as mountingWire
import "removable-sticker.kcl" as removableSticker
// Produce the model for each imported part
heatSink
fn translatePart(part) {
part
|> translate(x = heatSinkDepth / 2, z = 40 + fanSize / 2)
|> patternLinear3d(
%,
instances = 2,
distance = heatSinkDepth + fanHeight,
axis = [-1, 0, 0],
)
return { }
}
translatePart(part = fanHousing)
translatePart(part = motor)
translatePart(part = fan)
mountingWire
|> patternCircular3d(
%,
instances = 2,
axis = [0, 1, 0],
center = [0, 0, 40 + fanSize / 2],
arcDegrees = 360,
rotateDuplicates = true,
)
|> patternCircular3d(
%,
instances = 2,
axis = [0, 0, 1],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
removableSticker

View File

@ -0,0 +1,21 @@
// Motor
// A small electric motor to power the fan
// Set Units
@settings(defaultLengthUnit = mm)
// Import Parameters
import * from "parameters.kcl"
// Model the motor body and stem
startPlane = offsetPlane(YZ, offset = 4)
motorBody = startSketchOn(startPlane)
|> circle(center = [0, 0], radius = 10, tag = $seg04)
|> extrude(length = 17)
|> fillet(radius = 2, tags = [getOppositeEdge(seg04), seg04])
|> appearance(color = "#021b55")
motorStem = startSketchOn(offsetPlane(YZ, offset = 21))
|> circle(center = [0, 0], radius = 1)
|> extrude(length = 3.8)
|> appearance(color = "#cbcccd")
[motorBody, motorStem]

View File

@ -0,0 +1,79 @@
// Mounting Wire
// The flexible metal wire used to clip the fans onto the heat sink
// Set units
@settings(defaultLengthUnit = mm)
// Import parameters
import * from "parameters.kcl"
// Draw the XZ component of the mounting wire path
upperArm = startSketchOn(offsetPlane(XZ, offset = fanSize / 2 + 2))
|> startProfile(at = [-12, 40 + fanSize / 2])
|> yLine(length = 7)
|> tangentialArc(radius = 2, angle = 90)
|> xLine(length = -9)
|> tangentialArc(radius = 2, angle = -90)
|> yLine(length = 14)
|> tangentialArc(radius = 2, angle = 90)
|> xLine(length = -9)
|> tangentialArc(radius = 2, angle = -80)
|> angledLine(angle = 100, endAbsoluteY = 40 + fanSize / 2 + mountingHoleSpacing / 2 - 1.5)
|> tangentialArc(radius = 2, angle = 80, tag = $seg07)
// Draw the XZ component of the mounting wire path
lowerArm = startSketchOn(offsetPlane(XZ, offset = fanSize / 2 + 2))
|> startProfile(at = [-12, 40 + fanSize / 2])
|> yLine(length = -7)
|> tangentialArc(radius = 2, angle = -90)
|> xLine(length = -9)
|> tangentialArc(radius = 2, angle = 90)
|> yLine(length = -14)
|> tangentialArc(radius = 2, angle = -90)
|> xLine(length = -9)
|> tangentialArc(radius = 2, angle = 80)
|> angledLine(angle = -100, endAbsoluteY = 40 + fanSize / 2 - (mountingHoleSpacing / 2) + 1.5)
|> tangentialArc(radius = 2, angle = -80, tag = $seg08)
// Create the profile of the mounting wire and sweep along the XZ path
wireProfile = startSketchOn(offsetPlane(XY, offset = 40 + fanSize / 2))
sweepUpperArm = circle(wireProfile, center = [-12, -fanSize / 2 - 2], radius = 1)
|> sweep(%, path = upperArm)
sweepLowerArm = circle(wireProfile, center = [-12, -fanSize / 2 - 2], radius = 1)
|> sweep(%, path = lowerArm)
// Draw the XY components of the mounting wire path
upperHook = startSketchOn(offsetPlane(XY, offset = segEndY(seg07)))
|> startProfile(at = [segEndX(seg07), -fanSize / 2 - 2])
|> xLine(endAbsolute = -heatSinkDepth / 2 - fanHeight)
|> tangentialArc(radius = 2, angle = -90)
|> yLine(endAbsolute = -mountingHoleSpacing / 2 - 2)
|> tangentialArc(radius = 2, angle = -90)
|> xLine(length = fanHeight / 3)
// Draw the XY components of the mounting wire path
lowerHook = startSketchOn(offsetPlane(XY, offset = segEndY(seg08)))
|> startProfile(at = [segEndX(seg07), -fanSize / 2 - 2])
|> xLine(endAbsolute = -heatSinkDepth / 2 - fanHeight)
|> tangentialArc(radius = 2, angle = -90)
|> yLine(endAbsolute = -mountingHoleSpacing / 2 - 2)
|> tangentialArc(radius = 2, angle = -90)
|> xLine(length = fanHeight / 3)
// Sweep the wire profile around the hook-shaped segments of the mounting wire
hookProfile = startSketchOn(offsetPlane(YZ, offset = segEndX(seg07)))
sweepUpperHook = circle(hookProfile, center = [-fanSize / 2 - 2, segEndY(seg07)], radius = 1)
|> sweep(%, path = upperHook)
sweepLowerHook = circle(hookProfile, center = [-fanSize / 2 - 2, segEndY(seg08)], radius = 1)
|> sweep(%, path = lowerHook)
// Union each piece of the wire into a single continuous sweep
[
sweepLowerArm,
sweepLowerHook,
sweepUpperArm,
sweepUpperHook
]
|> appearance(color = "#0d0d0d")

View File

@ -0,0 +1,13 @@
// Global parameters for the CPU cooler
// Set units
@settings(defaultLengthUnit = mm)
// Define Parameters
export fanSize = 120
export fanHeight = 25
export mountingHoleSpacing = 105
export mountingHoleSize = 4.5
export bendRadius = 15
export sheetThickness = 2.125
export heatSinkDepth = 55

View File

@ -0,0 +1,25 @@
// Removable Sticker
// Protective sticker to be removed before adhering the heat sink to the CPU
// Set units
@settings(defaultLengthUnit = mm)
// Create a simple body to represent the removable warning sticker. Brightly color the sticker so that the user will not forget to remove it before installing the device
removableSticker = startSketchOn(-XY)
|> startProfile(at = [-12, -12])
|> angledLine(angle = 0, length = 24, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = 24, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|> close()
|> extrude(length = .3)
|> appearance(color = "#021b55")
|> chamfer(
length = 3,
tags = [
getNextAdjacentEdge(rectangleSegmentA001),
getNextAdjacentEdge(rectangleSegmentB001),
getNextAdjacentEdge(rectangleSegmentC001),
getNextAdjacentEdge(rectangleSegmentD001)
],
)

View File

@ -15,7 +15,7 @@ holeDia = 4
sketch001 = startSketchOn(XY)
|> startProfile(at = [0, 0])
|> angledLine(angle = 0, length = width, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = length, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length, tag = $rectangleSegmentB001)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD001)
|> close()
@ -74,7 +74,7 @@ function001([
sketch003 = startSketchOn(XY)
|> startProfile(at = [width * 1.2, 0])
|> angledLine(angle = 0, length = width, tag = $rectangleSegmentA002)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length = length, tag = $rectangleSegmentB002)
|> angledLine(angle = segAng(rectangleSegmentA001) + 90, length, tag = $rectangleSegmentB002)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC002)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $rectangleSegmentD002)
|> close()

View File

@ -1,112 +0,0 @@
// Spur Gear
// A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear.
// Set units
@settings(defaultLengthUnit = in, kclVersion = 1.0)
// Define parameters
nTeeth = 21
module = 0.5
pitchDiameter = module * nTeeth
pressureAngle = 20
addendum = module
deddendum = 1.25 * module
baseDiameter = pitchDiameter * cos(pressureAngle)
tipDiameter = pitchDiameter + 2 * module
gearHeight = 3
// Interpolate points along the involute curve
cmo = 101
rs = map(
[0..cmo],
f = fn(@i) {
return baseDiameter / 2 + i / cmo * (tipDiameter - baseDiameter) / 2
},
)
// Calculate operating pressure angle
angles = map(
rs,
f = fn(@r) {
return units::toDegrees(acos(baseDiameter / 2 / r))
},
)
// Calculate the involute function
invas = map(
angles,
f = fn(@a) {
return tan(a) - units::toRadians(a)
},
)
// Map the involute curve
xs = map(
[0..cmo],
f = fn(@i) {
return rs[i] * cos(invas[i]: number(rad))
},
)
ys = map(
[0..cmo],
f = fn(@i) {
return rs[i] * sin(invas[i]: number(rad))
},
)
// Extrude the gear body
body = startSketchOn(XY)
|> circle(center = [0, 0], radius = baseDiameter / 2)
|> extrude(length = gearHeight)
toothAngle = 360 / nTeeth / 1.5
// Plot the involute curve
fn leftInvolute(@i, accum) {
j = 100 - i // iterate backwards
return line(accum, endAbsolute = [xs[j], ys[j]])
}
fn rightInvolute(@i, accum) {
x = rs[i] * cos(-toothAngle + units::toDegrees(atan(ys[i] / xs[i])))
y = -rs[i] * sin(-toothAngle + units::toDegrees(atan(ys[i] / xs[i])))
return line(accum, endAbsolute = [x, y])
}
// Draw gear teeth
start = startSketchOn(XY)
|> startProfile(at = [xs[101], ys[101]])
teeth = reduce([0..100], initial = start, f = leftInvolute)
|> arc(angleStart = 0, angleEnd = toothAngle, radius = baseDiameter / 2)
|> reduce([1..101], initial = %, f = rightInvolute)
|> close()
|> extrude(length = gearHeight)
|> patternCircular3d(
axis = [0, 0, 1],
center = [0, 0, 0],
instances = nTeeth,
arcDegrees = 360,
rotateDuplicates = true,
)
// Define the constants of the keyway and the bore hole
keywayWidth = 0.250
keywayDepth = keywayWidth / 2
holeDiam = 2
holeRadius = 1
startAngle = asin(keywayWidth / 2 / holeRadius)
// Sketch the keyway and center hole and extrude
keyWay = startSketchOn(body, face = END)
|> startProfile(at = [
holeRadius * cos(startAngle),
holeRadius * sin(startAngle)
])
|> xLine(length = keywayDepth)
|> yLine(length = -keywayWidth)
|> xLine(length = -keywayDepth)
|> arc(angleStart = -1 * units::toDegrees(startAngle) + 360, angleEnd = 180, radius = holeRadius)
|> arc(angleStart = 180, angleEnd = units::toDegrees(startAngle), radius = holeRadius)
|> close()
|> extrude(length = -gearHeight)

View File

@ -0,0 +1,118 @@
// Claw Hammer
// Often used in construction, a claw hammer is a levered metal hand tool that is used to strike and extract nails
// Set units
@settings(defaultLengthUnit = in)
// Sketch the side profile of the hammer head
headSideProfile = startSketchOn(XZ)
|> startProfile(at = [0.33, 11.26])
|> yLine(length = 0.1)
|> tangentialArc(endAbsolute = [0.95, 11.92])
|> tangentialArc(endAbsolute = [2.72, 11.26], tag = $seg01)
|> angledLine(angle = tangentToEnd(seg01) + 90, length = .2)
|> angledLine(angle = tangentToEnd(seg01) - 10, length = -0.5)
|> tangentialArc(endAbsolute = [-0.91, 12.78], tag = $seg03)
|> tangentialArc(endAbsolute = [-1.67, 12.85])
|> xLine(length = -.25)
|> tangentialArc(angle = 90, radius = .05)
|> yLine(length = -1.125, tag = $seg02)
|> tangentialArc(angle = 90, radius = .05)
|> xLine(length = .25, tag = $seg04)
|> angledLine(angle = 23, length = 0.1)
|> tangentialArc(endAbsolute = [-0.33, profileStartY(%)])
|> xLine(endAbsolute = profileStartX(%))
|> close()
|> extrude(length = 3, symmetric = true)
// Sketch the top profile of the hammer head
headTopProfile = startSketchOn(offsetPlane(XY, offset = 13))
leftSideCut = startProfile(headTopProfile, at = [-4, -1.6])
|> line(endAbsolute = [segEndX(seg02), -segLen(seg02) / 2])
|> arc(
%,
angleStart = 180,
angleEnd = 270,
radius = .05,
)
|> xLine(endAbsolute = segEndX(seg04))
|> arc(interiorAbsolute = [segEndX(seg03) - .1, lastSegY(%) + .03], endAbsolute = [segEndX(seg03), lastSegY(%)])
|> tangentialArc(endAbsolute = [3.39, -1.15])
|> yLine(endAbsolute = profileStartY(%))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> extrude(length = -14)
rearCut = startProfile(headTopProfile, at = [3.39, -0.56])
|> angledLine(angle = 177, length = 0.1)
|> tangentialArc(endAbsolute = [1.86, -0.37])
|> tangentialArc(endAbsolute = [lastSegX(%), -lastSegY(%)])
|> tangentialArc(endAbsolute = [profileStartX(%), -profileStartY(%)])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> extrude(length = -14)
rightSideCut = startProfile(headTopProfile, at = [-4, 1.6])
|> line(endAbsolute = [segEndX(seg02), segLen(seg02) / 2])
|> arc(
%,
angleStart = -180,
angleEnd = -270,
radius = .05,
)
|> xLine(endAbsolute = segEndX(seg04))
|> arc(interiorAbsolute = [segEndX(seg03) - .1, lastSegY(%) - .03], endAbsolute = [segEndX(seg03), lastSegY(%)])
|> tangentialArc(endAbsolute = [3.39, 1.15])
|> yLine(endAbsolute = profileStartY(%))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> extrude(length = -14)
// Subtract the top profiles from the side profile to create a CSG hammer shape
firstProfiles = subtract(
[headSideProfile],
tools = [
union([
leftSideCut,
union([rearCut, rightSideCut])
])
],
)
// Extrude a polygon through the center of the hammer head to create the mounting hole for the handle
handleHole = startSketchOn(XY)
|> polygon(
%,
radius = .28,
numSides = 10,
center = [0, 0],
)
|> extrude(length = 14)
// Add an additional fillet feature to support the handle, and union it to the rest of the head
baseSupport = startSketchOn(offsetPlane(XY, offset = 11.5))
|> circle(center = [0, 0], radius = .45, tag = $seg05)
|> extrude(length = 1, tagStart = $capStart001)
|> fillet(
radius = .05,
tags = [
getCommonEdge(faces = [seg05, capStart001])
],
)
// Union all pieces into a single solid, then cut the handle hole
hammerHead = union([firstProfiles, baseSupport])
|> subtract(tools = [handleHole])
// Draw a profile for the handle, then revolve around the center axis
handleSketch = startSketchOn(XZ)
|> startProfile(at = [0.01, 0])
|> xLine(length = 1.125 / 2)
|> tangentialArc(angle = 90, radius = 0.05)
|> tangentialArc(endAbsolute = [0.38, 12.8 / 1.612])
|> tangentialArc(endAbsolute = [0.28, 12.8])
|> xLine(endAbsolute = profileStartX(%))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
handle = revolve(handleSketch, angle = 360, axis = Y)
|> appearance(color = "#f14f04")

View File

@ -0,0 +1,99 @@
// Helical Gear
// A helical gear is a type of cylindrical gear where the teeth are slanted at an angle relative to the axis of rotation. This greatly reduces noise and wear when transmitting torque across meshed spinning gears
// Set units
@settings(defaultLengthUnit = mm)
// Define a function to create a helical gear
fn helicalGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) {
// Calculate gear parameters
pitchDiameter = module * nTeeth
addendum = module
deddendum = 1.25 * module
baseDiameter = pitchDiameter * cos(pressureAngle)
tipDiameter = pitchDiameter + 2 * module
// Define the constants of the keyway and the bore hole
keywayWidth = 2
keywayDepth = keywayWidth / 2
holeDiam = 7
holeRadius = holeDiam / 2
startAngle = asin(keywayWidth / 2 / holeRadius)
// Sketch the keyway and center hole
holeWithKeyway = startSketchOn(XY)
|> startProfile(at = [
holeRadius * cos(startAngle),
holeRadius * sin(startAngle)
])
|> xLine(length = keywayDepth)
|> yLine(length = -keywayWidth)
|> xLine(length = -keywayDepth)
|> arc(angleStart = -1 * startAngle + 360, angleEnd = 180, radius = holeRadius)
|> arc(angleStart = 180, angleEnd = startAngle, radius = holeRadius)
|> close()
// Define a function to create a rotated gear sketch on an offset plane
fn helicalGearSketch(offsetHeight) {
// Calculate the amount to rotate each planar sketch of the gear given the gear helix angle and total gear height
helixCalc = acos(offsetHeight * tan(helixAngle) / (tipDiameter / 2))
// Using the gear parameters, sketch an involute tooth spanning from the base diameter to the tip diameter
helicalGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|> involuteCircular(
startRadius = baseDiameter / 2,
endRadius = tipDiameter / 2,
angle = helixCalc,
tag = $seg01,
)
|> line(endAbsolute = polar(angle = 160 / nTeeth + helixCalc, length = tipDiameter / 2))
|> involuteCircular(
startRadius = baseDiameter / 2,
endRadius = tipDiameter / 2,
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
reverse = true,
)
// Position the end line of the sketch at the start of the next tooth
|> line(endAbsolute = polar(angle = 360 / nTeeth + helixCalc, length = baseDiameter / 2))
// Pattern the sketch about the center by the specified number of teeth, then close the sketch
|> patternCircular2d(
%,
instances = nTeeth,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
|> close()
|> subtract2d(tool = holeWithKeyway)
return helicalGearSketch
}
// Draw a gear sketch on the base plane
gearSketch001 = helicalGearSketch(offsetHeight = 0)
// Draw a rotated gear sketch on a middle interstitial plane
gearSketch002 = helicalGearSketch(offsetHeight = gearHeight / 2)
// Draw a rotated gear sketch at the gear height offset plane
gearSketch003 = helicalGearSketch(offsetHeight = gearHeight)
// Loft each rotated gear sketch together to form a helical gear
helicalGear = loft([
gearSketch001,
gearSketch002,
gearSketch003
])
return helicalGear
}
helicalGear(
nTeeth = 21,
module = 2,
pressureAngle = 20,
helixAngle = 35,
gearHeight = 7,
)

View File

@ -0,0 +1,197 @@
// Helical Planetary Gearset
// A helical planetary gearset is a type of planetary gear system where the teeth of the sun gear, planet gears, and/or ring gear are helical rather than straight. This design allows for smoother, quieter operation, greater load-carrying capacity, and more flexible shaft alignment.
// Set units
@settings(defaultLengthUnit = mm)
// Define a function to create a helical gear
fn helicalGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) {
// Calculate gear parameters
pitchDiameter = module * nTeeth
addendum = module
deddendum = 1.25 * module
baseDiameter = pitchDiameter * cos(pressureAngle)
tipDiameter = pitchDiameter + 2 * module
// Define the constants of the keyway and the bore hole
keywayWidth = 1
keywayDepth = keywayWidth / 2
holeDiam = 7
holeRadius = holeDiam / 2
startAngle = asin(keywayWidth / 2 / holeRadius)
// Sketch the keyway and center hole
holeWithKeyway = startSketchOn(XY)
|> startProfile(at = [
holeRadius * cos(startAngle),
holeRadius * sin(startAngle)
])
|> xLine(length = keywayDepth)
|> yLine(length = -keywayWidth)
|> xLine(length = -keywayDepth)
|> arc(angleStart = -1 * startAngle + 360, angleEnd = 180, radius = holeRadius)
|> arc(angleStart = 180, angleEnd = startAngle, radius = holeRadius)
|> close()
// Define a function to create a rotated gear sketch on an offset plane
fn helicalGearSketch(offsetHeight) {
// Calculate the amount to rotate each planar sketch of the gear given the gear helix angle and total gear height
helixCalc = acos(offsetHeight * tan(helixAngle) / (tipDiameter / 2))
// Using the gear parameters, sketch an involute tooth spanning from the base diameter to the tip diameter
helicalGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|> involuteCircular(
startRadius = baseDiameter / 2,
endRadius = tipDiameter / 2,
angle = helixCalc,
tag = $seg01,
)
|> line(endAbsolute = polar(angle = 160 / nTeeth + helixCalc, length = tipDiameter / 2))
|> involuteCircular(
startRadius = baseDiameter / 2,
endRadius = tipDiameter / 2,
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
reverse = true,
)
// Position the end line of the sketch at the start of the next tooth
|> line(endAbsolute = polar(angle = 360 / nTeeth + helixCalc, length = baseDiameter / 2))
// Pattern the sketch about the center by the specified number of teeth, then close the sketch
|> patternCircular2d(
%,
instances = nTeeth,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
|> close()
|> subtract2d(tool = holeWithKeyway)
return helicalGearSketch
}
// Draw a gear sketch on the base plane
gearSketch001 = helicalGearSketch(offsetHeight = 0)
// Draw a rotated gear sketch on a middle interstitial plane
gearSketch002 = helicalGearSketch(offsetHeight = gearHeight / 2)
// Draw a rotated gear sketch at the gear height offset plane
gearSketch003 = helicalGearSketch(offsetHeight = gearHeight)
// Loft each rotated gear sketch together to form a helical gear
helicalGear = loft([
gearSketch001,
gearSketch002,
gearSketch003
])
return helicalGear
}
// Define a function to create a ring gear
fn ringGear(nTeeth, module, pressureAngle, helixAngle, gearHeight) {
// Calculate gear parameters
pitchDiameter = module * nTeeth
addendum = module
deddendum = 1.25 * module
baseDiameter = pitchDiameter * cos(pressureAngle)
tipDiameter = pitchDiameter + 2 * module
// Define a function to create a rotated gear sketch on an offset plane
fn ringGearSketch(offsetHeight) {
// Calculate the amount to rotate each planar sketch of the gear given the gear helix angle and total gear height
helixCalc = acos(offsetHeight * tan(helixAngle) / (tipDiameter / 2))
// Using the gear parameters, sketch an involute tooth spanning from the base diameter to the tip diameter
ringTeeth = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|> startProfile(at = polar(angle = helixCalc, length = baseDiameter / 2))
|> involuteCircular(
startRadius = baseDiameter / 2,
endRadius = tipDiameter / 2,
angle = helixCalc,
tag = $seg01,
)
|> line(endAbsolute = polar(angle = 200 / nTeeth + helixCalc, length = tipDiameter / 2))
|> involuteCircular(
startRadius = baseDiameter / 2,
endRadius = tipDiameter / 2,
angle = -(4 * atan(segEndY(seg01) / segEndX(seg01)) - (3 * helixCalc)),
reverse = true,
)
// Position the end line of the sketch at the start of the next tooth
|> line(endAbsolute = polar(angle = 360 / nTeeth + helixCalc, length = baseDiameter / 2))
// Pattern the sketch about the center by the specified number of teeth, then close the sketch
|> patternCircular2d(
%,
instances = nTeeth,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
|> close()
// Create a circular body that is larger than the tip diameter of the gear, then subtract the gear profile from the body
ringGearSketch = startSketchOn(offsetPlane(XY, offset = offsetHeight))
|> circle(center = [0, 0], radius = tipDiameter / 1.85)
|> subtract2d(tool = ringTeeth)
return ringGearSketch
}
// Draw a gear sketch on the base plane
gearSketch001 = ringGearSketch(offsetHeight = 0)
// Draw a rotated gear sketch on a middle interstitial plane
gearSketch002 = ringGearSketch(offsetHeight = gearHeight / 2)
// Draw a rotated gear sketch at the gear height offset plane
gearSketch003 = ringGearSketch(offsetHeight = gearHeight)
// Loft each rotated gear sketch together to form a ring gear
ringGear = loft([
gearSketch001,
gearSketch002,
gearSketch003
])
return ringGear
}
// Create the outer ring gear for the planetary gearset
ringGear(
nTeeth = 42,
module = 1.5,
pressureAngle = 14,
helixAngle = -25,
gearHeight = 5,
)
// Create a central sun gear using a small helical gear
helicalGear(
nTeeth = 12,
module = 1.5,
pressureAngle = 14,
helixAngle = 25,
gearHeight = 5,
)
// Create the helical planet gears
numPlanetGears = 3
helicalGear(
nTeeth = 12,
module = 1.5,
pressureAngle = 14,
helixAngle = -25,
gearHeight = 5,
)
|> translate(y = (12 + 12) / 2 * 1.5 + 2.7)
|> patternCircular3d(
instances = numPlanetGears,
axis = [0, 0, 1],
center = [0, 0, 0],
arcDegrees = 360,
rotateDuplicates = false,
)

View File

@ -0,0 +1,123 @@
// Helium Tank
// A helium tank is a portable pressure vessel used to store and dispense helium gas for a variety of commercial and entertainment purposes
// Set units
@settings(defaultLengthUnit = in)
// Define parameters
tankHeight = 2.5ft
tankDiameter = 9
wallThickness = 0.125
portDiameter = 1.25
bracketThickness = 0.090
boltSize = 1 / 4
// Sketch the perimeter of the gas tank- inside and out, then revolve around the vertical axis.
tankSketch = startSketchOn(YZ)
|> startProfile(at = [portDiameter / 2, tankHeight])
|> yLine(length = -0.6)
|> xLine(length = 0.1)
|> tangentialArc(angle = -110, radius = 0.1)
|> tangentialArc(angle = 40, radius = 0.6)
|> tangentialArc(angle = -110, radius = 0.1)
|> tangentialArc(angle = 180, radius = 0.1)
|> tangentialArc(angle = -90, radius = tankDiameter / 2 - lastSegX(%), tag = $seg01)
|> angledLine(angle = tangentToEnd(seg01), endAbsoluteY = 1.5, tag = $seg09)
|> tangentialArc(angle = -90, radius = 2, tag = $seg02)
|> angledLine(angle = tangentToEnd(seg02), endAbsoluteX = 0.001, tag = $seg08)
|> yLine(length = wallThickness)
|> xLine(length = segLen(seg08))
|> tangentialArc(angle = 90, radius = 2 - wallThickness)
|> yLine(length = segLen(seg09))
|> tangentialArc(angle = 90, radius = tankDiameter / 2 - wallThickness - 1.3)
|> xLine(endAbsolute = profileStartX(%) - .1)
|> yLine(endAbsolute = profileStartY(%))
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
// Only revolving to 220deg so that the interior of the tank is visible. It should ultimately be closed at 360deg
tankRevolve = revolve(tankSketch, angle = 220, axis = Y)
// Model the brass valve on top of the tank port
valveBody = startSketchOn(offsetPlane(XY, offset = tankHeight - 0.5))
|> circle(center = [0, 0], radius = portDiameter / 1.9, tag = $seg03)
|> extrude(length = 1.5, tagEnd = $capEnd001)
|> fillet(
radius = 0.1,
tags = [
getCommonEdge(faces = [seg03, capEnd001])
],
)
// Model the outlet port of the valve, then union it all together
valvePort = startSketchOn(YZ)
|> circle(center = [0, tankHeight + 0.3], radius = portDiameter / 3)
|> subtract2d(tool = circle(center = [0, tankHeight + 0.3], radius = portDiameter / 3.25))
|> extrude(length = 1.3)
valve = union([valveBody, valvePort])
|> appearance(color = "#9a4618")
// Sketch the offset profile of the mounting bracket
bracketOffsetProfile = startSketchOn(offsetPlane(XY, offset = tankHeight * 0.67))
|> startProfile(at = [0, tankDiameter / 2 + wallThickness])
|> xLine(length = -0.1)
|> tangentialArc(angle = 35, radius = tankDiameter / 2 + wallThickness)
|> tangentialArc(angle = -135, radius = 0.25 - wallThickness, tag = $seg06)
|> angledLine(angle = tangentToEnd(seg06), length = tankDiameter / 7)
|> tangentialArc(angle = -80, radius = 0.25 - wallThickness, tag = $seg07)
|> angledLine(angle = tangentToEnd(seg07), endAbsoluteX = 0)
|> mirror2d(axis = Y)
|> close()
// Sketch the outer perimeter of the offset bracket, then subtract the inner offset to create a constant thickness sheet metal hoop
bracketProfile = startSketchOn(offsetPlane(XY, offset = tankHeight * 0.67))
|> startProfile(at = [0, tankDiameter / 2])
|> xLine(length = -0.1)
|> tangentialArc(angle = 35, radius = tankDiameter / 2)
|> tangentialArc(angle = -135, radius = 0.25, tag = $seg04)
|> angledLine(angle = tangentToEnd(seg04), length = tankDiameter / 7)
|> tangentialArc(angle = -80, radius = 0.25, tag = $seg05)
|> angledLine(angle = tangentToEnd(seg05), endAbsoluteX = 0)
|> mirror2d(axis = Y)
|> close()
|> subtract2d(tool = bracketOffsetProfile)
|> extrude(length = 1, symmetric = true)
// Cut holes in the bracket for a mounting strap
strapSleeve = startSketchOn(offsetPlane(XY, offset = tankHeight * 0.67))
|> circle(center = [0, .125 / 2], radius = 4.75)
|> subtract2d(tool = circle(center = [0, .125 / 2], radius = 4.65))
|> extrude(length = .8, symmetric = true)
bracketSleeve = subtract([bracketProfile], tools = [strapSleeve])
// Create holes in the bracket for anchor mounts to secure the bracket to a wall
mountingHoles = startSketchOn(offsetPlane(XZ, offset = -tankDiameter / 1.9))
|> circle(center = [tankDiameter / 4.5, tankHeight * 0.67], radius = boltSize / 2)
|> extrude(length = -5)
bracket = subtract(
[bracketSleeve],
tools = union(patternLinear3d(
mountingHoles,
instances = 2,
distance = tankDiameter / 2.25,
axis = [-1, 0, 0],
)),
)
|> appearance(color = "#cd0404")
// Model a circular strap to secure the tank to the bracket
mountingStrap = startSketchOn(offsetPlane(XY, offset = tankHeight * 0.67))
|> circle(center = [0, .155 / 2], radius = tankDiameter / 1.9)
|> subtract2d(tool = circle(center = [0, .155 / 2], radius = tankDiameter / 1.9 - 0.1))
|> extrude(length = .75, symmetric = true)
|> appearance(color = "#210d03")
// Create a second instance of the bracket and strap at a lower point on the tank
[bracket, mountingStrap]
|> patternLinear3d(
%,
instances = 2,
distance = tankHeight * 0.33,
axis = [0, 0, -1],
)

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