Compare commits

...

16 Commits

Author SHA1 Message Date
324b1d0251 pierremtb/adhoc/quick-eval-stream-offset-impact-on-e2e 2025-04-24 15:19:02 -04:00
305d613d40 Nadro/adhoc/system io machine (#6352)
* chore: saving off skeleton

* fix: saving skeleton

* chore: skeleton for loading projects from project directory path

* chore: cleaning up useless state transition to be an on event direct to action state

* fix: new structure for web vs desktop vs react machine provider code

* chore: saving off skeleton

* fix: skeleton logic for react? going to move it from a string to obj.string

* fix: trying to prevent error element unmount on global react components. This is bricking JS state

* fix: we are so back

* chore: implemented navigating to specfic KCL file

* chore: implementing renaming project

* chore: deleting project

* fix: auto fixes

* fix: old debug/testing file oops

* chore: generic create new file

* chore: skeleton for web create file provide

* chore: basic machine vitest... need to figure out how to get window.electron implemented in vitest?

* chore: save off progress before deleting other project implementation, a few missing features still

* chore: trying a different init skeleton? most likely will migrate

* chore: first attempt of purging projects context provider

* chore: enabling toast for some machine state

* chore: enabling more toast success and error

* chore: writing read write state to the system io based on the project path

* fix: tsc fixes

* fix: use file system watcher, navigate to project after creation via the requestProjectName

* chore: open project command, hooks vs snapshot context helpers

* chore: implemented open and create project for e2e testing. They are hard coded in poor spot for now.

* fix: codespell fixes

* chore: implementing more project commands

* chore: PR improvements for root.tsx

* chore: leaving comment about new Router.tsx layout

* fix: removing debugging code

* fix: rewriting component for readability

* fix: improving web initialization

* chore: implementing import file from url which is not actually that?

* fix: clearing search params on import file from url

* fix: fixed two e2e tests, forgot needsReview when making new command

* fix: fixing some import from url business logic to pass e2e tests

* chore: script for diffing circular deps +/-

* fix: formatting

* fix: massive fix for circular depsga!

* fix: trying to fix some errors and auto fmt

* fix: updating deps

* fix: removing debugging code

* fix: big clean up

* fix: more deletion

* fix: tsc cleanup

* fix: TSC TSC TSC TSC!

* fix: typo fix

* fix: clear query params on web only, desktop not required

* fix: removing unused code

* fmt

* Bring back `trap` removed in merge

* Use explicit types instead of `any`s on arg configs

* Add project commands directly to command palette

* fix: deleting debugging code, from PR review

* fix: this got added back(?)

* fix: using referred type

* fix: more PR clean up

* fix: big block comment for xstate architecture decision

* fix: more pr comment fixes

* fix: merge conflict just added them back why dude

* fix: more PR comments

* fix: big ciruclar deps fix, commandBarActor in appActor

---------

Co-authored-by: Frank Noirot <frankjohnson1993@gmail.com>
2025-04-24 13:32:49 -05:00
95f2caacab Fix testing-selections test / clicking on empty space (#6481)
fix clicking on empty space which happened to click on UI, causing the test to fail
2025-04-24 17:46:07 +00:00
8f61ee1d2f Change getOppositeEdge, getNextAdjacentEdge, and getPreviousAdjacentEdge to keyword args (#6469)
* Change getOppositeEdge, getNextAdjacentEdge, and getPreviousAdjacentEdge to keyword args

* Update generated docs
2025-04-24 12:39:37 -04:00
b02dbd4fe6 Kwargs: leg helpers (#6459)
legLen, legAngX, legAngY moved to keyword arguments
2025-04-24 09:53:19 -05:00
668f6671a9 BREAKING: Remove angleToMatchLengthX and angleToMatchLengthY (#6451)
* Remove angleToMatchLengthX and angleToMatchLengthY from constraint code generation

* Change KCL stdlib functions to be deprecated

* Remove references from TS tests

* Remove angleToMatchLengthX and angleToMatchLengthY

* Update docs

* Fix file paths
2025-04-24 14:33:27 +00:00
f6387eb7e9 fmt (#6473) 2025-04-24 13:10:06 +00:00
83a87b046f Declare std::offsetPlane in KCL (#6344)
* Declare std::offsetPlane in KCL

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

* Use two axes to define planes in KCL

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-04-24 22:01:27 +12:00
20c2ce3bac Shorten tronapp setup fail (#6362)
* shorten tron app setup fail

* fmt

* more tweaks to tronapp setup

* bump initial timeout back to 120s

* Update e2e/playwright/zoo-test.ts

---------

Co-authored-by: Zookeeper Lee <lee@zoo.dev>
2025-04-24 06:35:57 +00:00
2956f9ed55 YOU FOOLS I WON THE CONTEST (#6328)
* dodec

* fmt

* comment

* Update kcl-samples simulation test output

* Update kcl-samples simulation test output

* Fix so that just commands regenerate ast output

* overwrite

* Update just command to include manifest

* Update generated output

* merge main post

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-04-24 06:08:45 +00:00
510d74f2c7 Add clone (#5462)
* 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>

update the extrude idds

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

updates

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

fix sample

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

better docs

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

fix the start and end tag

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

better docs

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

updates

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

new tests

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

codespell

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>

* fix examples

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

* fix some stuff

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>

* updates

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

* add another test for fillet

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

* Update rust/kcl-lib/src/std/clone.rs

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

* fixes

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

* add sweep test

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

* revolve test;

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

* Update rust/kcl-lib/src/std/clone.rs

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>

* add another test for fillet

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

* allow cloning an imported geometry;

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

* allow for imported geometry

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

* updates

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

* update docs

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-04-24 04:26:09 +00:00
457ab28f74 Appearance import fixes (#6466)
* fix appearance imports

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

* fix appearance

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-04-24 00:43:16 +00:00
1502f923ee KCL bugfix: CallExpressionKw was not usable as math operand (#6460)
Closes https://github.com/KittyCAD/modeling-app/issues/4992
2025-04-23 21:21:57 +00:00
f03a684eec Modeling view appearance final tweaks (#6425)
* Remove bug button from LowerRightControls

There is a "report a bug" button in the help menus, both native and
lower-right corner. This is overkill, and users are not using coredump
well.

* Remove coredump from refresh UI button

* Add a "Refresh app" command to palette

* Update snapshots

* Rework "Refresh and report bug" menu item to "Report a bug"

* Add refresh button to sidebar

* Convert upper-right refresh button to Share

* Tweak styles of command button

* Make anonymous user icon same size as known user image

* Remove ModelStateIndicator

* Use hotkeyDisplay for the sidebar too

* Update snapshots

* Remove tooltip from command bar open button

* tsc, lint, and fmt
2025-04-23 15:20:45 -04:00
45e17c50e7 docs: Add docs for creating simulation tests (#6453) 2025-04-23 11:59:07 -07:00
6bf74379a7 Kwargs: hollow (#6438) 2025-04-23 18:35:33 +00:00
397 changed files with 33148 additions and 40993 deletions

View File

@ -4,6 +4,7 @@ on:
branches: branches:
- main - main
- all-e2e # this bypasses `fixme()` using `orRunWhenFullSuiteEnabled()` - all-e2e # this bypasses `fixme()` using `orRunWhenFullSuiteEnabled()`
- pierremtb/adhoc/quick-eval-stream-offset-impact-on-e2e
pull_request: pull_request:
schedule: schedule:
- cron: 0 * * * * # hourly - cron: 0 * * * * # hourly

File diff suppressed because one or more lines are too long

258
docs/kcl/clone.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ Get the next adjacent edge to the edge given.
```js ```js
getNextAdjacentEdge(tag: TagIdentifier): Uuid getNextAdjacentEdge(edge: TagIdentifier): Uuid
``` ```
@ -17,7 +17,7 @@ getNextAdjacentEdge(tag: TagIdentifier): Uuid
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes | | `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the next adjacent edge of. | Yes |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Get the opposite edge to the edge given.
```js ```js
getOppositeEdge(tag: TagIdentifier): Uuid getOppositeEdge(edge: TagIdentifier): Uuid
``` ```
@ -17,7 +17,7 @@ getOppositeEdge(tag: TagIdentifier): Uuid
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes | | `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the opposite edge of. | Yes |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Get the previous adjacent edge to the edge given.
```js ```js
getPreviousAdjacentEdge(tag: TagIdentifier): Uuid getPreviousAdjacentEdge(edge: TagIdentifier): Uuid
``` ```
@ -17,7 +17,7 @@ getPreviousAdjacentEdge(tag: TagIdentifier): Uuid
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes | | `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the previous adjacent edge of. | Yes |
### Returns ### Returns

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -32,8 +32,6 @@ layout: manual
* [`Z`](kcl/consts/std-Z) * [`Z`](kcl/consts/std-Z)
* [`abs`](kcl/abs) * [`abs`](kcl/abs)
* [`acos`](kcl/acos) * [`acos`](kcl/acos)
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
* [`angleToMatchLengthY`](kcl/angleToMatchLengthY)
* [`angledLine`](kcl/angledLine) * [`angledLine`](kcl/angledLine)
* [`angledLineThatIntersects`](kcl/angledLineThatIntersects) * [`angledLineThatIntersects`](kcl/angledLineThatIntersects)
* [`appearance`](kcl/appearance) * [`appearance`](kcl/appearance)
@ -47,6 +45,7 @@ layout: manual
* [`ceil`](kcl/ceil) * [`ceil`](kcl/ceil)
* [`chamfer`](kcl/chamfer) * [`chamfer`](kcl/chamfer)
* [`circleThreePoint`](kcl/circleThreePoint) * [`circleThreePoint`](kcl/circleThreePoint)
* [`clone`](kcl/clone)
* [`close`](kcl/close) * [`close`](kcl/close)
* [`extrude`](kcl/extrude) * [`extrude`](kcl/extrude)
* [`fillet`](kcl/fillet) * [`fillet`](kcl/fillet)
@ -74,7 +73,7 @@ layout: manual
* [`map`](kcl/map) * [`map`](kcl/map)
* [`max`](kcl/max) * [`max`](kcl/max)
* [`min`](kcl/min) * [`min`](kcl/min)
* [`offsetPlane`](kcl/offsetPlane) * [`offsetPlane`](kcl/std-offsetPlane)
* [`patternCircular2d`](kcl/patternCircular2d) * [`patternCircular2d`](kcl/patternCircular2d)
* [`patternCircular3d`](kcl/patternCircular3d) * [`patternCircular3d`](kcl/patternCircular3d)
* [`patternLinear2d`](kcl/patternLinear2d) * [`patternLinear2d`](kcl/patternLinear2d)

View File

@ -22,6 +22,5 @@ once fixed in engine will just start working here with no language changes.
chamfer cases work currently. chamfer cases work currently.
- **Appearance**: Changing the appearance on a loft does not work. - **Appearance**: Changing the appearance on a loft does not work.
Changing the appearance on an imported model does not work.
- **CSG Booleans**: Coplanar (bodies that share a plane) unions, subtractions, and intersections are not currently supported. - **CSG Booleans**: Coplanar (bodies that share a plane) unions, subtractions, and intersections are not currently supported.

View File

@ -24,8 +24,8 @@ legAngX(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl/types/number) | | Yes | | `hypotenuse` | [`number`](/docs/kcl/types/number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl/types/number) | | Yes | | `leg` | [`number`](/docs/kcl/types/number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns ### Returns
@ -35,7 +35,7 @@ legAngX(
### Examples ### Examples
```js ```js
legAngX(5, 3) legAngX(hypotenuse = 5, leg = 3)
``` ```

View File

@ -24,8 +24,8 @@ legAngY(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl/types/number) | | Yes | | `hypotenuse` | [`number`](/docs/kcl/types/number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl/types/number) | | Yes | | `leg` | [`number`](/docs/kcl/types/number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns ### Returns
@ -35,7 +35,7 @@ legAngY(
### Examples ### Examples
```js ```js
legAngY(5, 3) legAngY(hypotenuse = 5, leg = 3)
``` ```

View File

@ -24,8 +24,8 @@ legLen(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl/types/number) | | Yes | | `hypotenuse` | [`number`](/docs/kcl/types/number) | The length of the triangle's hypotenuse | Yes |
| `leg` | [`number`](/docs/kcl/types/number) | | Yes | | `leg` | [`number`](/docs/kcl/types/number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
### Returns ### Returns
@ -35,7 +35,7 @@ legLen(
### Examples ### Examples
```js ```js
legLen(5, 3) legLen(hypotenuse = 5, leg = 3)
``` ```

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

122
docs/kcl/std-offsetPlane.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,84 @@
---
title: "GeometryWithImportedGeometry"
excerpt: "A geometry including an imported geometry."
layout: manual
---
A geometry including an imported geometry.
**This schema accepts exactly one of the following:**
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Sketch`](/docs/kcl/types/Sketch)| | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the sketch (this will change when the engine's reference to it changes). | No |
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
| `artifactId` |[`string`](/docs/kcl/types/string)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
| `originalId` |[`string`](/docs/kcl/types/string)| | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the solid. | No |
| `artifactId` |[`string`](/docs/kcl/types/string)| The artifact ID of the solid. Unlike `id`, this doesn't change. | No |
| `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No |
| `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No |
| `height` |[`number`](/docs/kcl/types/number)| The height of the solid. | No |
| `startCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion start cap | No |
| `endCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion end cap | No |
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| The units of the solid. | No |
| `sectional` |`boolean`| Is this a sectional solid? | No |
----
Data for an imported geometry.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ImportedGeometry`| | No |
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
----

View File

@ -30,7 +30,6 @@ A sketch type.
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No | | `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No | | `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y axis be? | No | | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
---- ----
@ -52,7 +51,6 @@ A face.
| `value` |[`string`](/docs/kcl/types/string)| The tag of the face. | No | | `value` |[`string`](/docs/kcl/types/string)| The tag of the face. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's X axis be? | No | | `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No | | `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No | | `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No | | `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |

View File

@ -21,7 +21,7 @@ test.describe(
clickCoords: { x: number; y: number } clickCoords: { x: number; y: number }
) => { ) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1700, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
const XYPlanRed: [number, number, number] = [98, 50, 51] const XYPlanRed: [number, number, number] = [98, 50, 51]

View File

@ -406,6 +406,7 @@ profile003 = startProfileAt([0, -4.93], sketch001)
await fsp.mkdir(testProject, { recursive: true }) await fsp.mkdir(testProject, { recursive: true })
await fsp.writeFile(join(testProject, 'main.kcl'), beforeKclCode, 'utf-8') await fsp.writeFile(join(testProject, 'main.kcl'), beforeKclCode, 'utf-8')
}) })
await page.setBodyDimensions({ width: 1500, height: 500 })
// One dumb hardcoded screen pixel value // One dumb hardcoded screen pixel value
const testPoint = { x: 650, y: 250 } const testPoint = { x: 650, y: 250 }
const sketchColor: [number, number, number] = [149, 149, 149] const sketchColor: [number, number, number] = [149, 149, 149]

View File

@ -409,11 +409,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
) )
.toBe(true) .toBe(true)
}) })
test('Home.Help.Refresh and report a bug', async ({ test('Home.Help.Report a bug', async ({ tronApp, cmdBar, page }) => {
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail() if (!tronApp) fail()
// Run electron snippet to find the Menu! // Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
@ -424,9 +420,8 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
if (!app || !app.applicationMenu) { if (!app || !app.applicationMenu) {
return false return false
} }
const menu = app.applicationMenu.getMenuItemById( const menu =
'Help.Refresh and report a bug' app.applicationMenu.getMenuItemById('Help.Report a bug')
)
if (!menu) return false if (!menu) return false
menu.click() menu.click()
return true return true
@ -2291,7 +2286,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
if (!menu) fail() if (!menu) fail()
}) })
}) })
test('Modeling.Help.Refresh and report a bug', async ({ test('Modeling.Help.Report a bug', async ({
tronApp, tronApp,
cmdBar, cmdBar,
page, page,
@ -2315,9 +2310,8 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
async () => async () =>
await tronApp.electron.evaluate(async ({ app }) => { await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) return false if (!app || !app.applicationMenu) return false
const menu = app.applicationMenu.getMenuItemById( const menu =
'Help.Refresh and report a bug' app.applicationMenu.getMenuItemById('Help.Report a bug')
)
if (!menu) return false if (!menu) return false
menu.click() menu.click()
return true return true

View File

@ -488,6 +488,7 @@ test('Restarting onboarding on desktop takes one attempt', async ({
join(routerTemplateDir, 'main.kcl') join(routerTemplateDir, 'main.kcl')
) )
}) })
await page.setBodyDimensions({ width: 1500, height: 500 })
// Our constants // Our constants
const u = await getUtils(page) const u = await getUtils(page)

View File

@ -87,7 +87,7 @@ test.describe('Point-and-click assemblies tests', () => {
fsp.writeFile(path.join(bracketDir, 'main.kcl'), ''), fsp.writeFile(path.join(bracketDir, 'main.kcl'), ''),
]) ])
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.openProject(projectName) await homePage.openProject(projectName)
await scene.settled(cmdBar) await scene.settled(cmdBar)
await toolbar.closePane('code') await toolbar.closePane('code')
@ -225,7 +225,7 @@ test.describe('Point-and-click assemblies tests', () => {
fsp.writeFile(path.join(bracketDir, 'main.kcl'), ''), fsp.writeFile(path.join(bracketDir, 'main.kcl'), ''),
]) ])
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.openProject(projectName) await homePage.openProject(projectName)
await scene.settled(cmdBar) await scene.settled(cmdBar)
await toolbar.closePane('code') await toolbar.closePane('code')
@ -403,7 +403,7 @@ test.describe('Point-and-click assemblies tests', () => {
fsp.writeFile(path.join(bracketDir, 'main.kcl'), ''), fsp.writeFile(path.join(bracketDir, 'main.kcl'), ''),
]) ])
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.openProject(projectName) await homePage.openProject(projectName)
await scene.settled(cmdBar) await scene.settled(cmdBar)
await toolbar.closePane('code') await toolbar.closePane('code')

View File

@ -203,7 +203,7 @@ test.describe('Point-and-click tests', () => {
await context.addInitScript((file) => { await context.addInitScript((file) => {
localStorage.setItem('persistCode', file) localStorage.setItem('persistCode', file)
}, file) }, file)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -366,7 +366,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
await context.addInitScript((file) => { await context.addInitScript((file) => {
localStorage.setItem('persistCode', file) localStorage.setItem('persistCode', file)
}, file) }, file)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -689,7 +689,7 @@ openSketch = startSketchOn(XY)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
// Wait for the scene and stream to load // Wait for the scene and stream to load
@ -864,7 +864,7 @@ openSketch = startSketchOn(XY)
// Setup // Setup
await test.step(`Initial test setup`, async () => { await test.step(`Initial test setup`, async () => {
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
// Wait for the scene and stream to load // Wait for the scene and stream to load
@ -1215,7 +1215,7 @@ openSketch = startSketchOn(XY)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -1360,7 +1360,7 @@ extrude001 = extrude(profile001, length = 100)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -1496,7 +1496,7 @@ extrude001 = extrude(profile001, length = 100)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
// One dumb hardcoded screen pixel value // One dumb hardcoded screen pixel value
@ -1587,7 +1587,7 @@ loft001 = loft([sketch001, sketch002])
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -1676,7 +1676,7 @@ sketch002 = startSketchOn(XZ)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -1818,7 +1818,7 @@ sketch002 = startSketchOn(XZ)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -1924,7 +1924,7 @@ extrude001 = extrude(sketch001, length = -12)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
// verify modeling scene is loaded // verify modeling scene is loaded
@ -2199,7 +2199,7 @@ fillet001 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -2263,7 +2263,7 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
// verify modeling scene is loaded // verify modeling scene is loaded
@ -2397,7 +2397,7 @@ extrude001 = extrude(profile001, length = 5)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
// verify modeling scene is loaded // verify modeling scene is loaded
@ -2520,7 +2520,7 @@ extrude001 = extrude(sketch001, length = -12)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
}) })
@ -2821,7 +2821,7 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -2946,7 +2946,7 @@ extrude001 = extrude(sketch001, length = 30)
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.connectionEstablished() await scene.connectionEstablished()
@ -3089,7 +3089,7 @@ extrude001 = extrude(sketch001, length = 40)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -3236,7 +3236,7 @@ extrude002 = extrude(sketch002, length = 50)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1700, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -3325,7 +3325,7 @@ profile001 = startProfileAt([-20, 20], sketch001)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
await toolbar.openPane('feature-tree') await toolbar.openPane('feature-tree')
@ -3405,7 +3405,7 @@ sweep001 = sweep(sketch001, path = sketch002)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -3487,7 +3487,7 @@ tag=$rectangleSegmentC002,
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -3565,7 +3565,7 @@ sketch002 = startSketchOn(extrude001, face = rectangleSegmentA001)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.connectionEstablished() await scene.connectionEstablished()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -3656,7 +3656,7 @@ sketch002 = startSketchOn(extrude001, face = rectangleSegmentA001)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.connectionEstablished() await scene.connectionEstablished()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -3736,7 +3736,7 @@ extrude001 = extrude(profile001, length = 100)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -3867,7 +3867,7 @@ extrude001 = extrude(profile001, length = 100)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -4023,7 +4023,7 @@ extrude001 = extrude(profile001, length = 100)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -4152,7 +4152,7 @@ extrude001 = extrude(profile001, length = 1)
await context.addInitScript((initialCode) => { await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode) localStorage.setItem('persistCode', initialCode)
}, initialCode) }, initialCode)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)

View File

@ -400,11 +400,6 @@ test(
await expect(page.getByText('broken-code')).toBeVisible() await expect(page.getByText('broken-code')).toBeVisible()
await page.getByText('broken-code').click() await page.getByText('broken-code').click()
// Gotcha: You can not use scene.settled() since the KCL code is going to fail
await expect(
page.getByTestId('model-state-indicator-playing')
).toBeAttached()
// Gotcha: Scroll to the text content in code mirror because CodeMirror lazy loads DOM content // Gotcha: Scroll to the text content in code mirror because CodeMirror lazy loads DOM content
await editor.scrollToText( await editor.scrollToText(
"|> line(end = [0, wallMountL], tag = 'outerEdge')" "|> line(end = [0, wallMountL], tag = 'outerEdge')"
@ -779,7 +774,9 @@ test.describe(`Project management commands`, () => {
// Constants and locators // Constants and locators
const projectHomeLink = page.getByTestId('project-link') const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' }) const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'rename project' }) const commandOption = page.getByRole('option', {
name: 'rename project',
})
const projectNameOption = page.getByRole('option', { name: projectName }) const projectNameOption = page.getByRole('option', { name: projectName })
const projectRenamedName = `untitled` const projectRenamedName = `untitled`
// const projectMenuButton = page.getByTestId('project-sidebar-toggle') // const projectMenuButton = page.getByTestId('project-sidebar-toggle')
@ -839,7 +836,9 @@ test.describe(`Project management commands`, () => {
// Constants and locators // Constants and locators
const projectHomeLink = page.getByTestId('project-link') const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' }) const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'delete project' }) const commandOption = page.getByRole('option', {
name: 'delete project',
})
const projectNameOption = page.getByRole('option', { name: projectName }) const projectNameOption = page.getByRole('option', { name: projectName })
const commandWarning = page.getByText('Are you sure you want to delete?') const commandWarning = page.getByText('Are you sure you want to delete?')
const commandSubmitButton = page.getByRole('button', { const commandSubmitButton = page.getByRole('button', {
@ -891,7 +890,9 @@ test.describe(`Project management commands`, () => {
// Constants and locators // Constants and locators
const projectHomeLink = page.getByTestId('project-link') const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' }) const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'rename project' }) const commandOption = page.getByRole('option', {
name: 'rename project',
})
const projectNameOption = page.getByRole('option', { name: projectName }) const projectNameOption = page.getByRole('option', { name: projectName })
const projectRenamedName = `untitled` const projectRenamedName = `untitled`
const commandContinueButton = page.getByRole('button', { const commandContinueButton = page.getByRole('button', {
@ -947,7 +948,9 @@ test.describe(`Project management commands`, () => {
// Constants and locators // Constants and locators
const projectHomeLink = page.getByTestId('project-link') const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' }) const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'delete project' }) const commandOption = page.getByRole('option', {
name: 'delete project',
})
const projectNameOption = page.getByRole('option', { name: projectName }) const projectNameOption = page.getByRole('option', { name: projectName })
const commandWarning = page.getByText('Are you sure you want to delete?') const commandWarning = page.getByText('Are you sure you want to delete?')
const commandSubmitButton = page.getByRole('button', { const commandSubmitButton = page.getByRole('button', {
@ -1812,8 +1815,8 @@ test(
'basic_fillet_cube_next_adjacent.kcl', 'basic_fillet_cube_next_adjacent.kcl',
'basic_fillet_cube_previous_adjacent.kcl', 'basic_fillet_cube_previous_adjacent.kcl',
'basic_fillet_cube_start.kcl', 'basic_fillet_cube_start.kcl',
'big_number_angle_to_match_length_x.kcl', 'broken-code-test.kcl',
'big_number_angle_to_match_length_y.kcl', 'circular_pattern3d_a_pattern.kcl',
'close_arc.kcl', 'close_arc.kcl',
'computed_var.kcl', 'computed_var.kcl',
'cube-embedded.gltf', 'cube-embedded.gltf',

View File

@ -53,6 +53,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
await context.addInitScript((file) => { await context.addInitScript((file) => {
localStorage.setItem('persistCode', file) localStorage.setItem('persistCode', file)
}, file) }, file)
await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -148,6 +149,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
await context.addInitScript((file) => { await context.addInitScript((file) => {
localStorage.setItem('persistCode', file) localStorage.setItem('persistCode', file)
}, file) }, file)
await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -203,6 +205,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
await context.addInitScript((file) => { await context.addInitScript((file) => {
localStorage.setItem('persistCode', file) localStorage.setItem('persistCode', file)
}, file) }, file)
await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -272,6 +275,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
await context.addInitScript((file) => { await context.addInitScript((file) => {
localStorage.setItem('persistCode', file) localStorage.setItem('persistCode', file)
}, file) }, file)
await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)

View File

@ -343,7 +343,7 @@ extrude002 = extrude(profile002, length = 150)
) )
const websocketPromise = page.waitForEvent('websocket') const websocketPromise = page.waitForEvent('websocket')
await page.setBodyDimensions({ width: 500, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
const websocket = await websocketPromise const websocket = await websocketPromise
@ -679,10 +679,12 @@ extrude002 = extrude(profile002, length = 150)
scene, scene,
toolbar, toolbar,
viewport, viewport,
page,
}) => { }) => {
await context.folderSetupFn(async (dir) => { await context.folderSetupFn(async (dir) => {
const legoDir = path.join(dir, 'lego') const legoDir = path.join(dir, 'lego')
await fsp.mkdir(legoDir, { recursive: true }) await fsp.mkdir(legoDir, { recursive: true })
await page.setBodyDimensions({ width: 1500, height: 500 })
await fsp.copyFile( await fsp.copyFile(
executorInputPath('e2e-can-sketch-on-chamfer.kcl'), executorInputPath('e2e-can-sketch-on-chamfer.kcl'),
path.join(legoDir, 'main.kcl') path.join(legoDir, 'main.kcl')

View File

@ -115,6 +115,7 @@ sketch001 = startSketchOn(XZ)
) )
}) })
await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -1541,6 +1542,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
) )
}) })
await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -2230,7 +2232,7 @@ profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
) )
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.settled(cmdBar) await scene.settled(cmdBar)
await expect( await expect(
@ -2339,7 +2341,7 @@ extrude001 = extrude(profile003, length = 5)
) )
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.connectionEstablished() await scene.connectionEstablished()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -2437,7 +2439,7 @@ profile002 = startProfileAt([85.81, 52.55], sketch002)
) )
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
@ -2595,7 +2597,7 @@ extrude003 = extrude(profile011, length = 2.5)
) )
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await scene.connectionEstablished() await scene.connectionEstablished()
await scene.settled(cmdBar) await scene.settled(cmdBar)
@ -2710,7 +2712,7 @@ loft([profile001, profile002])
) )
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
@ -2768,7 +2770,7 @@ loft([profile001, profile002])
) )
}) })
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
await expect( await expect(
page.getByRole('button', { name: 'Start Sketch' }) page.getByRole('button', { name: 'Start Sketch' })
@ -3260,6 +3262,7 @@ profile003 = startProfileAt([-201.08, 254.17], sketch002)
await context.addInitScript((file) => { await context.addInitScript((file) => {
localStorage.setItem('persistCode', file) localStorage.setItem('persistCode', file)
}, file) }, file)
await page.setBodyDimensions({ width: 1500, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
const [objClick] = scene.makeMouseHelpers(600, 250) const [objClick] = scene.makeMouseHelpers(600, 250)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

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

After

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

After

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

After

Width:  |  Height:  |  Size: 44 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: 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: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

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

After

Width:  |  Height:  |  Size: 75 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: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 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: 67 KiB

After

Width:  |  Height:  |  Size: 68 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

View File

@ -36,7 +36,6 @@ export const headerMasks = (page: Page) => [
] ]
export const networkingMasks = (page: Page) => [ export const networkingMasks = (page: Page) => [
page.getByTestId('model-state-indicator'),
page.getByTestId('network-toggle'), page.getByTestId('network-toggle'),
] ]
@ -85,12 +84,6 @@ async function waitForPageLoadWithRetry(page: Page) {
await expect(async () => { await expect(async () => {
await page.goto('/') await page.goto('/')
const errorMessage = 'App failed to load - 🔃 Retrying ...' const errorMessage = 'App failed to load - 🔃 Retrying ...'
await expect(
page.getByTestId('model-state-indicator-playing'),
errorMessage
).toBeAttached({
timeout: 20_000,
})
await expect( await expect(
page.getByRole('button', { name: 'sketch Start Sketch' }), page.getByRole('button', { name: 'sketch Start Sketch' }),
@ -103,11 +96,6 @@ async function waitForPageLoadWithRetry(page: Page) {
// lee: This needs to be replaced by scene.settled() eventually. // lee: This needs to be replaced by scene.settled() eventually.
async function waitForPageLoad(page: Page) { async function waitForPageLoad(page: Page) {
// wait for all spinners to be gone
await expect(page.getByTestId('model-state-indicator-playing')).toBeVisible({
timeout: 20_000,
})
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeEnabled({ await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeEnabled({
timeout: 20_000, timeout: 20_000,
}) })

View File

@ -39,12 +39,12 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
}) })
const emptySpaceHover = () => const emptySpaceHover = () =>
test.step('Hover over empty space', async () => { test.step('Hover over empty space', async () => {
await page.mouse.move(700, 143, { steps: 5 }) await page.mouse.move(1000, 143, { steps: 5 })
await expect(page.locator('.hover-highlight')).not.toBeVisible() await expect(page.locator('.hover-highlight')).not.toBeVisible()
}) })
const emptySpaceClick = () => const emptySpaceClick = () =>
test.step(`Click in empty space`, async () => { test.step(`Click in empty space`, async () => {
await page.mouse.click(700, 143) await page.mouse.click(1000, 143)
await expect(page.locator('.cm-line').last()).toHaveClass( await expect(page.locator('.cm-line').last()).toHaveClass(
/cm-activeLine/ /cm-activeLine/
) )

View File

@ -30,21 +30,68 @@ declare module '@playwright/test' {
// *for one worker*. // *for one worker*.
const electronZooInstance = new ElectronZoo() const electronZooInstance = new ElectronZoo()
// Track whether this is the first run for this worker process
// Mac needs more time for the first window creation
let isFirstRun = true
// Our custom decorated Zoo test object. Makes it easier to add fixtures, and // Our custom decorated Zoo test object. Makes it easier to add fixtures, and
// switch between web and electron if needed. // switch between web and electron if needed.
const playwrightTestFnWithFixtures_ = playwrightTestFn.extend<{ const playwrightTestFnWithFixtures_ = playwrightTestFn.extend<{
tronApp?: ElectronZoo tronApp?: ElectronZoo
}>({ }>({
tronApp: async ({}, use, testInfo) => { tronApp: [
if (process.env.PLATFORM === 'web') { async ({}, use, testInfo) => {
await use(undefined) if (process.env.PLATFORM === 'web') {
return await use(undefined)
} return
}
await electronZooInstance.createInstanceIfMissing(testInfo) // Create a single timeout for the entire tronApp setup process
await use(electronZooInstance) // This will ensure tests fail faster if there's an issue with setup
await electronZooInstance.makeAvailableAgain() // instead of waiting for the full global timeout (120s)
}, // First runs need more time especially on Mac for window creation
const setupTimeout = isFirstRun ? 120_000 : 30_000
let timeoutId: NodeJS.Timeout | undefined
const setupPromise = new Promise<void>((resolve, reject) => {
timeoutId = setTimeout(() => {
reject(
new Error(
`tronApp setup timed out after ${setupTimeout}ms${isFirstRun ? ' (first run)' : ' (subsequent run)'}`
)
)
}, setupTimeout)
// Execute the async setup in a separate function
const doSetup = async () => {
try {
await electronZooInstance.createInstanceIfMissing(testInfo)
resolve()
} catch (error) {
reject(error)
}
}
// Start the setup process
void doSetup()
})
try {
await setupPromise
if (timeoutId) clearTimeout(timeoutId)
// First run is complete at this point
isFirstRun = false
await use(electronZooInstance)
await electronZooInstance.makeAvailableAgain()
} catch (error) {
if (timeoutId) clearTimeout(timeoutId)
throw error
}
},
{ timeout: 120_000 }, // Keep the global timeout as fallback
],
}) })
const test = playwrightTestFnWithFixtures_.extend<Fixtures>( const test = playwrightTestFnWithFixtures_.extend<Fixtures>(

View File

@ -3,14 +3,13 @@
> dpdm --no-warning --no-tree -T --skip-dynamic-imports=circular src/index.tsx > dpdm --no-warning --no-tree -T --skip-dynamic-imports=circular src/index.tsx
• Circular Dependencies • Circular Dependencies
01) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/modifyAst/addEdgeTreatment.ts 1) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/modifyAst/addEdgeTreatment.ts
02) src/lang/std/sketch.ts -> src/lang/modifyAst.ts 2) src/lang/std/sketch.ts -> src/lang/modifyAst.ts
03) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/std/sketchcombos.ts 3) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/std/sketchcombos.ts
04) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts 4) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts
05) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts -> src/machines/appMachine.ts -> src/machines/engineStreamMachine.ts 5) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts
06) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts -> src/machines/appMachine.ts -> src/machines/settingsMachine.ts 6) src/lib/singletons.ts -> src/lang/codeManager.ts
07) src/machines/appMachine.ts -> src/machines/settingsMachine.ts -> src/machines/commandBarMachine.ts -> src/lib/commandBarConfigs/authCommandConfig.ts 7) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts
08) src/lib/singletons.ts -> src/lang/codeManager.ts 8) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts
09) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts 9) src/hooks/useModelingContext.ts -> src/components/ModelingMachineProvider.tsx -> src/components/Toolbar/Intersect.tsx -> src/components/SetHorVertDistanceModal.tsx -> src/lib/useCalculateKclExpression.ts
10) src/hooks/useModelingContext.ts -> src/components/ModelingMachineProvider.tsx -> src/components/Toolbar/Intersect.tsx -> src/components/SetHorVertDistanceModal.tsx -> src/lib/useCalculateKclExpression.ts 10) src/routes/Onboarding/index.tsx -> src/routes/Onboarding/Camera.tsx -> src/routes/Onboarding/utils.tsx
11) src/routes/Onboarding/index.tsx -> src/routes/Onboarding/Camera.tsx -> src/routes/Onboarding/utils.tsx

View File

@ -114,6 +114,7 @@
"circular-deps": "dpdm --no-warning --no-tree -T --skip-dynamic-imports=circular src/index.tsx", "circular-deps": "dpdm --no-warning --no-tree -T --skip-dynamic-imports=circular src/index.tsx",
"circular-deps:overwrite": "npm run circular-deps | sed '$d' | grep -v '^npm run' > known-circular.txt", "circular-deps:overwrite": "npm run circular-deps | sed '$d' | grep -v '^npm run' > known-circular.txt",
"circular-deps:diff": "./scripts/diff-circular-deps.sh", "circular-deps:diff": "./scripts/diff-circular-deps.sh",
"circular-deps:diff:nodejs": "npm run circular-deps:diff || node ./scripts/diff.js",
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json", "files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
"files:set-notes": "./scripts/set-files-notes.sh", "files:set-notes": "./scripts/set-files-notes.sh",
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh", "files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",

View File

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

View File

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

View File

@ -1,88 +1,79 @@
// Hollow Dodecahedron // Dodecahedron
// A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the individual faces of the dodecahedron and extruding inwards. // A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the a dodecahedron with a series of intersects.
// Set units // Set units
@settings(defaultLengthUnit = in) @settings(defaultLengthUnit = in)
// Input parameters // Define the dihedral angle for a regular dodecahedron
// circumscribed radius dihedral = 116.565
circR = 25
// Calculated parameters // Create a face template function that makes a large thin cube
// Thickness of the dodecahedron fn createFaceTemplate(dither) {
wallThickness = circR * 0.2 baseSketch = startSketchOn(XY)
|> startProfileAt([-1000 - dither, -1000 - dither], %)
// Angle between faces in radians |> line(endAbsolute = [1000 + dither, -1000 - dither])
dihedral = acos(-(sqrt(5) / 5)) |> line(endAbsolute = [1000 + dither, 1000 + dither])
|> line(endAbsolute = [-1000 - dither, 1000 + dither])
// Inscribed radius |> close()
inscR = circR / 15 * sqrt(75 + 30 * sqrt(5)) extruded = extrude(baseSketch, length = 1000 + dither + 1000)
return extruded
// Pentagon edge length |> translate(x = 0, y = 0, z = -260 - dither)
edgeL = 4 * circR / (sqrt(3) * (1 + sqrt(5)))
// Pentagon radius
pentR = edgeL / 2 / sin(toRadians(36))
// Define a plane for the bottom angled face
plane = {
origin = [
-inscR * cos(toRadians(toDegrees(dihedral) - 90)),
0,
inscR - (inscR * sin(toRadians(toDegrees(dihedral) - 90)))
],
xAxis = [cos(dihedral), 0.0, sin(dihedral)],
yAxis = [0, 1, 0],
zAxis = [sin(dihedral), 0, -cos(dihedral)]
} }
// Create a regular pentagon inscribed in a circle of radius pentR // Define the rotations array with [pitch, roll, yaw, dither] for each face
bottomFace = startSketchOn(XY) faceRotations = [
|> polygon( [0, 0, 0, 0],
radius = pentR, // face1 - reference face
numSides = 5, [dihedral, 0, 0, 0.1],
center = [0, 0], // face2
inscribed = true, [dihedral, 0, 72, 0.2],
) // face3
[dihedral, 0, 144, 0.3],
// face4
[dihedral, 0, 216, 0.4],
// face5
[dihedral, 0, 288, 0.5],
// face6
[180, 0, 0, 0.6],
// face7
[180 - dihedral, 0, 36, 0.7],
// face8
[180 - dihedral, 0, 108, 0.8],
// face9
[180 - dihedral, 0, 180, 0.9],
// face10
[180 - dihedral, 0, 252, 0.11],
// face11
[180 - dihedral, 0, 324, 0.12],
// face12
]
bottomSideFace = startSketchOn(plane) // Create faces by mapping over the rotations array
|> polygon( dodecFaces = map(faceRotations, fn(rotation) {
radius = pentR, return createFaceTemplate(rotation[3])
numSides = 5, |> rotate(
center = [0, 0], pitch = rotation[0],
inscribed = true, roll = rotation[1],
) yaw = rotation[2],
global = true,
)
})
// Extrude the faces in each plane fn calculateArrayLength(arr) {
bottom = extrude(bottomFace, length = wallThickness) return reduce(arr, 0, fn(item, accumulator) {
bottomSide = extrude(bottomSideFace, length = wallThickness) return accumulator + 1
})
}
// Pattern the sides so we have a full dodecahedron fn createIntersection(solids) {
bottomBowl = patternCircular3d( fn reduceIntersect(previous, current) {
bottomSide, return intersect([previous, current])
instances = 5, }
axis = [0, 0, 1], lastIndex = calculateArrayLength(solids) - 1
center = [0, 0, 0], lastSolid = solids[lastIndex]
arcDegrees = 360, remainingSolids = pop(solids)
rotateDuplicates = true, return reduce(remainingSolids, lastSolid, reduceIntersect)
) }
// Pattern the bottom to create the top face // Apply intersection to all faces
patternCircular3d( createIntersection(dodecFaces)
bottom,
instances = 2,
axis = [0, 1, 0],
center = [0, 0, inscR],
arcDegrees = 360,
rotateDuplicates = true,
)
// Pattern the bottom angled faces to create the top
patternCircular3d(
bottomBowl,
instances = 2,
axis = [0, 1, 0],
center = [0, 0, inscR],
arcDegrees = 360,
rotateDuplicates = true,
)

View File

@ -202,19 +202,17 @@ plane000 = {
height + binHeight * countBinHeight height + binHeight * countBinHeight
], ],
xAxis = [0.0, 1.0, 0.0], xAxis = [0.0, 1.0, 0.0],
yAxis = [0.0, 0.0, 1.0], yAxis = [0.0, 0.0, 1.0]
zAxis = [1.0, 0.0, 0.0]
} }
plane001 = { plane001 = {
origin = [ origin = [
0.0, 0.0,
cornerRadius, countBinLength * (binLength + 2 * binTol) - cornerRadius,
height + binHeight * countBinHeight height + binHeight * countBinHeight
], ],
xAxis = [1.0, 0.0, 0.0], xAxis = [1.0, 0.0, 0.0],
yAxis = [0.0, 0.0, 1.0], yAxis = [0.0, 0.0, 1.0]
zAxis = [0.0, 1.0, 0.0]
} }
plane002 = { plane002 = {
@ -224,8 +222,7 @@ plane002 = {
height + binHeight * countBinHeight height + binHeight * countBinHeight
], ],
xAxis = [0.0, 1.0, 0.0], xAxis = [0.0, 1.0, 0.0],
yAxis = [0.0, 0.0, 1.0], yAxis = [0.0, 0.0, 1.0]
zAxis = [1.0, 0.0, 0.0]
} }
// Extrude a single side of the lip of the bin // Extrude a single side of the lip of the bin

View File

@ -66,8 +66,8 @@
"file": "main.kcl", "file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "dodecahedron/main.kcl", "pathFromProjectDirectoryToFirstFile": "dodecahedron/main.kcl",
"multipleFiles": false, "multipleFiles": false,
"title": "Hollow Dodecahedron", "title": "Dodecahedron",
"description": "A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the individual faces of the dodecahedron and extruding inwards." "description": "A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the a dodecahedron with a series of intersects."
}, },
{ {
"file": "main.kcl", "file": "main.kcl",

View File

@ -4,10 +4,10 @@
@settings(defaultLengthUnit = in) @settings(defaultLengthUnit = in)
// Axis Angles // Axis Angles
export axisJ4 = 25 export axisJ4 = 25deg
export axisJ3 = 60 export axisJ3 = 60deg
export axisJ2 = 110 export axisJ2 = 110deg
export axisJ1 = 80 export axisJ1 = 80deg
// Robot Arm Base // Robot Arm Base
export basePlateRadius = 5 export basePlateRadius = 5
@ -30,29 +30,26 @@ export axisJ3CArmThickness = 2.5
export plane001 = { export plane001 = {
origin = [0.0, 0.0, baseHeight - 1.5 + 0.1], origin = [0.0, 0.0, baseHeight - 1.5 + 0.1],
xAxis = [1.0, 0.0, 0.0], xAxis = [1.0, 0.0, 0.0],
yAxis = [0.0, 1.0, 0.0], yAxis = [0.0, 1.0, 0.0]
zAxis = [0.0, 0.0, 1.0]
} }
export plane002 = { export plane002 = {
origin = [0.0, 0.0, 0.0], origin = [0.0, 0.0, 0.0],
xAxis = [ xAxis = [
sin(toRadians(axisJ1)), sin(axisJ1): number(in),
cos(toRadians(axisJ1)), cos(axisJ1): number(in),
0.0 0.0
], ],
yAxis = [0.0, 0.0, 1.0], yAxis = [0.0, 0.0, 1.0]
zAxis = [1.0, 0.0, 0.0]
} }
// Define Plane to Move J2 Axis Robot Arm // Define Plane to Move J2 Axis Robot Arm
export plane003 = { export plane003 = {
origin = [-0.1, 0.0, 0.0], origin = [-0.1, 0.0, 0.0],
xAxis = [ xAxis = [
sin(toRadians(axisJ1)), sin(axisJ1): number(in),
cos(toRadians(axisJ1)), cos(axisJ1): number(in),
0.0 0.0
], ],
yAxis = [0.0, 0.0, 1.0], yAxis = [0.0, 0.0, 1.0]
zAxis = [1.0, 0.0, 0.0]
} }

View File

@ -62,8 +62,7 @@ customPlane = {
z = 0 z = 0
}, },
xAxis = { x = 1, y = 0, z = 0 }, xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 0, z = 1 }, yAxis = { x = 0, y = 0, z = 1 }
zAxis = { x = 0, y = -1, z = 0 }
} }
sketch003 = startSketchOn(customPlane) sketch003 = startSketchOn(customPlane)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
@ -98,19 +97,18 @@ customPlane2 = {
y = 0, y = 0,
z = 0 z = 0
}, },
xAxis = { x = 0, y = -1, z = 0 }, xAxis = { x = 0, y = 1, z = 0 },
yAxis = { x = 0, y = 0, z = 1 }, yAxis = { x = 0, y = 0, z = 1 }
zAxis = { x = 1, y = 0, z = 0 }
} }
sketch005 = startSketchOn(customPlane2) sketch005 = startSketchOn(customPlane2)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> yLine(endAbsolute = height) |> yLine(endAbsolute = height)
|> xLine(endAbsolute = wallsWidth) |> xLine(endAbsolute = -wallsWidth)
|> tangentialArc(endAbsolute = [ |> tangentialArc(endAbsolute = [
(frontLength - wallsWidth) / 2 + wallsWidth, -1 * ((frontLength - wallsWidth) / 2 + wallsWidth),
height - ((height - exitHeight) / 2) height - ((height - exitHeight) / 2)
]) ])
|> tangentialArc(endAbsolute = [frontLength, exitHeight]) |> tangentialArc(endAbsolute = [-frontLength, exitHeight])
|> yLine(endAbsolute = 0, tag = $seg03) |> yLine(endAbsolute = 0, tag = $seg03)
|> close() |> close()
|> extrude(length = wallThickness) |> extrude(length = wallThickness)
@ -138,8 +136,7 @@ customPlane3 = {
z = wallThickness z = wallThickness
}, },
xAxis = { x = 0, y = -1, z = 0 }, xAxis = { x = 0, y = -1, z = 0 },
yAxis = { x = 1, y = 0, z = 0 }, yAxis = { x = 1, y = 0, z = 0 }
zAxis = { x = 0, y = 0, z = 1 }
} }
sketch008 = startSketchOn(customPlane3) sketch008 = startSketchOn(customPlane3)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@ -6,7 +6,7 @@ uses-engine = { max-threads = 4 }
after-engine = { max-threads = 12 } after-engine = { max-threads = 12 }
[profile.default] [profile.default]
slow-timeout = { period = "90s", terminate-after = 1 } slow-timeout = { period = "180s", terminate-after = 1 }
[profile.ci] [profile.ci]
slow-timeout = { period = "50s", terminate-after = 5 } slow-timeout = { period = "50s", terminate-after = 5 }

View File

@ -6,11 +6,12 @@
mod tests; mod tests;
mod unbox; mod unbox;
use std::collections::HashMap; use std::{collections::HashMap, fs};
use convert_case::Casing; use convert_case::Casing;
use inflector::{cases::camelcase::to_camel_case, Inflector}; use inflector::{cases::camelcase::to_camel_case, Inflector};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use proc_macro2::Span;
use quote::{format_ident, quote, quote_spanned, ToTokens}; use quote::{format_ident, quote, quote_spanned, ToTokens};
use regex::Regex; use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
@ -21,6 +22,16 @@ use syn::{
}; };
use unbox::unbox; use unbox::unbox;
#[proc_macro_attribute]
pub fn stdlib(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
do_output(do_stdlib(attr.into(), item.into()))
}
#[proc_macro_attribute]
pub fn for_each_std_mod(_attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
do_for_each_std_mod(item.into()).into()
}
/// Describes an argument of a stdlib function. /// Describes an argument of a stdlib function.
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
struct ArgMetadata { struct ArgMetadata {
@ -73,11 +84,6 @@ struct StdlibMetadata {
args: HashMap<String, ArgMetadata>, args: HashMap<String, ArgMetadata>,
} }
#[proc_macro_attribute]
pub fn stdlib(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
do_output(do_stdlib(attr.into(), item.into()))
}
fn do_stdlib( fn do_stdlib(
attr: proc_macro2::TokenStream, attr: proc_macro2::TokenStream,
item: proc_macro2::TokenStream, item: proc_macro2::TokenStream,
@ -86,6 +92,31 @@ fn do_stdlib(
do_stdlib_inner(metadata, attr, item) do_stdlib_inner(metadata, attr, item)
} }
fn do_for_each_std_mod(item: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
let item: syn::ItemFn = syn::parse2(item.clone()).unwrap();
let mut result = proc_macro2::TokenStream::new();
for name in fs::read_dir("kcl-lib/std").unwrap().filter_map(|e| {
let e = e.unwrap();
let filename = e.file_name();
filename.to_str().unwrap().strip_suffix(".kcl").map(str::to_owned)
}) {
let mut item = item.clone();
item.sig.ident = syn::Ident::new(&format!("{}_{}", item.sig.ident, name), Span::call_site());
let stmts = &item.block.stmts;
//let name = format!("\"{name}\"");
let block = quote! {
{
const STD_MOD_NAME: &str = #name;
#(#stmts)*
}
};
item.block = Box::new(syn::parse2(block).unwrap());
result.extend(Some(item.into_token_stream()));
}
result
}
fn do_output(res: Result<(proc_macro2::TokenStream, Vec<Error>), Error>) -> proc_macro::TokenStream { fn do_output(res: Result<(proc_macro2::TokenStream, Vec<Error>), Error>) -> proc_macro::TokenStream {
match res { match res {
Err(err) => err.to_compile_error().into(), Err(err) => err.to_compile_error().into(),
@ -671,6 +702,7 @@ fn normalize_comment_string(s: String) -> Vec<String> {
/// Represent an item without concern for its body which may (or may not) /// Represent an item without concern for its body which may (or may not)
/// contain syntax errors. /// contain syntax errors.
#[derive(Clone)]
struct ItemFnForSignature { struct ItemFnForSignature {
pub attrs: Vec<Attribute>, pub attrs: Vec<Attribute>,
pub vis: Visibility, pub vis: Visibility,

View File

@ -19,6 +19,30 @@ We've built a lot of tooling to make contributing to KCL easier. If you are inte
11. Run `just redo-kcl-stdlib-docs` to generate new Markdown documentation for your function that will be used [to generate docs on our website](https://zoo.dev/docs/kcl). 11. Run `just redo-kcl-stdlib-docs` to generate new Markdown documentation for your function that will be used [to generate docs on our website](https://zoo.dev/docs/kcl).
12. Create a PR in GitHub. 12. Create a PR in GitHub.
## Making a Simulation Test
If you have KCL code that you want to test, simulation tests are the preferred way to do that.
Make a new sim test. Replace `foo_bar` with the snake case name of your test. The name needs to be unique.
```shell
just new-sim-test foo_bar
```
It will show the commands it ran, including the path to a new file `foo_bar/input.kcl`. Edit that with your KCL. If you need additional KCL files to import, include them in this directory.
Then run it.
```shell
just overwrite-sim-test foo_bar
```
The above should create a bunch of output files in the same directory.
Make sure you actually look at them. Specifically, if there's an `execution_error.snap`, it means the execution failed. Depending on the test, this may be what you expect. But if it's not, delete the snap file and run it again.
When it looks good, commit all the files, including `input.kcl`, generated output files in the test directory, and changes to `simulation_tests.rs`.
## Bumping the version ## Bumping the version
If you bump the version of kcl-lib and push it to crates, be sure to update the repos we own that use it as well. These are: If you bump the version of kcl-lib and push it to crates, be sure to update the repos we own that use it as well. These are:

View File

@ -1,9 +0,0 @@
const part001 = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [1, 3.82], tag = $seg01)
|> angled(
angle = -angleToMatchLengthX(seg01, 3, %),
endAbsoluteX = 3,
)
|> close()
|> extrude(length = 10)

View File

@ -1,9 +0,0 @@
const part001 = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [1, 3.82], tag = $seg01)
|> angledLine(
angle = -angleToMatchLengthY(seg01, 3, %),
endAbsoluteX = 3,
)
|> close()
|> extrude(length = 10)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 68 KiB

View File

@ -61,8 +61,10 @@ impl CollectionVisitor {
format!("std::{}::", self.name) format!("std::{}::", self.name)
}; };
let mut dd = match var.kind { let mut dd = match var.kind {
VariableKind::Fn => DocData::Fn(FnData::from_ast(var, qual_name, preferred_prefix)), VariableKind::Fn => DocData::Fn(FnData::from_ast(var, qual_name, preferred_prefix, name)),
VariableKind::Const => DocData::Const(ConstData::from_ast(var, qual_name, preferred_prefix)), VariableKind::Const => {
DocData::Const(ConstData::from_ast(var, qual_name, preferred_prefix, name))
}
}; };
dd.with_meta(&var.outer_attrs); dd.with_meta(&var.outer_attrs);
@ -79,7 +81,7 @@ impl CollectionVisitor {
} else { } else {
format!("std::{}::", self.name) format!("std::{}::", self.name)
}; };
let mut dd = DocData::Ty(TyData::from_ast(ty, qual_name, preferred_prefix)); let mut dd = DocData::Ty(TyData::from_ast(ty, qual_name, preferred_prefix, name));
dd.with_meta(&ty.outer_attrs); dd.with_meta(&ty.outer_attrs);
for a in &ty.outer_attrs { for a in &ty.outer_attrs {
@ -114,6 +116,16 @@ impl DocData {
} }
} }
/// The name of the module in which the item is declared, e.g., `sketch`
#[allow(dead_code)]
pub fn module_name(&self) -> &str {
match self {
DocData::Fn(f) => &f.module_name,
DocData::Const(c) => &c.module_name,
DocData::Ty(t) => &t.module_name,
}
}
#[allow(dead_code)] #[allow(dead_code)]
pub fn file_name(&self) -> String { pub fn file_name(&self) -> String {
match self { match self {
@ -132,6 +144,7 @@ impl DocData {
} }
} }
/// The path to the module through which the item is accessed, e.g., `std::sketch`
#[allow(dead_code)] #[allow(dead_code)]
pub fn mod_name(&self) -> String { pub fn mod_name(&self) -> String {
let q = match self { let q = match self {
@ -217,6 +230,8 @@ pub struct ConstData {
/// Code examples. /// Code examples.
/// These are tested and we know they compile and execute. /// These are tested and we know they compile and execute.
pub examples: Vec<(String, ExampleProperties)>, pub examples: Vec<(String, ExampleProperties)>,
pub module_name: String,
} }
impl ConstData { impl ConstData {
@ -224,6 +239,7 @@ impl ConstData {
var: &crate::parsing::ast::types::VariableDeclaration, var: &crate::parsing::ast::types::VariableDeclaration,
mut qual_name: String, mut qual_name: String,
preferred_prefix: &str, preferred_prefix: &str,
module_name: &str,
) -> Self { ) -> Self {
assert_eq!(var.kind, crate::parsing::ast::types::VariableKind::Const); assert_eq!(var.kind, crate::parsing::ast::types::VariableKind::Const);
@ -263,6 +279,7 @@ impl ConstData {
summary: None, summary: None,
description: None, description: None,
examples: Vec::new(), examples: Vec::new(),
module_name: module_name.to_owned(),
} }
} }
@ -334,6 +351,8 @@ pub struct FnData {
pub examples: Vec<(String, ExampleProperties)>, pub examples: Vec<(String, ExampleProperties)>,
#[allow(dead_code)] #[allow(dead_code)]
pub referenced_types: Vec<String>, pub referenced_types: Vec<String>,
pub module_name: String,
} }
impl FnData { impl FnData {
@ -341,6 +360,7 @@ impl FnData {
var: &crate::parsing::ast::types::VariableDeclaration, var: &crate::parsing::ast::types::VariableDeclaration,
mut qual_name: String, mut qual_name: String,
preferred_prefix: &str, preferred_prefix: &str,
module_name: &str,
) -> Self { ) -> Self {
assert_eq!(var.kind, crate::parsing::ast::types::VariableKind::Fn); assert_eq!(var.kind, crate::parsing::ast::types::VariableKind::Fn);
let crate::parsing::ast::types::Expr::FunctionExpression(expr) = &var.declaration.init else { let crate::parsing::ast::types::Expr::FunctionExpression(expr) = &var.declaration.init else {
@ -375,6 +395,7 @@ impl FnData {
description: None, description: None,
examples: Vec::new(), examples: Vec::new(),
referenced_types: referenced_types.into_iter().collect(), referenced_types: referenced_types.into_iter().collect(),
module_name: module_name.to_owned(),
} }
} }
@ -654,6 +675,8 @@ pub struct TyData {
pub examples: Vec<(String, ExampleProperties)>, pub examples: Vec<(String, ExampleProperties)>,
#[allow(dead_code)] #[allow(dead_code)]
pub referenced_types: Vec<String>, pub referenced_types: Vec<String>,
pub module_name: String,
} }
impl TyData { impl TyData {
@ -661,6 +684,7 @@ impl TyData {
ty: &crate::parsing::ast::types::TypeDeclaration, ty: &crate::parsing::ast::types::TypeDeclaration,
mut qual_name: String, mut qual_name: String,
preferred_prefix: &str, preferred_prefix: &str,
module_name: &str,
) -> Self { ) -> Self {
let name = ty.name.name.clone(); let name = ty.name.name.clone();
qual_name.push_str(&name); qual_name.push_str(&name);
@ -684,6 +708,7 @@ impl TyData {
description: None, description: None,
examples: Vec::new(), examples: Vec::new(),
referenced_types: referenced_types.into_iter().collect(), referenced_types: referenced_types.into_iter().collect(),
module_name: module_name.to_owned(),
} }
} }
@ -1009,6 +1034,8 @@ fn collect_type_names_from_primitive(ty: &PrimitiveType) -> String {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use kcl_derive_docs::for_each_std_mod;
use super::*; use super::*;
#[test] #[test]
@ -1047,18 +1074,28 @@ mod test {
); );
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[for_each_std_mod]
async fn test_examples() -> miette::Result<()> { #[tokio::test(flavor = "multi_thread")]
async fn test_examples() {
let std = walk_prelude(); let std = walk_prelude();
let mut errs = Vec::new();
for d in std { for d in std {
if d.module_name() != STD_MOD_NAME {
continue;
}
for (i, eg) in d.examples().enumerate() { for (i, eg) in d.examples().enumerate() {
let result = match crate::test_server::execute_and_snapshot(eg, None).await { let result = match crate::test_server::execute_and_snapshot(eg, None).await {
Err(crate::errors::ExecError::Kcl(e)) => { Err(crate::errors::ExecError::Kcl(e)) => {
return Err(miette::Report::new(crate::errors::Report { errs.push(
error: e.error, miette::Report::new(crate::errors::Report {
filename: format!("{}{i}", d.name()), error: e.error,
kcl_source: eg.to_string(), filename: format!("{}{i}", d.name()),
})); kcl_source: eg.to_string(),
})
.to_string(),
);
continue;
} }
Err(other_err) => panic!("{}", other_err), Err(other_err) => panic!("{}", other_err),
Ok(img) => img, Ok(img) => img,
@ -1071,6 +1108,8 @@ mod test {
} }
} }
Ok(()) if !errs.is_empty() {
panic!("{}", errs.join("\n\n"));
}
} }
} }

View File

@ -129,6 +129,8 @@ impl StdLibFnArg {
}; };
if (self.type_ == "Sketch" if (self.type_ == "Sketch"
|| self.type_ == "[Sketch]" || self.type_ == "[Sketch]"
|| self.type_ == "Geometry"
|| self.type_ == "GeometryWithImportedGeometry"
|| self.type_ == "Solid" || self.type_ == "Solid"
|| self.type_ == "[Solid]" || self.type_ == "[Solid]"
|| self.type_ == "SketchSurface" || self.type_ == "SketchSurface"
@ -502,6 +504,8 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
fn to_autocomplete_snippet(&self) -> Result<String> { fn to_autocomplete_snippet(&self) -> Result<String> {
if self.name() == "loft" { if self.name() == "loft" {
return Ok("loft([${0:sketch000}, ${1:sketch001}])".to_string()); return Ok("loft([${0:sketch000}, ${1:sketch001}])".to_string());
} else if self.name() == "clone" {
return Ok("clone(${0:part001})".to_string());
} else if self.name() == "union" { } else if self.name() == "union" {
return Ok("union([${0:extrude001}, ${1:extrude002}])".to_string()); return Ok("union([${0:extrude001}, ${1:extrude002}])".to_string());
} else if self.name() == "subtract" { } else if self.name() == "subtract" {
@ -1089,6 +1093,14 @@ mod tests {
); );
} }
#[test]
#[allow(clippy::literal_string_with_formatting_args)]
fn get_autocomplete_snippet_clone() {
let clone_fn: Box<dyn StdLibFn> = Box::new(crate::std::clone::Clone);
let snippet = clone_fn.to_autocomplete_snippet().unwrap();
assert_eq!(snippet, r#"clone(${0:part001})"#);
}
// We want to test the snippets we compile at lsp start. // We want to test the snippets we compile at lsp start.
#[test] #[test]
fn get_all_stdlib_autocomplete_snippets() { fn get_all_stdlib_autocomplete_snippets() {

View File

@ -1142,9 +1142,15 @@ impl Node<UnaryExpression> {
} }
KclValue::Plane { value } => { KclValue::Plane { value } => {
let mut plane = value.clone(); let mut plane = value.clone();
plane.z_axis.x *= -1.0; if plane.x_axis.x != 0.0 {
plane.z_axis.y *= -1.0; plane.x_axis.x *= -1.0;
plane.z_axis.z *= -1.0; }
if plane.x_axis.y != 0.0 {
plane.x_axis.y *= -1.0;
}
if plane.x_axis.z != 0.0 {
plane.x_axis.z *= -1.0;
}
plane.value = PlaneType::Uninit; plane.value = PlaneType::Uninit;
plane.id = exec_state.next_uuid(); plane.id = exec_state.next_uuid();
@ -2637,7 +2643,6 @@ p = {
origin = { x = 0, y = 0, z = 0 }, origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 }, xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 }, yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 }
}: Plane }: Plane
p2 = -p p2 = -p
"#; "#;
@ -2649,7 +2654,11 @@ p2 = -p
.get_from("p2", result.mem_env, SourceRange::default(), 0) .get_from("p2", result.mem_env, SourceRange::default(), 0)
.unwrap() .unwrap()
{ {
KclValue::Plane { value } => assert_eq!(value.z_axis.z, -1.0), KclValue::Plane { value } => {
assert_eq!(value.x_axis.x, -1.0);
assert_eq!(value.x_axis.y, 0.0);
assert_eq!(value.x_axis.z, 0.0);
}
_ => unreachable!(), _ => unreachable!(),
} }
} }

View File

@ -47,6 +47,29 @@ impl Geometry {
} }
} }
/// A geometry including an imported geometry.
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
pub enum GeometryWithImportedGeometry {
Sketch(Sketch),
Solid(Solid),
ImportedGeometry(Box<ImportedGeometry>),
}
impl GeometryWithImportedGeometry {
pub async fn id(&mut self, ctx: &ExecutorContext) -> Result<uuid::Uuid, KclError> {
match self {
GeometryWithImportedGeometry::Sketch(s) => Ok(s.id),
GeometryWithImportedGeometry::Solid(e) => Ok(e.id),
GeometryWithImportedGeometry::ImportedGeometry(i) => {
let id = i.id(ctx).await?;
Ok(id)
}
}
}
}
/// A set of geometry. /// A set of geometry.
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
@ -262,8 +285,6 @@ pub struct Plane {
pub x_axis: Point3d, pub x_axis: Point3d,
/// What should the plane's Y axis be? /// What should the plane's Y axis be?
pub y_axis: Point3d, pub y_axis: Point3d,
/// The z-axis (normal).
pub z_axis: Point3d,
#[serde(skip)] #[serde(skip)]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
} }
@ -296,13 +317,6 @@ impl Plane {
z: 0.0, z: 0.0,
units: UnitLen::Mm, units: UnitLen::Mm,
}, },
z_axis:
Point3d {
x: 0.0,
y: 0.0,
z: 1.0,
units: UnitLen::Mm,
},
.. ..
} => return PlaneData::XY, } => return PlaneData::XY,
Self { Self {
@ -315,7 +329,7 @@ impl Plane {
}, },
x_axis: x_axis:
Point3d { Point3d {
x: 1.0, x: -1.0,
y: 0.0, y: 0.0,
z: 0.0, z: 0.0,
units: UnitLen::Mm, units: UnitLen::Mm,
@ -327,13 +341,6 @@ impl Plane {
z: 0.0, z: 0.0,
units: UnitLen::Mm, units: UnitLen::Mm,
}, },
z_axis:
Point3d {
x: 0.0,
y: 0.0,
z: -1.0,
units: UnitLen::Mm,
},
.. ..
} => return PlaneData::NegXY, } => return PlaneData::NegXY,
Self { Self {
@ -358,13 +365,6 @@ impl Plane {
z: 1.0, z: 1.0,
units: UnitLen::Mm, units: UnitLen::Mm,
}, },
z_axis:
Point3d {
x: 0.0,
y: -1.0,
z: 0.0,
units: UnitLen::Mm,
},
.. ..
} => return PlaneData::XZ, } => return PlaneData::XZ,
Self { Self {
@ -377,7 +377,7 @@ impl Plane {
}, },
x_axis: x_axis:
Point3d { Point3d {
x: 1.0, x: -1.0,
y: 0.0, y: 0.0,
z: 0.0, z: 0.0,
units: UnitLen::Mm, units: UnitLen::Mm,
@ -389,13 +389,6 @@ impl Plane {
z: 1.0, z: 1.0,
units: UnitLen::Mm, units: UnitLen::Mm,
}, },
z_axis:
Point3d {
x: 0.0,
y: 1.0,
z: 0.0,
units: UnitLen::Mm,
},
.. ..
} => return PlaneData::NegXZ, } => return PlaneData::NegXZ,
Self { Self {
@ -420,13 +413,6 @@ impl Plane {
z: 1.0, z: 1.0,
units: UnitLen::Mm, units: UnitLen::Mm,
}, },
z_axis:
Point3d {
x: 1.0,
y: 0.0,
z: 0.0,
units: UnitLen::Mm,
},
.. ..
} => return PlaneData::YZ, } => return PlaneData::YZ,
Self { Self {
@ -440,7 +426,7 @@ impl Plane {
x_axis: x_axis:
Point3d { Point3d {
x: 0.0, x: 0.0,
y: 1.0, y: -1.0,
z: 0.0, z: 0.0,
units: UnitLen::Mm, units: UnitLen::Mm,
}, },
@ -451,13 +437,6 @@ impl Plane {
z: 1.0, z: 1.0,
units: UnitLen::Mm, units: UnitLen::Mm,
}, },
z_axis:
Point3d {
x: -1.0,
y: 0.0,
z: 0.0,
units: UnitLen::Mm,
},
.. ..
} => return PlaneData::NegYZ, } => return PlaneData::NegYZ,
_ => {} _ => {}
@ -468,7 +447,6 @@ impl Plane {
origin: self.origin, origin: self.origin,
x_axis: self.x_axis, x_axis: self.x_axis,
y_axis: self.y_axis, y_axis: self.y_axis,
z_axis: self.z_axis,
} }
} }
@ -481,7 +459,6 @@ impl Plane {
origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm), origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm), x_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm),
y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
value: PlaneType::XY, value: PlaneType::XY,
meta: vec![], meta: vec![],
}, },
@ -489,9 +466,8 @@ impl Plane {
id, id,
artifact_id: id.into(), artifact_id: id.into(),
origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm), origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm), x_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm),
y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, 0.0, -1.0, UnitLen::Mm),
value: PlaneType::XY, value: PlaneType::XY,
meta: vec![], meta: vec![],
}, },
@ -501,7 +477,6 @@ impl Plane {
origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm), origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm), x_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm),
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, -1.0, 0.0, UnitLen::Mm),
value: PlaneType::XZ, value: PlaneType::XZ,
meta: vec![], meta: vec![],
}, },
@ -511,7 +486,6 @@ impl Plane {
origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm), origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm), x_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm),
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
value: PlaneType::XZ, value: PlaneType::XZ,
meta: vec![], meta: vec![],
}, },
@ -521,7 +495,6 @@ impl Plane {
origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm), origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm), x_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm),
value: PlaneType::YZ, value: PlaneType::YZ,
meta: vec![], meta: vec![],
}, },
@ -529,18 +502,12 @@ impl Plane {
id, id,
artifact_id: id.into(), artifact_id: id.into(),
origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm), origin: Point3d::new(0.0, 0.0, 0.0, UnitLen::Mm),
x_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm), x_axis: Point3d::new(0.0, -1.0, 0.0, UnitLen::Mm),
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm),
value: PlaneType::YZ, value: PlaneType::YZ,
meta: vec![], meta: vec![],
}, },
PlaneData::Plane { PlaneData::Plane { origin, x_axis, y_axis } => {
origin,
x_axis,
y_axis,
z_axis,
} => {
let id = exec_state.next_uuid(); let id = exec_state.next_uuid();
Plane { Plane {
id, id,
@ -548,7 +515,6 @@ impl Plane {
origin, origin,
x_axis, x_axis,
y_axis, y_axis,
z_axis,
value: PlaneType::Custom, value: PlaneType::Custom,
meta: vec![], meta: vec![],
} }
@ -577,8 +543,6 @@ pub struct Face {
pub x_axis: Point3d, pub x_axis: Point3d,
/// What should the face's Y axis be? /// What should the face's Y axis be?
pub y_axis: Point3d, pub y_axis: Point3d,
/// The z-axis (normal).
pub z_axis: Point3d,
/// The solid the face is on. /// The solid the face is on.
pub solid: Box<Solid>, pub solid: Box<Solid>,
pub units: UnitLen, pub units: UnitLen,
@ -656,7 +620,8 @@ impl Sketch {
adjust_camera: false, adjust_camera: false,
planar_normal: if let SketchSurface::Plane(plane) = &self.on { planar_normal: if let SketchSurface::Plane(plane) = &self.on {
// We pass in the normal for the plane here. // We pass in the normal for the plane here.
Some(plane.z_axis.into()) let normal = plane.x_axis.cross(&plane.y_axis);
Some(normal.into())
} else { } else {
None None
}, },
@ -700,12 +665,6 @@ impl SketchSurface {
SketchSurface::Face(face) => face.y_axis, SketchSurface::Face(face) => face.y_axis,
} }
} }
pub(crate) fn z_axis(&self) -> Point3d {
match self {
SketchSurface::Plane(plane) => plane.z_axis,
SketchSurface::Face(face) => face.z_axis,
}
}
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -807,7 +766,10 @@ pub struct Solid {
/// Chamfers or fillets on this solid. /// Chamfers or fillets on this solid.
#[serde(default, skip_serializing_if = "Vec::is_empty")] #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_cuts: Vec<EdgeCut>, pub edge_cuts: Vec<EdgeCut>,
/// The units of the solid.
pub units: UnitLen, pub units: UnitLen,
/// Is this a sectional solid?
pub sectional: bool,
/// Metadata. /// Metadata.
#[serde(skip)] #[serde(skip)]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
@ -858,6 +820,13 @@ impl EdgeCut {
} }
} }
pub fn set_id(&mut self, id: uuid::Uuid) {
match self {
EdgeCut::Fillet { id: ref mut i, .. } => *i = id,
EdgeCut::Chamfer { id: ref mut i, .. } => *i = id,
}
}
pub fn edge_id(&self) -> uuid::Uuid { pub fn edge_id(&self) -> uuid::Uuid {
match self { match self {
EdgeCut::Fillet { edge_id, .. } => *edge_id, EdgeCut::Fillet { edge_id, .. } => *edge_id,
@ -865,6 +834,13 @@ impl EdgeCut {
} }
} }
pub fn set_edge_id(&mut self, id: uuid::Uuid) {
match self {
EdgeCut::Fillet { edge_id: ref mut i, .. } => *i = id,
EdgeCut::Chamfer { edge_id: ref mut i, .. } => *i = id,
}
}
pub fn tag(&self) -> Option<TagNode> { pub fn tag(&self) -> Option<TagNode> {
match self { match self {
EdgeCut::Fillet { tag, .. } => *tag.clone(), EdgeCut::Fillet { tag, .. } => *tag.clone(),
@ -929,6 +905,27 @@ impl Point3d {
pub const fn is_zero(&self) -> bool { pub const fn is_zero(&self) -> bool {
self.x == 0.0 && self.y == 0.0 && self.z == 0.0 self.x == 0.0 && self.y == 0.0 && self.z == 0.0
} }
/// Calculate the cross product of this vector with another
pub fn cross(&self, other: &Self) -> Self {
let other = if other.units == self.units {
other
} else {
&Point3d {
x: self.units.adjust_to(other.x, self.units).0,
y: self.units.adjust_to(other.y, self.units).0,
z: self.units.adjust_to(other.z, self.units).0,
units: self.units,
}
};
Self {
x: self.y * other.z - self.z * other.y,
y: self.z * other.x - self.x * other.z,
z: self.x * other.y - self.y * other.x,
units: self.units,
}
}
} }
impl From<[TyF64; 3]> for Point3d { impl From<[TyF64; 3]> for Point3d {
@ -1184,6 +1181,21 @@ impl Path {
} }
} }
pub fn set_id(&mut self, id: uuid::Uuid) {
match self {
Path::ToPoint { base } => base.geo_meta.id = id,
Path::Horizontal { base, .. } => base.geo_meta.id = id,
Path::AngledLineTo { base, .. } => base.geo_meta.id = id,
Path::Base { base } => base.geo_meta.id = id,
Path::TangentialArcTo { base, .. } => base.geo_meta.id = id,
Path::TangentialArc { base, .. } => base.geo_meta.id = id,
Path::Circle { base, .. } => base.geo_meta.id = id,
Path::CircleThreePoint { base, .. } => base.geo_meta.id = id,
Path::Arc { base, .. } => base.geo_meta.id = id,
Path::ArcThreePoint { base, .. } => base.geo_meta.id = id,
}
}
pub fn get_tag(&self) -> Option<TagNode> { pub fn get_tag(&self) -> Option<TagNode> {
match self { match self {
Path::ToPoint { base } => base.tag.clone(), Path::ToPoint { base } => base.tag.clone(),

View File

@ -9,8 +9,8 @@ use crate::{
execution::{ execution::{
annotations::{SETTINGS, SETTINGS_UNIT_LENGTH}, annotations::{SETTINGS, SETTINGS_UNIT_LENGTH},
types::{NumericType, PrimitiveType, RuntimeType, UnitLen}, types::{NumericType, PrimitiveType, RuntimeType, UnitLen},
EnvironmentRef, ExecState, Face, Helix, ImportedGeometry, MetaSettings, Metadata, Plane, Sketch, Solid, EnvironmentRef, ExecState, Face, Geometry, GeometryWithImportedGeometry, Helix, ImportedGeometry, MetaSettings,
TagIdentifier, Metadata, Plane, Sketch, Solid, TagIdentifier,
}, },
parsing::ast::types::{ parsing::ast::types::{
DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node, TagDeclarator, TagNode, DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node, TagDeclarator, TagNode,
@ -611,3 +611,22 @@ impl KclValue {
} }
} }
} }
impl From<Geometry> for KclValue {
fn from(value: Geometry) -> Self {
match value {
Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
}
}
}
impl From<GeometryWithImportedGeometry> for KclValue {
fn from(value: GeometryWithImportedGeometry) -> Self {
match value {
GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
}
}
}

View File

@ -1277,7 +1277,7 @@ const part001 = startSketchOn(XY)
|> line(end = [3, 4], tag = $seg01) |> line(end = [3, 4], tag = $seg01)
|> line(end = [ |> line(end = [
min(segLen(seg01), myVar), min(segLen(seg01), myVar),
-legLen(segLen(seg01), myVar) -legLen(hypotenuse = segLen(seg01), leg = myVar)
]) ])
"#; "#;
@ -1292,7 +1292,7 @@ const part001 = startSketchOn(XY)
|> line(end = [3, 4], tag = $seg01) |> line(end = [3, 4], tag = $seg01)
|> line(end = [ |> line(end = [
min(segLen(seg01), myVar), min(segLen(seg01), myVar),
legLen(segLen(seg01), myVar) legLen(hypotenuse = segLen(seg01), leg = myVar)
]) ])
"#; "#;
@ -1684,7 +1684,7 @@ let shape = layer() |> patternTransform(instances = 10, transform = transform)
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_math_execute_with_functions() { async fn test_math_execute_with_functions() {
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#; let ast = r#"const myVar = 2 + min(100, -1 + legLen(hypotenuse = 5, leg = 3))"#;
let result = parse_execute(ast).await.unwrap(); let result = parse_execute(ast).await.unwrap();
assert_eq!( assert_eq!(
5.0, 5.0,

View File

@ -28,6 +28,10 @@ pub enum RuntimeType {
} }
impl RuntimeType { impl RuntimeType {
pub fn edge() -> Self {
RuntimeType::Primitive(PrimitiveType::Edge)
}
pub fn sketch() -> Self { pub fn sketch() -> Self {
RuntimeType::Primitive(PrimitiveType::Sketch) RuntimeType::Primitive(PrimitiveType::Sketch)
} }
@ -1043,10 +1047,6 @@ impl KclValue {
.get("yAxis") .get("yAxis")
.and_then(Point3d::from_kcl_val) .and_then(Point3d::from_kcl_val)
.ok_or(CoercionError::from(self))?; .ok_or(CoercionError::from(self))?;
let z_axis = value
.get("zAxis")
.and_then(Point3d::from_kcl_val)
.ok_or(CoercionError::from(self))?;
let id = exec_state.mod_local.id_generator.next_uuid(); let id = exec_state.mod_local.id_generator.next_uuid();
let plane = Plane { let plane = Plane {
@ -1055,7 +1055,6 @@ impl KclValue {
origin, origin,
x_axis, x_axis,
y_axis, y_axis,
z_axis,
value: super::PlaneType::Uninit, value: super::PlaneType::Uninit,
meta: meta.clone(), meta: meta.clone(),
}; };

View File

@ -43,5 +43,5 @@ async fn main() {
.await .await
.unwrap(); .unwrap();
let mut exec_state = ExecState::new(&ctx); let mut exec_state = ExecState::new(&ctx);
ctx.run(&program, &mut exec_state).await.unwrap(); ctx.run(&program, &mut exec_state).await.map_err(|e| e.error).unwrap();
} }

View File

@ -2074,6 +2074,7 @@ fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
member_expression.map(Box::new).map(Expr::MemberExpression), member_expression.map(Box::new).map(Expr::MemberExpression),
literal.map(Expr::Literal), literal.map(Expr::Literal),
fn_call.map(Box::new).map(Expr::CallExpression), fn_call.map(Box::new).map(Expr::CallExpression),
fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
name.map(Box::new).map(Expr::Name), name.map(Box::new).map(Expr::Name),
binary_expr_in_parens.map(Box::new).map(Expr::BinaryExpression), binary_expr_in_parens.map(Box::new).map(Expr::BinaryExpression),
unnecessarily_bracketed, unnecessarily_bracketed,
@ -3254,6 +3255,14 @@ mod tests {
assert_eq!(err.message, "Unexpected end of file. The compiler expected )"); assert_eq!(err.message, "Unexpected end of file. The compiler expected )");
} }
#[test]
fn kw_call_as_operand() {
let tokens = crate::parsing::token::lex("f(x = 1)", ModuleId::default()).unwrap();
let tokens = tokens.as_slice();
let op = operand.parse(tokens).unwrap();
println!("{op:#?}");
}
#[test] #[test]
fn weird_program_just_a_pipe() { fn weird_program_just_a_pipe() {
let tokens = crate::parsing::token::lex("|", ModuleId::default()).unwrap(); let tokens = crate::parsing::token::lex("|", ModuleId::default()).unwrap();
@ -5389,6 +5398,7 @@ my14 = 4 ^ 2 - 3 ^ 2 * 2
bar = x, bar = x,
)"# )"#
); );
snapshot_test!(kw_function_in_binary_op, r#"val = f(x = 1) + 1"#);
} }
#[allow(unused)] #[allow(unused)]

View File

@ -0,0 +1,99 @@
---
source: kcl-lib/src/parsing/parser.rs
expression: actual
---
{
"body": [
{
"commentStart": 0,
"declaration": {
"commentStart": 0,
"end": 18,
"id": {
"commentStart": 0,
"end": 3,
"name": "val",
"start": 0,
"type": "Identifier"
},
"init": {
"commentStart": 6,
"end": 18,
"left": {
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 8,
"end": 9,
"name": "x",
"start": 8,
"type": "Identifier"
},
"arg": {
"commentStart": 12,
"end": 13,
"raw": "1",
"start": 12,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
}
}
],
"callee": {
"abs_path": false,
"commentStart": 6,
"end": 7,
"name": {
"commentStart": 6,
"end": 7,
"name": "f",
"start": 6,
"type": "Identifier"
},
"path": [],
"start": 6,
"type": "Name"
},
"commentStart": 6,
"end": 14,
"start": 6,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
"operator": "+",
"right": {
"commentStart": 17,
"end": 18,
"raw": "1",
"start": 17,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1.0,
"suffix": "None"
}
},
"start": 6,
"type": "BinaryExpression",
"type": "BinaryExpression"
},
"start": 0,
"type": "VariableDeclarator"
},
"end": 18,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"commentStart": 0,
"end": 18,
"start": 0
}

View File

@ -1354,48 +1354,6 @@ mod tangential_arc {
super::execute(TEST_NAME, true).await super::execute(TEST_NAME, true).await
} }
} }
mod big_number_angle_to_match_length_x {
const TEST_NAME: &str = "big_number_angle_to_match_length_x";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}
mod big_number_angle_to_match_length_y {
const TEST_NAME: &str = "big_number_angle_to_match_length_y";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}
mod sketch_on_face_circle_tagged { mod sketch_on_face_circle_tagged {
const TEST_NAME: &str = "sketch_on_face_circle_tagged"; const TEST_NAME: &str = "sketch_on_face_circle_tagged";

View File

@ -277,11 +277,11 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
/// import "tests/inputs/cube.sldprt" as cube /// import "tests/inputs/cube.sldprt" as cube
/// ///
/// cube /// cube
/// // |> appearance( /// |> appearance(
/// // color = "#ff0000", /// color = "#ff0000",
/// // metalness = 50, /// metalness = 50,
/// // roughness = 50 /// roughness = 50
/// // ) /// )
/// ``` /// ```
#[stdlib { #[stdlib {
name = "appearance", name = "appearance",

View File

@ -615,22 +615,6 @@ impl Args {
Ok(numbers) Ok(numbers)
} }
pub(crate) fn get_hypotenuse_leg(&self) -> Result<(f64, f64, NumericType), KclError> {
let numbers = self.get_number_array_with_types()?;
if numbers.len() != 2 {
return Err(KclError::Type(KclErrorDetails {
message: format!("Expected a number array of length 2, found `{:?}`", numbers),
source_ranges: vec![self.source_range],
}));
}
let mut numbers = numbers.into_iter();
let a = numbers.next().unwrap();
let b = numbers.next().unwrap();
Ok(NumericType::combine_eq_coerce(a, b))
}
pub(crate) fn get_sketches(&self, exec_state: &mut ExecState) -> Result<(Vec<Sketch>, Sketch), KclError> { pub(crate) fn get_sketches(&self, exec_state: &mut ExecState) -> Result<(Vec<Sketch>, Sketch), KclError> {
let Some(arg0) = self.args.first() else { let Some(arg0) = self.args.first() else {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
@ -675,64 +659,10 @@ impl Args {
Ok((sketches, sketch)) Ok((sketches, sketch))
} }
pub(crate) fn get_data<'a, T>(&'a self) -> Result<T, KclError>
where
T: FromArgs<'a>,
{
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_sketch_surface(&self) -> Result<([TyF64; 2], SketchSurface, Option<TagNode>), KclError> { pub(crate) fn get_data_and_sketch_surface(&self) -> Result<([TyF64; 2], SketchSurface, Option<TagNode>), KclError> {
FromArgs::from_args(self, 0) FromArgs::from_args(self, 0)
} }
pub(crate) fn get_length_and_solid(&self, exec_state: &mut ExecState) -> Result<(TyF64, Box<Solid>), KclError> {
let Some(arg0) = self.args.first() else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a `number(Length)` for first argument".to_owned(),
source_ranges: vec![self.source_range],
}));
};
let val0 = arg0.value.coerce(&RuntimeType::length(), exec_state).map_err(|_| {
KclError::Type(KclErrorDetails {
message: format!(
"Expected a `number(Length)` for first argument, found {}",
arg0.value.human_friendly_type()
),
source_ranges: vec![self.source_range],
})
})?;
let data = TyF64::from_kcl_val(&val0).unwrap();
let Some(arg1) = self.args.get(1) else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a solid for second argument".to_owned(),
source_ranges: vec![self.source_range],
}));
};
let sarg = arg1
.value
.coerce(&RuntimeType::Primitive(PrimitiveType::Solid), exec_state)
.map_err(|_| {
KclError::Type(KclErrorDetails {
message: format!(
"Expected a solid for second argument, found {}",
arg1.value.human_friendly_type()
),
source_ranges: vec![self.source_range],
})
})?;
let solid = match sarg {
KclValue::Solid { value } => value,
_ => unreachable!(),
};
Ok((data, solid))
}
pub(crate) fn get_tag_to_number_sketch(&self) -> Result<(TagIdentifier, TyF64, Sketch), KclError> {
FromArgs::from_args(self, 0)
}
pub(crate) async fn get_adjacent_face_to_tag( pub(crate) async fn get_adjacent_face_to_tag(
&self, &self,
exec_state: &mut ExecState, exec_state: &mut ExecState,
@ -1071,6 +1001,27 @@ impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::coord::Direction {
} }
} }
impl<'a> FromKclValue<'a> for crate::execution::Geometry {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Sketch { value } => Some(Self::Sketch(*value.to_owned())),
KclValue::Solid { value } => Some(Self::Solid(*value.to_owned())),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for crate::execution::GeometryWithImportedGeometry {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::Sketch { value } => Some(Self::Sketch(*value.to_owned())),
KclValue::Solid { value } => Some(Self::Solid(*value.to_owned())),
KclValue::ImportedGeometry(value) => Some(Self::ImportedGeometry(Box::new(value.clone()))),
_ => None,
}
}
}
impl<'a> FromKclValue<'a> for FaceTag { impl<'a> FromKclValue<'a> for FaceTag {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let case1 = || match arg.as_str() { let case1 = || match arg.as_str() {
@ -1130,7 +1081,6 @@ impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
origin: value.origin, origin: value.origin,
x_axis: value.x_axis, x_axis: value.x_axis,
y_axis: value.y_axis, y_axis: value.y_axis,
z_axis: value.z_axis,
}); });
} }
// Case 1: predefined plane // Case 1: predefined plane
@ -1151,13 +1101,7 @@ impl<'a> FromKclValue<'a> for super::sketch::PlaneData {
let origin = plane.get("origin").and_then(FromKclValue::from_kcl_val)?; let origin = plane.get("origin").and_then(FromKclValue::from_kcl_val)?;
let x_axis = plane.get("xAxis").and_then(FromKclValue::from_kcl_val)?; let x_axis = plane.get("xAxis").and_then(FromKclValue::from_kcl_val)?;
let y_axis = plane.get("yAxis").and_then(FromKclValue::from_kcl_val)?; let y_axis = plane.get("yAxis").and_then(FromKclValue::from_kcl_val)?;
let z_axis = plane.get("zAxis").and_then(FromKclValue::from_kcl_val)?; Some(Self::Plane { origin, x_axis, y_axis })
Some(Self::Plane {
origin,
x_axis,
y_axis,
z_axis,
})
} }
} }

View File

@ -0,0 +1,909 @@
//! Standard library clone.
use std::collections::HashMap;
use anyhow::Result;
use kcl_derive_docs::stdlib;
use kcmc::{
each_cmd as mcmd,
ok_response::{output::EntityGetAllChildUuids, OkModelingCmdResponse},
websocket::OkWebSocketResponseData,
ModelingCmd,
};
use kittycad_modeling_cmds::{self as kcmc};
use super::extrude::do_post_extrude;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
types::{NumericType, PrimitiveType, RuntimeType},
ExecState, GeometryWithImportedGeometry, KclValue, Sketch, Solid,
},
parsing::ast::types::TagNode,
std::{extrude::NamedCapTags, Args},
};
/// Clone a sketch or solid.
///
/// This works essentially like a copy-paste operation.
pub async fn clone(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let geometry = args.get_unlabeled_kw_arg_typed(
"geometry",
&RuntimeType::Union(vec![
RuntimeType::Primitive(PrimitiveType::Sketch),
RuntimeType::Primitive(PrimitiveType::Solid),
RuntimeType::imported(),
]),
exec_state,
)?;
let cloned = inner_clone(geometry, exec_state, args).await?;
Ok(cloned.into())
}
/// Clone a sketch or solid.
///
/// This works essentially like a copy-paste operation.
///
/// This doesn't really have much utility unless you need the equivalent of a double
/// instance pattern with zero transformations.
///
/// Really only use this function if YOU ARE SURE you need it. In most cases you
/// do not need clone and using a pattern with `instance = 2` is more appropriate.
///
/// ```no_run
/// // Clone a basic sketch and move it and extrude it.
/// exampleSketch = startSketchOn(XY)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
/// |> line(end = [-10, 0])
/// |> close()
///
/// clonedSketch = clone(exampleSketch)
/// |> scale(
/// x = 1.0,
/// y = 1.0,
/// z = 2.5,
/// )
/// |> translate(
/// x = 15.0,
/// y = 0,
/// z = 0,
/// )
/// |> extrude(length = 5)
/// ```
///
/// ```no_run
/// // Clone a basic solid and move it.
///
/// exampleSketch = startSketchOn(XY)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
/// |> line(end = [-10, 0])
/// |> close()
///
/// myPart = extrude(exampleSketch, length = 5)
/// clonedPart = clone(myPart)
/// |> translate(
/// x = 25.0,
/// )
/// ```
///
/// ```no_run
/// // Translate and rotate a cloned sketch to create a loft.
///
/// sketch001 = startSketchOn(XY)
/// |> startProfileAt([-10, 10], %)
/// |> xLine(length = 20)
/// |> yLine(length = -20)
/// |> xLine(length = -20)
/// |> close()
///
/// sketch002 = clone(sketch001)
/// |> translate(x = 0, y = 0, z = 20)
/// |> rotate(axis = [0, 0, 1.0], angle = 45)
///
/// loft([sketch001, sketch002])
/// ```
///
/// ```no_run
/// // Translate a cloned solid. Fillet only the clone.
///
/// sketch001 = startSketchOn(XY)
/// |> startProfileAt([-10, 10], %)
/// |> xLine(length = 20)
/// |> yLine(length = -20)
/// |> xLine(length = -20, tag = $filletTag)
/// |> close()
/// |> extrude(length = 5)
///
///
/// sketch002 = clone(sketch001)
/// |> translate(x = 0, y = 0, z = 20)
/// |> fillet(
/// radius = 2,
/// tags = [getNextAdjacentEdge(filletTag)],
/// )
/// ```
///
/// ```no_run
/// // You can reuse the tags from the original geometry with the cloned geometry.
///
/// sketch001 = startSketchOn(XY)
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10], tag = $sketchingFace)
/// |> line(end = [-10, 0])
/// |> close()
///
/// sketch002 = clone(sketch001)
/// |> translate(x = 10, y = 20, z = 0)
/// |> extrude(length = 5)
///
/// startSketchOn(sketch002, face = sketchingFace)
/// |> startProfileAt([1, 1], %)
/// |> line(end = [8, 0])
/// |> line(end = [0, 8])
/// |> line(end = [-8, 0])
/// |> close(tag = $sketchingFace002)
/// |> extrude(length = 10)
/// ```
///
/// ```no_run
/// // You can also use the tags from the original geometry to fillet the cloned geometry.
///
/// width = 20
/// length = 10
/// thickness = 1
/// filletRadius = 2
///
/// mountingPlateSketch = startSketchOn(XY)
/// |> startProfileAt([-width/2, -length/2], %)
/// |> line(endAbsolute = [width/2, -length/2], tag = $edge1)
/// |> line(endAbsolute = [width/2, length/2], tag = $edge2)
/// |> line(endAbsolute = [-width/2, length/2], tag = $edge3)
/// |> close(tag = $edge4)
///
/// mountingPlate = extrude(mountingPlateSketch, length = thickness)
///
/// clonedMountingPlate = clone(mountingPlate)
/// |> fillet(
/// radius = filletRadius,
/// tags = [
/// getNextAdjacentEdge(edge1),
/// getNextAdjacentEdge(edge2),
/// getNextAdjacentEdge(edge3),
/// getNextAdjacentEdge(edge4)
/// ],
/// )
/// |> translate(x = 0, y = 50, z = 0)
/// ```
///
/// ```no_run
/// // Create a spring by sweeping around a helix path from a cloned sketch.
///
/// // Create a helix around the Z axis.
/// helixPath = helix(
/// angleStart = 0,
/// ccw = true,
/// revolutions = 4,
/// length = 10,
/// radius = 5,
/// axis = Z,
/// )
///
///
/// springSketch = startSketchOn(YZ)
/// |> circle( center = [0, 0], radius = 1)
///
/// // Create a spring by sweeping around the helix path.
/// sweepedSpring = clone(springSketch)
/// |> translate(x=100)
/// |> sweep(path = helixPath)
/// ```
///
/// ```
/// // A donut shape from a cloned sketch.
/// sketch001 = startSketchOn(XY)
/// |> circle( center = [15, 0], radius = 5 )
///
/// sketch002 = clone(sketch001)
/// |> translate( z = 30)
/// |> revolve(
/// angle = 360,
/// axis = Y,
/// )
/// ```
///
/// ```no_run
/// // Sketch on the end of a revolved face by tagging the end face.
/// // This shows the cloned geometry will have the same tags as the original geometry.
///
/// exampleSketch = startSketchOn(XY)
/// |> startProfileAt([4, 12], %)
/// |> line(end = [2, 0])
/// |> line(end = [0, -6])
/// |> line(end = [4, -6])
/// |> line(end = [0, -6])
/// |> line(end = [-3.75, -4.5])
/// |> line(end = [0, -5.5])
/// |> line(end = [-2, 0])
/// |> close()
///
/// example001 = revolve(exampleSketch, axis = Y, angle = 180, tagEnd = $end01)
///
/// // example002 = clone(example001)
/// // |> translate(x = 0, y = 20, z = 0)
///
/// // Sketch on the cloned face.
/// // exampleSketch002 = startSketchOn(example002, face = end01)
/// // |> startProfileAt([4.5, -5], %)
/// // |> line(end = [0, 5])
/// // |> line(end = [5, 0])
/// // |> line(end = [0, -5])
/// // |> close()
///
/// // example003 = extrude(exampleSketch002, length = 5)
/// ```
///
/// ```no_run
/// // Clone an imported model.
///
/// import "tests/inputs/cube.sldprt" as cube
///
/// myCube = cube
///
/// clonedCube = clone(myCube)
/// |> translate(
/// x = 1020,
/// )
/// |> appearance(
/// color = "#ff0000",
/// metalness = 50,
/// roughness = 50
/// )
/// ```
#[stdlib {
name = "clone",
feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
args = {
geometry = { docs = "The sketch, solid, or imported geometry to be cloned" },
}
}]
async fn inner_clone(
geometry: GeometryWithImportedGeometry,
exec_state: &mut ExecState,
args: Args,
) -> Result<GeometryWithImportedGeometry, KclError> {
let new_id = exec_state.next_uuid();
let mut geometry = geometry.clone();
let old_id = geometry.id(&args.ctx).await?;
let mut new_geometry = match &geometry {
GeometryWithImportedGeometry::ImportedGeometry(imported) => {
let mut new_imported = imported.clone();
new_imported.id = new_id;
GeometryWithImportedGeometry::ImportedGeometry(new_imported)
}
GeometryWithImportedGeometry::Sketch(sketch) => {
let mut new_sketch = sketch.clone();
new_sketch.id = new_id;
new_sketch.original_id = new_id;
new_sketch.artifact_id = new_id.into();
GeometryWithImportedGeometry::Sketch(new_sketch)
}
GeometryWithImportedGeometry::Solid(solid) => {
let mut new_solid = solid.clone();
new_solid.id = new_id;
new_solid.sketch.original_id = new_id;
new_solid.artifact_id = new_id.into();
GeometryWithImportedGeometry::Solid(new_solid)
}
};
if args.ctx.no_engine_commands().await {
return Ok(new_geometry);
}
args.batch_modeling_cmd(new_id, ModelingCmd::from(mcmd::EntityClone { entity_id: old_id }))
.await?;
fix_tags_and_references(&mut new_geometry, old_id, exec_state, &args)
.await
.map_err(|e| {
KclError::Internal(KclErrorDetails {
message: format!("failed to fix tags and references: {:?}", e),
source_ranges: vec![args.source_range],
})
})?;
Ok(new_geometry)
}
/// Fix the tags and references of the cloned geometry.
async fn fix_tags_and_references(
new_geometry: &mut GeometryWithImportedGeometry,
old_geometry_id: uuid::Uuid,
exec_state: &mut ExecState,
args: &Args,
) -> Result<()> {
let new_geometry_id = new_geometry.id(&args.ctx).await?;
let entity_id_map = get_old_new_child_map(new_geometry_id, old_geometry_id, exec_state, args).await?;
// Fix the path references in the new geometry.
match new_geometry {
GeometryWithImportedGeometry::ImportedGeometry(_) => {}
GeometryWithImportedGeometry::Sketch(sketch) => {
fix_sketch_tags_and_references(sketch, &entity_id_map, exec_state).await?;
}
GeometryWithImportedGeometry::Solid(solid) => {
// Make the sketch id the new geometry id.
solid.sketch.id = new_geometry_id;
solid.sketch.original_id = new_geometry_id;
solid.sketch.artifact_id = new_geometry_id.into();
fix_sketch_tags_and_references(&mut solid.sketch, &entity_id_map, exec_state).await?;
let (start_tag, end_tag) = get_named_cap_tags(solid);
// Fix the edge cuts.
for edge_cut in solid.edge_cuts.iter_mut() {
let Some(new_edge_id) = entity_id_map.get(&edge_cut.edge_id()) else {
anyhow::bail!("Failed to find new edge id for old edge id: {:?}", edge_cut.edge_id());
};
edge_cut.set_edge_id(*new_edge_id);
let Some(id) = entity_id_map.get(&edge_cut.id()) else {
anyhow::bail!(
"Failed to find new edge cut id for old edge cut id: {:?}",
edge_cut.id()
);
};
edge_cut.set_id(*id);
}
// Do the after extrude things to update those ids, based on the new sketch
// information.
let new_solid = do_post_extrude(
&solid.sketch,
new_geometry_id.into(),
crate::std::args::TyF64::new(
solid.height,
NumericType::Known(crate::execution::types::UnitType::Length(solid.units)),
),
solid.sectional,
&NamedCapTags {
start: start_tag.as_ref(),
end: end_tag.as_ref(),
},
exec_state,
args,
)
.await?;
*solid = new_solid;
}
}
Ok(())
}
async fn get_old_new_child_map(
new_geometry_id: uuid::Uuid,
old_geometry_id: uuid::Uuid,
exec_state: &mut ExecState,
args: &Args,
) -> Result<HashMap<uuid::Uuid, uuid::Uuid>> {
// Get the new geometries entity ids.
let response = args
.send_modeling_cmd(
exec_state.next_uuid(),
ModelingCmd::from(mcmd::EntityGetAllChildUuids {
entity_id: new_geometry_id,
}),
)
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response:
OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids {
entity_ids: new_entity_ids,
}),
} = response
else {
anyhow::bail!("Expected EntityGetAllChildUuids response, got: {:?}", response);
};
// Get the old geometries entity ids.
let response = args
.send_modeling_cmd(
exec_state.next_uuid(),
ModelingCmd::from(mcmd::EntityGetAllChildUuids {
entity_id: old_geometry_id,
}),
)
.await?;
let OkWebSocketResponseData::Modeling {
modeling_response:
OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids {
entity_ids: old_entity_ids,
}),
} = response
else {
anyhow::bail!("Expected EntityGetAllChildUuids response, got: {:?}", response);
};
// Create a map of old entity ids to new entity ids.
Ok(HashMap::from_iter(
old_entity_ids
.iter()
.zip(new_entity_ids.iter())
.map(|(old_id, new_id)| (*old_id, *new_id)),
))
}
/// Fix the tags and references of a sketch.
async fn fix_sketch_tags_and_references(
new_sketch: &mut Sketch,
entity_id_map: &HashMap<uuid::Uuid, uuid::Uuid>,
exec_state: &mut ExecState,
) -> Result<()> {
// Fix the path references in the sketch.
for path in new_sketch.paths.as_mut_slice() {
let Some(new_path_id) = entity_id_map.get(&path.get_id()) else {
anyhow::bail!("Failed to find new path id for old path id: {:?}", path.get_id());
};
path.set_id(*new_path_id);
}
// Fix the tags
// This is annoying, in order to fix the tags we need to iterate over the paths again, but not
// mutable borrow the paths.
for path in new_sketch.paths.clone() {
// Check if this path has a tag.
if let Some(tag) = path.get_tag() {
new_sketch.add_tag(&tag, &path, exec_state);
}
}
// Fix the base path.
// TODO: Right now this one does not work, ignore for now and see if we really need it.
/* let Some(new_base_path) = entity_id_map.get(&new_sketch.start.geo_meta.id) else {
anyhow::bail!(
"Failed to find new base path id for old base path id: {:?}",
new_sketch.start.geo_meta.id
);
};
new_sketch.start.geo_meta.id = *new_base_path;*/
Ok(())
}
// Return the named cap tags for the original solid.
fn get_named_cap_tags(solid: &Solid) -> (Option<TagNode>, Option<TagNode>) {
let mut start_tag = None;
let mut end_tag = None;
// Check the start cap.
if let Some(start_cap_id) = solid.start_cap_id {
// Check if we had a value for that cap.
for value in &solid.value {
if value.get_id() == start_cap_id {
start_tag = value.get_tag().clone();
break;
}
}
}
// Check the end cap.
if let Some(end_cap_id) = solid.end_cap_id {
// Check if we had a value for that cap.
for value in &solid.value {
if value.get_id() == end_cap_id {
end_tag = value.get_tag().clone();
break;
}
}
}
(start_tag, end_tag)
}
#[cfg(test)]
mod tests {
use pretty_assertions::{assert_eq, assert_ne};
use crate::exec::KclValue;
// Ensure the clone function returns a sketch with different ids for all the internal paths and
// the resulting sketch.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_clone_sketch() {
let code = r#"cube = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, 10])
|> line(end = [10, 0])
|> line(end = [0, -10])
|> close()
clonedCube = clone(cube)
"#;
let ctx = crate::test_server::new_context(true, None).await.unwrap();
let program = crate::Program::parse_no_errs(code).unwrap();
// Execute the program.
let result = ctx.run_with_caching(program.clone()).await.unwrap();
let cube = result.variables.get("cube").unwrap();
let cloned_cube = result.variables.get("clonedCube").unwrap();
assert_ne!(cube, cloned_cube);
let KclValue::Sketch { value: cube } = cube else {
panic!("Expected a sketch, got: {:?}", cube);
};
let KclValue::Sketch { value: cloned_cube } = cloned_cube else {
panic!("Expected a sketch, got: {:?}", cloned_cube);
};
assert_ne!(cube.id, cloned_cube.id);
assert_ne!(cube.original_id, cloned_cube.original_id);
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
assert_eq!(cloned_cube.original_id, cloned_cube.id);
for (path, cloned_path) in cube.paths.iter().zip(cloned_cube.paths.iter()) {
assert_ne!(path.get_id(), cloned_path.get_id());
assert_eq!(path.get_tag(), cloned_path.get_tag());
}
assert_eq!(cube.tags.len(), 0);
assert_eq!(cloned_cube.tags.len(), 0);
}
// Ensure the clone function returns a solid with different ids for all the internal paths and
// references.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_clone_solid() {
let code = r#"cube = startSketchOn(XY)
|> startProfileAt([0,0], %)
|> line(end = [0, 10])
|> line(end = [10, 0])
|> line(end = [0, -10])
|> close()
|> extrude(length = 5)
clonedCube = clone(cube)
"#;
let ctx = crate::test_server::new_context(true, None).await.unwrap();
let program = crate::Program::parse_no_errs(code).unwrap();
// Execute the program.
let result = ctx.run_with_caching(program.clone()).await.unwrap();
let cube = result.variables.get("cube").unwrap();
let cloned_cube = result.variables.get("clonedCube").unwrap();
assert_ne!(cube, cloned_cube);
let KclValue::Solid { value: cube } = cube else {
panic!("Expected a solid, got: {:?}", cube);
};
let KclValue::Solid { value: cloned_cube } = cloned_cube else {
panic!("Expected a solid, got: {:?}", cloned_cube);
};
assert_ne!(cube.id, cloned_cube.id);
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
assert_ne!(path.get_id(), cloned_path.get_id());
assert_eq!(path.get_tag(), cloned_path.get_tag());
}
for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
assert_ne!(value.get_id(), cloned_value.get_id());
assert_eq!(value.get_tag(), cloned_value.get_tag());
}
assert_eq!(cube.sketch.tags.len(), 0);
assert_eq!(cloned_cube.sketch.tags.len(), 0);
assert_eq!(cube.edge_cuts.len(), 0);
assert_eq!(cloned_cube.edge_cuts.len(), 0);
}
// Ensure the clone function returns a sketch with different ids for all the internal paths and
// the resulting sketch.
// AND TAGS.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_clone_sketch_with_tags() {
let code = r#"cube = startSketchOn(XY)
|> startProfileAt([0,0], %) // tag this one
|> line(end = [0, 10], tag = $tag02)
|> line(end = [10, 0], tag = $tag03)
|> line(end = [0, -10], tag = $tag04)
|> close(tag = $tag05)
clonedCube = clone(cube)
"#;
let ctx = crate::test_server::new_context(true, None).await.unwrap();
let program = crate::Program::parse_no_errs(code).unwrap();
// Execute the program.
let result = ctx.run_with_caching(program.clone()).await.unwrap();
let cube = result.variables.get("cube").unwrap();
let cloned_cube = result.variables.get("clonedCube").unwrap();
assert_ne!(cube, cloned_cube);
let KclValue::Sketch { value: cube } = cube else {
panic!("Expected a sketch, got: {:?}", cube);
};
let KclValue::Sketch { value: cloned_cube } = cloned_cube else {
panic!("Expected a sketch, got: {:?}", cloned_cube);
};
assert_ne!(cube.id, cloned_cube.id);
assert_ne!(cube.original_id, cloned_cube.original_id);
for (path, cloned_path) in cube.paths.iter().zip(cloned_cube.paths.iter()) {
assert_ne!(path.get_id(), cloned_path.get_id());
assert_eq!(path.get_tag(), cloned_path.get_tag());
}
for (tag_name, tag) in &cube.tags {
let cloned_tag = cloned_cube.tags.get(tag_name).unwrap();
let tag_info = tag.get_cur_info().unwrap();
let cloned_tag_info = cloned_tag.get_cur_info().unwrap();
assert_ne!(tag_info.id, cloned_tag_info.id);
assert_ne!(tag_info.sketch, cloned_tag_info.sketch);
assert_ne!(tag_info.path, cloned_tag_info.path);
assert_eq!(tag_info.surface, None);
assert_eq!(cloned_tag_info.surface, None);
}
}
// Ensure the clone function returns a solid with different ids for all the internal paths and
// references.
// WITH TAGS.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_clone_solid_with_tags() {
let code = r#"cube = startSketchOn(XY)
|> startProfileAt([0,0], %) // tag this one
|> line(end = [0, 10], tag = $tag02)
|> line(end = [10, 0], tag = $tag03)
|> line(end = [0, -10], tag = $tag04)
|> close(tag = $tag05)
|> extrude(length = 5) // TODO: Tag these
clonedCube = clone(cube)
"#;
let ctx = crate::test_server::new_context(true, None).await.unwrap();
let program = crate::Program::parse_no_errs(code).unwrap();
// Execute the program.
let result = ctx.run_with_caching(program.clone()).await.unwrap();
let cube = result.variables.get("cube").unwrap();
let cloned_cube = result.variables.get("clonedCube").unwrap();
assert_ne!(cube, cloned_cube);
let KclValue::Solid { value: cube } = cube else {
panic!("Expected a solid, got: {:?}", cube);
};
let KclValue::Solid { value: cloned_cube } = cloned_cube else {
panic!("Expected a solid, got: {:?}", cloned_cube);
};
assert_ne!(cube.id, cloned_cube.id);
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
assert_ne!(path.get_id(), cloned_path.get_id());
assert_eq!(path.get_tag(), cloned_path.get_tag());
}
for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
assert_ne!(value.get_id(), cloned_value.get_id());
assert_eq!(value.get_tag(), cloned_value.get_tag());
}
for (tag_name, tag) in &cube.sketch.tags {
let cloned_tag = cloned_cube.sketch.tags.get(tag_name).unwrap();
let tag_info = tag.get_cur_info().unwrap();
let cloned_tag_info = cloned_tag.get_cur_info().unwrap();
assert_ne!(tag_info.id, cloned_tag_info.id);
assert_ne!(tag_info.sketch, cloned_tag_info.sketch);
assert_ne!(tag_info.path, cloned_tag_info.path);
assert_ne!(tag_info.surface, cloned_tag_info.surface);
}
assert_eq!(cube.edge_cuts.len(), 0);
assert_eq!(cloned_cube.edge_cuts.len(), 0);
}
// Ensure we can get all paths even on a sketch where we closed it and it was already closed.
#[tokio::test(flavor = "multi_thread")]
#[ignore = "this test is not working yet, need to fix the getting of ids if sketch already closed"]
async fn kcl_test_clone_cube_already_closed_sketch() {
let code = r#"// Clone a basic solid and move it.
exampleSketch = startSketchOn(XY)
|> startProfileAt([0, 0], %)
|> line(end = [10, 0])
|> line(end = [0, 10])
|> line(end = [-10, 0])
|> line(end = [0, -10])
|> close()
cube = extrude(exampleSketch, length = 5)
clonedCube = clone(cube)
|> translate(
x = 25.0,
)"#;
let ctx = crate::test_server::new_context(true, None).await.unwrap();
let program = crate::Program::parse_no_errs(code).unwrap();
// Execute the program.
let result = ctx.run_with_caching(program.clone()).await.unwrap();
let cube = result.variables.get("cube").unwrap();
let cloned_cube = result.variables.get("clonedCube").unwrap();
assert_ne!(cube, cloned_cube);
let KclValue::Solid { value: cube } = cube else {
panic!("Expected a solid, got: {:?}", cube);
};
let KclValue::Solid { value: cloned_cube } = cloned_cube else {
panic!("Expected a solid, got: {:?}", cloned_cube);
};
assert_ne!(cube.id, cloned_cube.id);
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
assert_ne!(path.get_id(), cloned_path.get_id());
assert_eq!(path.get_tag(), cloned_path.get_tag());
}
for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
assert_ne!(value.get_id(), cloned_value.get_id());
assert_eq!(value.get_tag(), cloned_value.get_tag());
}
for (tag_name, tag) in &cube.sketch.tags {
let cloned_tag = cloned_cube.sketch.tags.get(tag_name).unwrap();
let tag_info = tag.get_cur_info().unwrap();
let cloned_tag_info = cloned_tag.get_cur_info().unwrap();
assert_ne!(tag_info.id, cloned_tag_info.id);
assert_ne!(tag_info.sketch, cloned_tag_info.sketch);
assert_ne!(tag_info.path, cloned_tag_info.path);
assert_ne!(tag_info.surface, cloned_tag_info.surface);
}
for (edge_cut, cloned_edge_cut) in cube.edge_cuts.iter().zip(cloned_cube.edge_cuts.iter()) {
assert_ne!(edge_cut.id(), cloned_edge_cut.id());
assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id());
assert_eq!(edge_cut.tag(), cloned_edge_cut.tag());
}
}
// Ensure the clone function returns a solid with different ids for all the internal paths and
// references.
// WITH TAGS AND EDGE CUTS.
#[tokio::test(flavor = "multi_thread")]
#[ignore = "this test is not working yet, need to fix the edge cut ids"]
async fn kcl_test_clone_solid_with_edge_cuts() {
let code = r#"cube = startSketchOn(XY)
|> startProfileAt([0,0], %) // tag this one
|> line(end = [0, 10], tag = $tag02)
|> line(end = [10, 0], tag = $tag03)
|> line(end = [0, -10], tag = $tag04)
|> close(tag = $tag05)
|> extrude(length = 5) // TODO: Tag these
|> fillet(
radius = 2,
tags = [
getNextAdjacentEdge(tag02),
],
tag = $fillet01,
)
|> fillet(
radius = 2,
tags = [
getNextAdjacentEdge(tag04),
],
tag = $fillet02,
)
|> chamfer(
length = 2,
tags = [
getNextAdjacentEdge(tag03),
],
tag = $chamfer01,
)
|> chamfer(
length = 2,
tags = [
getNextAdjacentEdge(tag05),
],
tag = $chamfer02,
)
clonedCube = clone(cube)
"#;
let ctx = crate::test_server::new_context(true, None).await.unwrap();
let program = crate::Program::parse_no_errs(code).unwrap();
// Execute the program.
let result = ctx.run_with_caching(program.clone()).await.unwrap();
let cube = result.variables.get("cube").unwrap();
let cloned_cube = result.variables.get("clonedCube").unwrap();
assert_ne!(cube, cloned_cube);
let KclValue::Solid { value: cube } = cube else {
panic!("Expected a solid, got: {:?}", cube);
};
let KclValue::Solid { value: cloned_cube } = cloned_cube else {
panic!("Expected a solid, got: {:?}", cloned_cube);
};
assert_ne!(cube.id, cloned_cube.id);
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
assert_ne!(path.get_id(), cloned_path.get_id());
assert_eq!(path.get_tag(), cloned_path.get_tag());
}
for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
assert_ne!(value.get_id(), cloned_value.get_id());
assert_eq!(value.get_tag(), cloned_value.get_tag());
}
for (tag_name, tag) in &cube.sketch.tags {
let cloned_tag = cloned_cube.sketch.tags.get(tag_name).unwrap();
let tag_info = tag.get_cur_info().unwrap();
let cloned_tag_info = cloned_tag.get_cur_info().unwrap();
assert_ne!(tag_info.id, cloned_tag_info.id);
assert_ne!(tag_info.sketch, cloned_tag_info.sketch);
assert_ne!(tag_info.path, cloned_tag_info.path);
assert_ne!(tag_info.surface, cloned_tag_info.surface);
}
for (edge_cut, cloned_edge_cut) in cube.edge_cuts.iter().zip(cloned_cube.edge_cuts.iter()) {
assert_ne!(edge_cut.id(), cloned_edge_cut.id());
assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id());
assert_eq!(edge_cut.tag(), cloned_edge_cut.tag());
}
}
}

View File

@ -8,15 +8,15 @@ use uuid::Uuid;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ExecState, ExtrudeSurface, KclValue, TagIdentifier}, execution::{types::RuntimeType, ExecState, ExtrudeSurface, KclValue, TagIdentifier},
std::Args, std::Args,
}; };
/// Get the opposite edge to the edge given. /// Get the opposite edge to the edge given.
pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let tag: TagIdentifier = args.get_data()?; let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::edge(), exec_state)?;
let edge = inner_get_opposite_edge(tag, exec_state, args.clone()).await?; let edge = inner_get_opposite_edge(input_edge, exec_state, args.clone()).await?;
Ok(KclValue::Uuid { Ok(KclValue::Uuid {
value: edge, value: edge,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
@ -53,15 +53,24 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
/// ``` /// ```
#[stdlib { #[stdlib {
name = "getOppositeEdge", name = "getOppositeEdge",
keywords = true,
unlabeled_first = true,
args = {
edge = { docs = "The tag of the edge you want to find the opposite edge of." },
}
}] }]
async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<Uuid, KclError> { async fn inner_get_opposite_edge(
edge: TagIdentifier,
exec_state: &mut ExecState,
args: Args,
) -> Result<Uuid, KclError> {
if args.ctx.no_engine_commands().await { if args.ctx.no_engine_commands().await {
return Ok(exec_state.next_uuid()); return Ok(exec_state.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
let id = exec_state.next_uuid(); let id = exec_state.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
@ -88,9 +97,9 @@ async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState,
/// Get the next adjacent edge to the edge given. /// Get the next adjacent edge to the edge given.
pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let tag: TagIdentifier = args.get_data()?; let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::edge(), exec_state)?;
let edge = inner_get_next_adjacent_edge(tag, exec_state, args.clone()).await?; let edge = inner_get_next_adjacent_edge(input_edge, exec_state, args.clone()).await?;
Ok(KclValue::Uuid { Ok(KclValue::Uuid {
value: edge, value: edge,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
@ -127,19 +136,24 @@ pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> R
/// ``` /// ```
#[stdlib { #[stdlib {
name = "getNextAdjacentEdge", name = "getNextAdjacentEdge",
keywords = true,
unlabeled_first = true,
args = {
edge = { docs = "The tag of the edge you want to find the next adjacent edge of." },
}
}] }]
async fn inner_get_next_adjacent_edge( async fn inner_get_next_adjacent_edge(
tag: TagIdentifier, edge: TagIdentifier,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Uuid, KclError> { ) -> Result<Uuid, KclError> {
if args.ctx.no_engine_commands().await { if args.ctx.no_engine_commands().await {
return Ok(exec_state.next_uuid()); return Ok(exec_state.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
let id = exec_state.next_uuid(); let id = exec_state.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
@ -167,7 +181,7 @@ async fn inner_get_next_adjacent_edge(
adjacent_edge.edge.ok_or_else(|| { adjacent_edge.edge.ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
message: format!("No edge found next adjacent to tag: `{}`", tag.value), message: format!("No edge found next adjacent to tag: `{}`", edge.value),
source_ranges: vec![args.source_range], source_ranges: vec![args.source_range],
}) })
}) })
@ -175,9 +189,9 @@ async fn inner_get_next_adjacent_edge(
/// Get the previous adjacent edge to the edge given. /// Get the previous adjacent edge to the edge given.
pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let tag: TagIdentifier = args.get_data()?; let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::edge(), exec_state)?;
let edge = inner_get_previous_adjacent_edge(tag, exec_state, args.clone()).await?; let edge = inner_get_previous_adjacent_edge(input_edge, exec_state, args.clone()).await?;
Ok(KclValue::Uuid { Ok(KclValue::Uuid {
value: edge, value: edge,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
@ -214,19 +228,24 @@ pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args)
/// ``` /// ```
#[stdlib { #[stdlib {
name = "getPreviousAdjacentEdge", name = "getPreviousAdjacentEdge",
keywords = true,
unlabeled_first = true,
args = {
edge = { docs = "The tag of the edge you want to find the previous adjacent edge of." },
}
}] }]
async fn inner_get_previous_adjacent_edge( async fn inner_get_previous_adjacent_edge(
tag: TagIdentifier, edge: TagIdentifier,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Uuid, KclError> { ) -> Result<Uuid, KclError> {
if args.ctx.no_engine_commands().await { if args.ctx.no_engine_commands().await {
return Ok(exec_state.next_uuid()); return Ok(exec_state.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
let id = exec_state.next_uuid(); let id = exec_state.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
@ -253,7 +272,7 @@ async fn inner_get_previous_adjacent_edge(
adjacent_edge.edge.ok_or_else(|| { adjacent_edge.edge.ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
message: format!("No edge found previous adjacent to tag: `{}`", tag.value), message: format!("No edge found previous adjacent to tag: `{}`", edge.value),
source_ranges: vec![args.source_range], source_ranges: vec![args.source_range],
}) })
}) })

View File

@ -471,6 +471,7 @@ pub(crate) async fn do_post_extrude<'a>(
meta: sketch.meta.clone(), meta: sketch.meta.clone(),
units: sketch.units, units: sketch.units,
height: length.to_length_units(sketch.units), height: length.to_length_units(sketch.units),
sectional,
sketch, sketch,
start_cap_id, start_cap_id,
end_cap_id, end_cap_id,

View File

@ -6,6 +6,7 @@ pub mod array;
pub mod assert; pub mod assert;
pub mod axis_or_reference; pub mod axis_or_reference;
pub mod chamfer; pub mod chamfer;
pub mod clone;
pub mod convert; pub mod convert;
pub mod csg; pub mod csg;
pub mod edge; pub mod edge;
@ -29,6 +30,7 @@ pub mod utils;
use anyhow::Result; use anyhow::Result;
pub use args::Args; pub use args::Args;
use args::TyF64;
use indexmap::IndexMap; use indexmap::IndexMap;
use kcl_derive_docs::stdlib; use kcl_derive_docs::stdlib;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -39,7 +41,10 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
docs::StdLibFn, docs::StdLibFn,
errors::KclError, errors::KclError,
execution::{types::PrimitiveType, ExecState, KclValue}, execution::{
types::{NumericType, PrimitiveType, RuntimeType, UnitAngle, UnitType},
ExecState, KclValue,
},
parsing::ast::types::Name, parsing::ast::types::Name,
}; };
@ -67,8 +72,6 @@ lazy_static! {
Box::new(crate::std::segment::SegLen), Box::new(crate::std::segment::SegLen),
Box::new(crate::std::segment::SegAng), Box::new(crate::std::segment::SegAng),
Box::new(crate::std::segment::TangentToEnd), Box::new(crate::std::segment::TangentToEnd),
Box::new(crate::std::segment::AngleToMatchLengthX),
Box::new(crate::std::segment::AngleToMatchLengthY),
Box::new(crate::std::shapes::CircleThreePoint), Box::new(crate::std::shapes::CircleThreePoint),
Box::new(crate::std::shapes::Polygon), Box::new(crate::std::shapes::Polygon),
Box::new(crate::std::sketch::InvoluteCircular), Box::new(crate::std::sketch::InvoluteCircular),
@ -87,6 +90,7 @@ lazy_static! {
Box::new(crate::std::sketch::TangentialArc), Box::new(crate::std::sketch::TangentialArc),
Box::new(crate::std::sketch::BezierCurve), Box::new(crate::std::sketch::BezierCurve),
Box::new(crate::std::sketch::Hole), Box::new(crate::std::sketch::Hole),
Box::new(crate::std::clone::Clone),
Box::new(crate::std::patterns::PatternLinear2D), Box::new(crate::std::patterns::PatternLinear2D),
Box::new(crate::std::patterns::PatternLinear3D), Box::new(crate::std::patterns::PatternLinear3D),
Box::new(crate::std::patterns::PatternCircular2D), Box::new(crate::std::patterns::PatternCircular2D),
@ -107,7 +111,6 @@ lazy_static! {
Box::new(crate::std::shell::Hollow), Box::new(crate::std::shell::Hollow),
Box::new(crate::std::sweep::Sweep), Box::new(crate::std::sweep::Sweep),
Box::new(crate::std::loft::Loft), Box::new(crate::std::loft::Loft),
Box::new(crate::std::planes::OffsetPlane),
Box::new(crate::std::math::Acos), Box::new(crate::std::math::Acos),
Box::new(crate::std::math::Asin), Box::new(crate::std::math::Asin),
Box::new(crate::std::math::Atan), Box::new(crate::std::math::Atan),
@ -205,6 +208,10 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|e, a| Box::pin(crate::std::revolve::revolve(e, a)), |e, a| Box::pin(crate::std::revolve::revolve(e, a)),
StdFnProps::default("std::revolve").include_in_feature_tree(), StdFnProps::default("std::revolve").include_in_feature_tree(),
), ),
("prelude", "offsetPlane") => (
|e, a| Box::pin(crate::std::planes::offset_plane(e, a)),
StdFnProps::default("std::offsetPlane").include_in_feature_tree(),
),
_ => unreachable!(), _ => unreachable!(),
} }
} }
@ -284,8 +291,10 @@ pub enum FunctionKind {
const DEFAULT_TOLERANCE: f64 = 0.0000001; const DEFAULT_TOLERANCE: f64 = 0.0000001;
/// Compute the length of the given leg. /// Compute the length of the given leg.
pub async fn leg_length(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn leg_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (hypotenuse, leg, ty) = args.get_hypotenuse_leg()?; let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
let (hypotenuse, leg, ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
let result = inner_leg_length(hypotenuse, leg); let result = inner_leg_length(hypotenuse, leg);
Ok(KclValue::from_number_with_type(result, ty, vec![args.into()])) Ok(KclValue::from_number_with_type(result, ty, vec![args.into()]))
} }
@ -293,10 +302,16 @@ pub async fn leg_length(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
/// Compute the length of the given leg. /// Compute the length of the given leg.
/// ///
/// ```no_run /// ```no_run
/// legLen(5, 3) /// legLen(hypotenuse = 5, leg = 3)
/// ``` /// ```
#[stdlib { #[stdlib {
name = "legLen", name = "legLen",
keywords = true,
unlabeled_first = false,
args = {
hypotenuse = { docs = "The length of the triangle's hypotenuse" },
leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" },
},
tags = ["utilities"], tags = ["utilities"],
}] }]
fn inner_leg_length(hypotenuse: f64, leg: f64) -> f64 { fn inner_leg_length(hypotenuse: f64, leg: f64) -> f64 {
@ -304,19 +319,31 @@ fn inner_leg_length(hypotenuse: f64, leg: f64) -> f64 {
} }
/// Compute the angle of the given leg for x. /// Compute the angle of the given leg for x.
pub async fn leg_angle_x(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn leg_angle_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (hypotenuse, leg, ty) = args.get_hypotenuse_leg()?; let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
let result = inner_leg_angle_x(hypotenuse, leg); let result = inner_leg_angle_x(hypotenuse, leg);
Ok(KclValue::from_number_with_type(result, ty, vec![args.into()])) Ok(KclValue::from_number_with_type(
result,
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
vec![args.into()],
))
} }
/// Compute the angle of the given leg for x. /// Compute the angle of the given leg for x.
/// ///
/// ```no_run /// ```no_run
/// legAngX(5, 3) /// legAngX(hypotenuse = 5, leg = 3)
/// ``` /// ```
#[stdlib { #[stdlib {
name = "legAngX", name = "legAngX",
keywords = true,
unlabeled_first = false,
args = {
hypotenuse = { docs = "The length of the triangle's hypotenuse" },
leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" },
},
tags = ["utilities"], tags = ["utilities"],
}] }]
fn inner_leg_angle_x(hypotenuse: f64, leg: f64) -> f64 { fn inner_leg_angle_x(hypotenuse: f64, leg: f64) -> f64 {
@ -324,19 +351,31 @@ fn inner_leg_angle_x(hypotenuse: f64, leg: f64) -> f64 {
} }
/// Compute the angle of the given leg for y. /// Compute the angle of the given leg for y.
pub async fn leg_angle_y(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn leg_angle_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (hypotenuse, leg, ty) = args.get_hypotenuse_leg()?; let hypotenuse: TyF64 = args.get_kw_arg_typed("hypotenuse", &RuntimeType::length(), exec_state)?;
let leg: TyF64 = args.get_kw_arg_typed("leg", &RuntimeType::length(), exec_state)?;
let (hypotenuse, leg, _ty) = NumericType::combine_eq_coerce(hypotenuse, leg);
let result = inner_leg_angle_y(hypotenuse, leg); let result = inner_leg_angle_y(hypotenuse, leg);
Ok(KclValue::from_number_with_type(result, ty, vec![args.into()])) Ok(KclValue::from_number_with_type(
result,
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
vec![args.into()],
))
} }
/// Compute the angle of the given leg for y. /// Compute the angle of the given leg for y.
/// ///
/// ```no_run /// ```no_run
/// legAngY(5, 3) /// legAngY(hypotenuse = 5, leg = 3)
/// ``` /// ```
#[stdlib { #[stdlib {
name = "legAngY", name = "legAngY",
keywords = true,
unlabeled_first = false,
args = {
hypotenuse = { docs = "The length of the triangle's hypotenuse" },
leg = { docs = "The length of one of the triangle's legs (i.e. non-hypotenuse side)" },
},
tags = ["utilities"], tags = ["utilities"],
}] }]
fn inner_leg_angle_y(hypotenuse: f64, leg: f64) -> f64 { fn inner_leg_angle_y(hypotenuse: f64, leg: f64) -> f64 {

View File

@ -1,6 +1,5 @@
//! Standard library plane helpers. //! Standard library plane helpers.
use kcl_derive_docs::stdlib;
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Color, ModelingCmd}; use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Color, ModelingCmd};
use kittycad_modeling_cmds as kcmc; use kittycad_modeling_cmds as kcmc;
@ -19,98 +18,6 @@ pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclV
Ok(KclValue::Plane { value: Box::new(plane) }) Ok(KclValue::Plane { value: Box::new(plane) })
} }
/// Offset a plane by a distance along its normal.
///
/// For example, if you offset the 'XZ' plane by 10, the new plane will be parallel to the 'XZ'
/// plane and 10 units away from it.
///
/// ```no_run
/// // Loft a square and a circle on the `XY` plane using offset.
/// squareSketch = startSketchOn('XY')
/// |> startProfileAt([-100, 200], %)
/// |> line(end = [200, 0])
/// |> line(end = [0, -200])
/// |> line(end = [-200, 0])
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = startSketchOn(offsetPlane('XY', offset = 150))
/// |> circle( center = [0, 100], radius = 50 )
///
/// loft([squareSketch, circleSketch])
/// ```
///
/// ```no_run
/// // Loft a square and a circle on the `XZ` plane using offset.
/// squareSketch = startSketchOn('XZ')
/// |> startProfileAt([-100, 200], %)
/// |> line(end = [200, 0])
/// |> line(end = [0, -200])
/// |> line(end = [-200, 0])
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = startSketchOn(offsetPlane('XZ', offset = 150))
/// |> circle( center = [0, 100], radius = 50 )
///
/// loft([squareSketch, circleSketch])
/// ```
///
/// ```no_run
/// // Loft a square and a circle on the `YZ` plane using offset.
/// squareSketch = startSketchOn('YZ')
/// |> startProfileAt([-100, 200], %)
/// |> line(end = [200, 0])
/// |> line(end = [0, -200])
/// |> line(end = [-200, 0])
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = startSketchOn(offsetPlane('YZ', offset = 150))
/// |> circle( center = [0, 100], radius = 50 )
///
/// loft([squareSketch, circleSketch])
/// ```
///
/// ```no_run
/// // Loft a square and a circle on the `-XZ` plane using offset.
/// squareSketch = startSketchOn('-XZ')
/// |> startProfileAt([-100, 200], %)
/// |> line(end = [200, 0])
/// |> line(end = [0, -200])
/// |> line(end = [-200, 0])
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = startSketchOn(offsetPlane('-XZ', offset = -150))
/// |> circle( center = [0, 100], radius = 50 )
///
/// loft([squareSketch, circleSketch])
/// ```
/// ```no_run
/// // A circle on the XY plane
/// startSketchOn("XY")
/// |> startProfileAt([0, 0], %)
/// |> circle( radius = 10, center = [0, 0] )
///
/// // Triangle on the plane 4 units above
/// startSketchOn(offsetPlane("XY", offset = 4))
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
/// |> close()
/// ```
#[stdlib {
name = "offsetPlane",
feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
args = {
plane = { docs = "The plane (e.g. XY) which this new plane is created from." },
offset = { docs = "Distance from the standard plane this new plane will be created at." },
}
}]
async fn inner_offset_plane( async fn inner_offset_plane(
plane: PlaneData, plane: PlaneData,
offset: TyF64, offset: TyF64,
@ -122,7 +29,8 @@ async fn inner_offset_plane(
// standard planes themselves. // standard planes themselves.
plane.value = PlaneType::Custom; plane.value = PlaneType::Custom;
plane.origin += plane.z_axis * offset.to_length_units(plane.origin.units); let normal = plane.x_axis.cross(&plane.y_axis);
plane.origin += normal * offset.to_length_units(plane.origin.units);
make_offset_plane_in_engine(&plane, exec_state, args).await?; make_offset_plane_in_engine(&plane, exec_state, args).await?;
Ok(plane) Ok(plane)

View File

@ -4,6 +4,7 @@ use anyhow::Result;
use kcl_derive_docs::stdlib; use kcl_derive_docs::stdlib;
use kittycad_modeling_cmds::shared::Angle; use kittycad_modeling_cmds::shared::Angle;
use super::utils::untype_point;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{
@ -13,8 +14,6 @@ use crate::{
std::{args::TyF64, utils::between, Args}, std::{args::TyF64, utils::between, Args},
}; };
use super::utils::untype_point;
/// Returns the point at the end of the given segment. /// Returns the point at the end of the given segment.
pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?; let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
@ -580,130 +579,3 @@ async fn inner_tangent_to_end(tag: &TagIdentifier, exec_state: &mut ExecState, a
Ok(previous_end_tangent.to_degrees()) Ok(previous_end_tangent.to_degrees())
} }
/// Returns the angle to match the given length for x.
pub async fn angle_to_match_length_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (tag, to, sketch) = args.get_tag_to_number_sketch()?;
let result = inner_angle_to_match_length_x(&tag, to, sketch, exec_state, args.clone())?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
}
/// Returns the angle to match the given length for x.
///
/// ```no_run
/// sketch001 = startSketchOn('XZ')
/// |> startProfileAt([0, 0], %)
/// |> line(end = [2, 5], tag = $seg01)
/// |> angledLine(
/// angle = -angleToMatchLengthX(seg01, 7, %),
/// endAbsoluteX = 10,
/// )
/// |> close()
///
/// extrusion = extrude(sketch001, length = 5)
/// ```
#[stdlib {
name = "angleToMatchLengthX",
}]
fn inner_angle_to_match_length_x(
tag: &TagIdentifier,
to: TyF64,
sketch: Sketch,
exec_state: &mut ExecState,
args: Args,
) -> Result<f64, KclError> {
let line = args.get_tag_engine_info(exec_state, tag)?;
let path = line.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a line segment with a path, found `{:?}`", line),
source_ranges: vec![args.source_range],
})
})?;
let length = path.length().n;
let last_line = sketch
.paths
.last()
.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
source_ranges: vec![args.source_range],
})
})?
.get_base();
let diff = (to.to_length_units(sketch.units) - last_line.to[0]).abs();
let angle_r = (diff / length).acos();
if diff > length {
Ok(0.0)
} else {
Ok(angle_r.to_degrees())
}
}
/// Returns the angle to match the given length for y.
pub async fn angle_to_match_length_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (tag, to, sketch) = args.get_tag_to_number_sketch()?;
let result = inner_angle_to_match_length_y(&tag, to, sketch, exec_state, args.clone())?;
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
}
/// Returns the angle to match the given length for y.
///
/// ```no_run
/// sketch001 = startSketchOn('XZ')
/// |> startProfileAt([0, 0], %)
/// |> line(end = [1, 2], tag = $seg01)
/// |> angledLine(
/// angle = angleToMatchLengthY(seg01, 15, %),
/// length = 5,
/// )
/// |> yLine(endAbsolute = 0)
/// |> close()
///
/// extrusion = extrude(sketch001, length = 5)
/// ```
#[stdlib {
name = "angleToMatchLengthY",
}]
fn inner_angle_to_match_length_y(
tag: &TagIdentifier,
to: TyF64,
sketch: Sketch,
exec_state: &mut ExecState,
args: Args,
) -> Result<f64, KclError> {
let line = args.get_tag_engine_info(exec_state, tag)?;
let path = line.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a line segment with a path, found `{:?}`", line),
source_ranges: vec![args.source_range],
})
})?;
let length = path.length().n;
let last_line = sketch
.paths
.last()
.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
source_ranges: vec![args.source_range],
})
})?
.get_base();
let diff = (to.to_length_units(sketch.units) - last_line.to[1]).abs();
let angle_r = (diff / length).asin();
if diff > length {
Ok(0.0)
} else {
Ok(angle_r.to_degrees())
}
}

View File

@ -247,9 +247,10 @@ async fn inner_shell(
/// Make the inside of a 3D object hollow. /// Make the inside of a 3D object hollow.
pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (thickness, solid) = args.get_length_and_solid(exec_state)?; let solid = args.get_unlabeled_kw_arg_typed("solid", &RuntimeType::solid(), exec_state)?;
let thickness: TyF64 = args.get_kw_arg_typed("thickness", &RuntimeType::length(), exec_state)?;
let value = inner_hollow(thickness, solid, exec_state, args).await?; let value = inner_hollow(solid, thickness, exec_state, args).await?;
Ok(KclValue::Solid { value }) Ok(KclValue::Solid { value })
} }
@ -267,7 +268,7 @@ pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// |> line(end = [-24, 0]) /// |> line(end = [-24, 0])
/// |> close() /// |> close()
/// |> extrude(length = 6) /// |> extrude(length = 6)
/// |> hollow (0.25, %) /// |> hollow(thickness = 0.25)
/// ``` /// ```
/// ///
/// ```no_run /// ```no_run
@ -279,7 +280,7 @@ pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// |> line(end = [-24, 0]) /// |> line(end = [-24, 0])
/// |> close() /// |> close()
/// |> extrude(length = 6) /// |> extrude(length = 6)
/// |> hollow (0.5, %) /// |> hollow(thickness = 0.5)
/// ``` /// ```
/// ///
/// ```no_run /// ```no_run
@ -301,15 +302,21 @@ pub async fn hollow(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// |> circle( center = [size / 2, -size / 2], radius = 25 ) /// |> circle( center = [size / 2, -size / 2], radius = 25 )
/// |> extrude(length = 50) /// |> extrude(length = 50)
/// ///
/// hollow(0.5, case) /// hollow(case, thickness = 0.5)
/// ``` /// ```
#[stdlib { #[stdlib {
name = "hollow", name = "hollow",
feature_tree_operation = true, feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
args = {
solid = { docs = "Which solid to shell out" },
thickness = {docs = "The thickness of the shell" },
}
}] }]
async fn inner_hollow( async fn inner_hollow(
thickness: TyF64,
solid: Box<Solid>, solid: Box<Solid>,
thickness: TyF64,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Box<Solid>, KclError> { ) -> Result<Box<Solid>, KclError> {

View File

@ -960,9 +960,6 @@ pub enum PlaneData {
/// What should the planes Y axis be? /// What should the planes Y axis be?
#[serde(rename = "yAxis")] #[serde(rename = "yAxis")]
y_axis: Point3d, y_axis: Point3d,
/// The z-axis (normal).
#[serde(rename = "zAxis")]
z_axis: Point3d,
}, },
} }
@ -1229,7 +1226,6 @@ async fn start_sketch_on_face(
// TODO: get this from the extrude plane data. // TODO: get this from the extrude plane data.
x_axis: solid.sketch.on.x_axis(), x_axis: solid.sketch.on.x_axis(),
y_axis: solid.sketch.on.y_axis(), y_axis: solid.sketch.on.y_axis(),
z_axis: solid.sketch.on.z_axis(),
units: solid.units, units: solid.units,
solid, solid,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
@ -1247,49 +1243,18 @@ async fn make_sketch_plane_from_orientation(
let clobber = false; let clobber = false;
let size = LengthUnit(60.0); let size = LengthUnit(60.0);
let hide = Some(true); let hide = Some(true);
match data { args.batch_modeling_cmd(
PlaneData::XY | PlaneData::NegXY | PlaneData::XZ | PlaneData::NegXZ | PlaneData::YZ | PlaneData::NegYZ => { plane.id,
// TODO: ignoring the default planes here since we already created them, breaks the ModelingCmd::from(mcmd::MakePlane {
// front end for the feature tree which is stupid and we should fix it. clobber,
let x_axis = match data { origin: plane.origin.into(),
PlaneData::NegXY => Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm), size,
PlaneData::NegXZ => Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm), x_axis: plane.x_axis.into(),
PlaneData::NegYZ => Point3d::new(0.0, -1.0, 0.0, UnitLen::Mm), y_axis: plane.y_axis.into(),
_ => plane.x_axis, hide,
}; }),
args.batch_modeling_cmd( )
plane.id, .await?;
ModelingCmd::from(mcmd::MakePlane {
clobber,
origin: plane.origin.into(),
size,
x_axis: x_axis.into(),
y_axis: plane.y_axis.into(),
hide,
}),
)
.await?;
}
PlaneData::Plane {
origin,
x_axis,
y_axis,
z_axis: _,
} => {
args.batch_modeling_cmd(
plane.id,
ModelingCmd::from(mcmd::MakePlane {
clobber,
origin: origin.into(),
size,
x_axis: x_axis.into(),
y_axis: y_axis.into(),
hide,
}),
)
.await?;
}
}
Ok(Box::new(plane)) Ok(Box::new(plane))
} }
@ -1384,7 +1349,8 @@ pub(crate) async fn inner_start_profile_at(
adjust_camera: false, adjust_camera: false,
planar_normal: if let SketchSurface::Plane(plane) = &sketch_surface { planar_normal: if let SketchSurface::Plane(plane) = &sketch_surface {
// We pass in the normal for the plane here. // We pass in the normal for the plane here.
Some(plane.z_axis.into()) let normal = plane.x_axis.cross(&plane.y_axis);
Some(normal.into())
} else { } else {
None None
}, },

View File

@ -2219,8 +2219,8 @@ myAng2 = 134
part001 = startSketchOn(XY) part001 = startSketchOn(XY)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([1, 3.82], %, $seg01) // ln-should-get-tag |> line([1, 3.82], %, $seg01) // ln-should-get-tag
|> angledLine(angle = -angleToMatchLengthX(seg01, myVar, %), length = myVar) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper |> angledLine(angle = -foo(seg01, myVar, %), length = myVar) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|> angledLine(angle = -angleToMatchLengthY(seg01, myVar, %), length = myVar) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper"#; |> angledLine(angle = -bar(seg01, myVar, %), length = myVar) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper"#;
let program = crate::parsing::top_level_parse(some_program_string).unwrap(); let program = crate::parsing::top_level_parse(some_program_string).unwrap();
let recasted = program.recast(&Default::default(), 0); let recasted = program.recast(&Default::default(), 0);
@ -2237,8 +2237,8 @@ myAng2 = 134
part001 = startSketchOn(XY) part001 = startSketchOn(XY)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([1, 3.82], %, $seg01) // ln-should-get-tag |> line([1, 3.82], %, $seg01) // ln-should-get-tag
|> angledLine(angle = -angleToMatchLengthX(seg01, myVar, %), length = myVar) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper |> angledLine(angle = -foo(seg01, myVar, %), length = myVar) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|> angledLine(angle = -angleToMatchLengthY(seg01, myVar, %), length = myVar) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper |> angledLine(angle = -bar(seg01, myVar, %), length = myVar) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
"#; "#;
let program = crate::parsing::top_level_parse(some_program_string).unwrap(); let program = crate::parsing::top_level_parse(some_program_string).unwrap();

View File

@ -12,21 +12,18 @@ export XY = {
origin = { x = 0, y = 0, z = 0 }, origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 }, xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 1, z = 0 }, yAxis = { x = 0, y = 1, z = 0 },
zAxis = { x = 0, y = 0, z = 1 },
}: Plane }: Plane
export XZ = { export XZ = {
origin = { x = 0, y = 0, z = 0 }, origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 1, y = 0, z = 0 }, xAxis = { x = 1, y = 0, z = 0 },
yAxis = { x = 0, y = 0, z = 1 }, yAxis = { x = 0, y = 0, z = 1 },
zAxis = { x = 0, y = -1, z = 0 },
}: Plane }: Plane
export YZ = { export YZ = {
origin = { x = 0, y = 0, z = 0 }, origin = { x = 0, y = 0, z = 0 },
xAxis = { x = 0, y = 1, z = 0 }, xAxis = { x = 0, y = 1, z = 0 },
yAxis = { x = 0, y = 0, z = 1 }, yAxis = { x = 0, y = 0, z = 1 },
zAxis = { x = 1, y = 0, z = 0 },
}: Plane }: Plane
export X = { export X = {
@ -449,3 +446,93 @@ export fn toRadians(@num: number(rad)): number(rad) {
export fn toDegrees(@num: number(deg)): number(deg) { export fn toDegrees(@num: number(deg)): number(deg) {
return num return num
} }
/// Offset a plane by a distance along its normal.
///
/// For example, if you offset the `XZ` plane by 10, the new plane will be parallel to the `XZ`
/// plane and 10 units away from it.
///
/// ```
/// // Loft a square and a circle on the `XY` plane using offset.
/// squareSketch = startSketchOn(XY)
/// |> startProfileAt([-100, 200], %)
/// |> line(end = [200, 0])
/// |> line(end = [0, -200])
/// |> line(end = [-200, 0])
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = startSketchOn(offsetPlane(XY, offset = 150))
/// |> circle( center = [0, 100], radius = 50 )
///
/// loft([squareSketch, circleSketch])
/// ```
///
/// ```
/// // Loft a square and a circle on the `XZ` plane using offset.
/// squareSketch = startSketchOn(XZ)
/// |> startProfileAt([-100, 200], %)
/// |> line(end = [200, 0])
/// |> line(end = [0, -200])
/// |> line(end = [-200, 0])
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = startSketchOn(offsetPlane(XZ, offset = 150))
/// |> circle( center = [0, 100], radius = 50 )
///
/// loft([squareSketch, circleSketch])
/// ```
///
/// ```
/// // Loft a square and a circle on the `YZ` plane using offset.
/// squareSketch = startSketchOn(YZ)
/// |> startProfileAt([-100, 200], %)
/// |> line(end = [200, 0])
/// |> line(end = [0, -200])
/// |> line(end = [-200, 0])
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = startSketchOn(offsetPlane(YZ, offset = 150))
/// |> circle( center = [0, 100], radius = 50 )
///
/// loft([squareSketch, circleSketch])
/// ```
///
/// ```
/// // Loft a square and a circle on the `-XZ` plane using offset.
/// squareSketch = startSketchOn(-XZ)
/// |> startProfileAt([-100, 200], %)
/// |> line(end = [200, 0])
/// |> line(end = [0, -200])
/// |> line(end = [-200, 0])
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
/// |> close()
///
/// circleSketch = startSketchOn(offsetPlane(-XZ, offset = 150))
/// |> circle(center = [0, 100], radius = 50)
///
/// loft([squareSketch, circleSketch])
/// ```
///
/// ```
/// // A circle on the XY plane
/// startSketchOn(XY)
/// |> startProfileAt([0, 0], %)
/// |> circle( radius = 10, center = [0, 0] )
///
/// // Triangle on the plane 4 units above
/// startSketchOn(offsetPlane(XY, offset = 4))
/// |> startProfileAt([0, 0], %)
/// |> line(end = [10, 0])
/// |> line(end = [0, 10])
/// |> close()
/// ```
@(impl = std_rust)
export fn offsetPlane(
/// The plane (e.g. `XY`) which this new plane is created from.
@plane: Plane,
/// Distance from the standard plane this new plane will be created at.
offset: number(Length),
): Plane {}

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