Compare commits
11 Commits
v0.51.0
...
nrc-std-ax
Author | SHA1 | Date | |
---|---|---|---|
87dfda28a9 | |||
42f44e11f5 | |||
16ad7ff77a | |||
71b9e40bd9 | |||
4f35197a96 | |||
40b0cf5fd3 | |||
355e6acf0d | |||
4ff38e7f44 | |||
1dcd3b84b7 | |||
2957216bd3 | |||
11160f0b40 |
@ -1,5 +1,6 @@
|
||||
NODE_ENV=development
|
||||
DEV=true
|
||||
|
||||
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
|
||||
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
|
||||
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
|
||||
@ -8,3 +9,5 @@ VITE_KC_SKIP_AUTH=false
|
||||
VITE_KC_CONNECTION_TIMEOUT_MS=5000
|
||||
# ONLY add your token in .env.development.local if you want to skip auth, otherwise this token takes precedence!
|
||||
#VITE_KC_DEV_TOKEN="your token from dev.zoo.dev should go in .env.development.local"
|
||||
|
||||
FAIL_ON_CONSOLE_ERRORS=true
|
||||
|
10
.envrc
@ -1,3 +1,13 @@
|
||||
# Load optional shared environment variables
|
||||
source_up_if_exists
|
||||
|
||||
# Load default development environment variables
|
||||
dotenv .env.development
|
||||
|
||||
# Load optional environment variables overrides
|
||||
dotenv_if_exists .env.development.local
|
||||
|
||||
# Load optional testing environment variables
|
||||
dotenv_if_exists e2e/playwright/playwright-secrets.env
|
||||
|
||||
use flake .
|
||||
|
@ -20,6 +20,7 @@
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-empty-object-type": "error",
|
||||
"@typescript-eslint/no-floating-promises": "error",
|
||||
"@typescript-eslint/no-misused-promises": "error",
|
||||
"@typescript-eslint/no-unused-vars": ["error", {
|
||||
|
2
.github/workflows/update-e2e-branch.yml
vendored
@ -38,7 +38,7 @@ jobs:
|
||||
# Get a new SHA to prevent overwriting the commit status on main
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git commit --amend --message="[all-e2e] $(git log --max-count=1 --pretty=%B)"
|
||||
git commit --allow-empty --message="[all-e2e] $(git log --max-count=1 --pretty=%B)"
|
||||
|
||||
# Overwrite the branch
|
||||
git remote set-url origin https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/${{ github.repository }}.git
|
||||
|
@ -54,7 +54,7 @@ example = extrude(exampleSketch, length = 5)
|
||||
// Add color to a revolved solid.
|
||||
sketch001 = startSketchOn(XY)
|
||||
|> circle(center = [15, 0], radius = 5)
|
||||
|> revolve(angle = 360, axis = 'y')
|
||||
|> revolve(angle = 360, axis = Y)
|
||||
|> appearance(color = '#ff0000', metalness = 90, roughness = 90)
|
||||
```
|
||||
|
||||
|
@ -9,13 +9,12 @@ layout: manual
|
||||
|
||||
### `std`
|
||||
|
||||
- [`HALF_TURN`](/docs/kcl/consts/std-HALF_TURN)
|
||||
- [`QUARTER_TURN`](/docs/kcl/consts/std-QUARTER_TURN)
|
||||
- [`THREE_QUARTER_TURN`](/docs/kcl/consts/std-THREE_QUARTER_TURN)
|
||||
- [`X`](/docs/kcl/consts/std-X)
|
||||
- [`XY`](/docs/kcl/consts/std-XY)
|
||||
- [`XZ`](/docs/kcl/consts/std-XZ)
|
||||
- [`Y`](/docs/kcl/consts/std-Y)
|
||||
- [`YZ`](/docs/kcl/consts/std-YZ)
|
||||
- [`ZERO`](/docs/kcl/consts/std-ZERO)
|
||||
- [`Z`](/docs/kcl/consts/std-Z)
|
||||
|
||||
### `std::math`
|
||||
|
||||
@ -23,3 +22,10 @@ layout: manual
|
||||
- [`PI`](/docs/kcl/consts/std-math-PI)
|
||||
- [`TAU`](/docs/kcl/consts/std-math-TAU)
|
||||
|
||||
### `std::turns`
|
||||
|
||||
- [`HALF_TURN`](/docs/kcl/consts/std-turns-HALF_TURN)
|
||||
- [`QUARTER_TURN`](/docs/kcl/consts/std-turns-QUARTER_TURN)
|
||||
- [`THREE_QUARTER_TURN`](/docs/kcl/consts/std-turns-THREE_QUARTER_TURN)
|
||||
- [`ZERO`](/docs/kcl/consts/std-turns-ZERO)
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "std::HALF_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::HALF_TURN: number(deg) = 180deg
|
||||
```
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "std::QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::QUARTER_TURN: number(deg) = 90deg
|
||||
```
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "std::THREE_QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::THREE_QUARTER_TURN: number(deg) = 270deg
|
||||
```
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "std::ZERO"
|
||||
title: "std::X"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
@ -9,7 +9,7 @@ layout: manual
|
||||
|
||||
|
||||
```js
|
||||
std::ZERO: number = 0
|
||||
std::X
|
||||
```
|
||||
|
||||
|
15
docs/kcl/consts/std-Y.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::Y"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::Y
|
||||
```
|
||||
|
||||
|
15
docs/kcl/consts/std-Z.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::Z"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::Z
|
||||
```
|
||||
|
||||
|
15
docs/kcl/consts/std-turns-HALF_TURN.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::turns::HALF_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::turns::HALF_TURN: number(deg) = 180deg
|
||||
```
|
||||
|
||||
|
15
docs/kcl/consts/std-turns-QUARTER_TURN.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::turns::QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::turns::QUARTER_TURN: number(deg) = 90deg
|
||||
```
|
||||
|
||||
|
15
docs/kcl/consts/std-turns-THREE_QUARTER_TURN.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::turns::THREE_QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::turns::THREE_QUARTER_TURN: number(deg) = 270deg
|
||||
```
|
||||
|
||||
|
15
docs/kcl/consts/std-turns-ZERO.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::turns::ZERO"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::turns::ZERO: number = 0
|
||||
```
|
||||
|
||||
|
@ -22,20 +22,22 @@ layout: manual
|
||||
* [`string`](kcl/types/string)
|
||||
* [`tag`](kcl/types/tag)
|
||||
* **std**
|
||||
* [`Axis2d`](kcl/types/Axis2d)
|
||||
* [`Axis3d`](kcl/types/Axis3d)
|
||||
* [`Edge`](kcl/types/Edge)
|
||||
* [`Face`](kcl/types/Face)
|
||||
* [`HALF_TURN`](kcl/consts/std-HALF_TURN)
|
||||
* [`Helix`](kcl/types/Helix)
|
||||
* [`Plane`](kcl/types/Plane)
|
||||
* [`Point2d`](kcl/types/Point2d)
|
||||
* [`Point3d`](kcl/types/Point3d)
|
||||
* [`QUARTER_TURN`](kcl/consts/std-QUARTER_TURN)
|
||||
* [`Sketch`](kcl/types/Sketch)
|
||||
* [`Solid`](kcl/types/Solid)
|
||||
* [`THREE_QUARTER_TURN`](kcl/consts/std-THREE_QUARTER_TURN)
|
||||
* [`X`](kcl/consts/std-X)
|
||||
* [`XY`](kcl/consts/std-XY)
|
||||
* [`XZ`](kcl/consts/std-XZ)
|
||||
* [`Y`](kcl/consts/std-Y)
|
||||
* [`YZ`](kcl/consts/std-YZ)
|
||||
* [`ZERO`](kcl/consts/std-ZERO)
|
||||
* [`Z`](kcl/consts/std-Z)
|
||||
* [`abs`](kcl/abs)
|
||||
* [`acos`](kcl/acos)
|
||||
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
|
||||
@ -72,7 +74,7 @@ layout: manual
|
||||
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||
* [`helix`](kcl/helix)
|
||||
* [`helix`](kcl/std-helix)
|
||||
* [`hole`](kcl/hole)
|
||||
* [`hollow`](kcl/hollow)
|
||||
* [`inch`](kcl/inch)
|
||||
@ -91,7 +93,6 @@ layout: manual
|
||||
* [`map`](kcl/map)
|
||||
* [`max`](kcl/max)
|
||||
* [`min`](kcl/min)
|
||||
* [`mirror2d`](kcl/mirror2d)
|
||||
* [`mm`](kcl/mm)
|
||||
* [`offsetPlane`](kcl/offsetPlane)
|
||||
* [`patternCircular2d`](kcl/patternCircular2d)
|
||||
@ -110,7 +111,7 @@ layout: manual
|
||||
* [`push`](kcl/push)
|
||||
* [`reduce`](kcl/reduce)
|
||||
* [`rem`](kcl/rem)
|
||||
* [`revolve`](kcl/revolve)
|
||||
* [`revolve`](kcl/std-revolve)
|
||||
* [`rotate`](kcl/rotate)
|
||||
* [`round`](kcl/round)
|
||||
* [`scale`](kcl/scale)
|
||||
@ -146,3 +147,9 @@ layout: manual
|
||||
* [`tan`](kcl/std-math-tan)
|
||||
* **std::sketch**
|
||||
* [`circle`](kcl/std-sketch-circle)
|
||||
* [`mirror2d`](kcl/std-sketch-mirror2d)
|
||||
* **std::turns**
|
||||
* [`turns::HALF_TURN`](kcl/consts/std-turns-HALF_TURN)
|
||||
* [`turns::QUARTER_TURN`](kcl/consts/std-turns-QUARTER_TURN)
|
||||
* [`turns::THREE_QUARTER_TURN`](kcl/consts/std-turns-THREE_QUARTER_TURN)
|
||||
* [`turns::ZERO`](kcl/consts/std-turns-ZERO)
|
||||
|
@ -146,7 +146,7 @@ exampleSketch = startSketchOn(XY)
|
||||
|> line(end = [-2, 0])
|
||||
|> close()
|
||||
|
||||
example = revolve(exampleSketch, axis = 'y', angle = 180)
|
||||
example = revolve(exampleSketch, axis = Y, angle = 180)
|
||||
|
||||
exampleSketch002 = startSketchOn(example, 'end')
|
||||
|> startProfileAt([4.5, -5], %)
|
||||
@ -177,7 +177,7 @@ exampleSketch = startSketchOn(XY)
|
||||
|
||||
example = revolve(
|
||||
exampleSketch,
|
||||
axis = 'y',
|
||||
axis = Y,
|
||||
angle = 180,
|
||||
tagEnd = $end01,
|
||||
)
|
||||
|
116
docs/kcl/std-helix.md
Normal file
246
docs/kcl/std-revolve.md
Normal file
98
docs/kcl/std-sketch-mirror2d.md
Normal file
17412
docs/kcl/std.json
@ -74,7 +74,7 @@ helixPath = helix(
|
||||
revolutions = 4,
|
||||
length = 10,
|
||||
radius = 5,
|
||||
axis = 'Z',
|
||||
axis = Z,
|
||||
)
|
||||
|
||||
// Create a spring by sweeping around the helix path.
|
||||
|
12
docs/kcl/types/Axis2d.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "std::Axis2d"
|
||||
excerpt: "An infinte line in 2d space."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
An infinte line in 2d space.
|
||||
|
||||
|
||||
|
||||
|
||||
|
12
docs/kcl/types/Axis3d.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "std::Axis3d"
|
||||
excerpt: "An infinte line in 3d space."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
An infinte line in 3d space.
|
||||
|
||||
|
||||
|
||||
|
||||
|
12
docs/kcl/types/Edge.md
Normal file
@ -0,0 +1,12 @@
|
||||
---
|
||||
title: "std::Edge"
|
||||
excerpt: "The edge of a solid."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
The edge of a solid.
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -21,7 +21,7 @@ sketch001 = startSketchOn(XZ)
|
||||
|> angledLine([-45, length001], %)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
revolve001 = revolve(sketch001, axis = "X")
|
||||
revolve001 = revolve(sketch001, axis = X)
|
||||
triangle()
|
||||
|> extrude(length = 30)
|
||||
plane001 = offsetPlane(XY, offset = 10)
|
||||
@ -126,7 +126,7 @@ test.describe('Feature Tree pane', () => {
|
||||
await testViewSource({
|
||||
operationName: 'Revolve',
|
||||
operationIndex: 0,
|
||||
expectedActiveLine: 'revolve001 = revolve(sketch001, axis = "X")',
|
||||
expectedActiveLine: 'revolve001 = revolve(sketch001, axis = X)',
|
||||
})
|
||||
await testViewSource({
|
||||
operationName: 'Triangle',
|
||||
|
@ -257,6 +257,46 @@ export const isErrorWhitelisted = (exception: Error) => {
|
||||
project: 'Google Chrome',
|
||||
foundInSpec: 'e2e/playwright/testing-settings.spec.ts',
|
||||
},
|
||||
// TODO: fix this error in the code
|
||||
{
|
||||
name: 'TypeError',
|
||||
message: "Cannot read properties of undefined (reading 'length')",
|
||||
stack: '',
|
||||
project: 'Google Chrome',
|
||||
foundInSpec: '', // many tests are impacted by this error
|
||||
},
|
||||
// TODO: fix this error in the code
|
||||
{
|
||||
name: 'ReferenceError',
|
||||
message: '_testUtils is not defined',
|
||||
stack: '',
|
||||
project: 'Google Chrome',
|
||||
foundInSpec: 'e2e/playwright/snapshot-tests.spec.ts',
|
||||
},
|
||||
// TODO: fix this error in the code
|
||||
{
|
||||
name: 'TypeError',
|
||||
message: 'Failed to fetch',
|
||||
stack: '',
|
||||
project: 'Google Chrome',
|
||||
foundInSpec: 'e2e/playwright/snapshot-tests.spec.ts',
|
||||
},
|
||||
// TODO: fix this error in the code
|
||||
{
|
||||
name: 'ReferenceError',
|
||||
message: 'originalCode is not defined',
|
||||
stack: '',
|
||||
project: 'Google Chrome',
|
||||
foundInSpec: 'e2e/playwright/onboarding-tests.spec.ts',
|
||||
},
|
||||
// TODO: fix this error in the code
|
||||
{
|
||||
name: 'ReferenceError',
|
||||
message: 'createNewVariableCheckbox is not defined',
|
||||
stack: '',
|
||||
project: 'Google Chrome',
|
||||
foundInSpec: 'e2e/playwright/testing-constraints.spec.ts',
|
||||
},
|
||||
]
|
||||
|
||||
const cleanString = (str: string) => str.replace(/[`"]/g, '')
|
||||
|
@ -1082,8 +1082,8 @@ openSketch = startSketchOn(XY)
|
||||
}) => {
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 620, y: 257 }
|
||||
const expectedOutput = `helix001 = helix( revolutions = 1, angleStart = 360, ccw = false, radius = 5, axis = 'X', length = 5,)`
|
||||
const expectedLine = `revolutions=1,`
|
||||
const expectedOutput = `helix001 = helix( axis = 'X', radius = 5, length = 5, revolutions = 1, angleStart = 360, ccw = false,)`
|
||||
const expectedLine = `axis='X',`
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
@ -1091,17 +1091,17 @@ openSketch = startSketchOn(XY)
|
||||
await toolbar.helixButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'axisOrEdge',
|
||||
currentArgKey: 'mode',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Mode: '',
|
||||
AngleStart: '',
|
||||
AxisOrEdge: '',
|
||||
CounterClockWise: '',
|
||||
Revolutions: '',
|
||||
Length: '',
|
||||
Radius: '',
|
||||
Revolutions: '',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'axisOrEdge',
|
||||
highlightedHeaderArg: 'mode',
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
@ -1110,7 +1110,19 @@ openSketch = startSketchOn(XY)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Mode: 'Axis',
|
||||
Axis: 'X',
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
Length: '5',
|
||||
Radius: '5',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
@ -1134,30 +1146,31 @@ openSketch = startSketchOn(XY)
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Helix',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: initialInput,
|
||||
currentArgKey: 'CounterClockWise',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
AngleStart: '360',
|
||||
Axis: 'X',
|
||||
CounterClockWise: '',
|
||||
Length: initialInput,
|
||||
Radius: '5',
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
Radius: '5',
|
||||
Length: initialInput,
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'length',
|
||||
highlightedHeaderArg: 'CounterClockWise',
|
||||
})
|
||||
await page.keyboard.press('Shift+Backspace')
|
||||
await expect(cmdBar.currentArgumentInput).toBeVisible()
|
||||
await cmdBar.currentArgumentInput.locator('.cm-content').fill(newInput)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
AngleStart: '360',
|
||||
Axis: 'X',
|
||||
CounterClockWise: '',
|
||||
Length: newInput,
|
||||
Radius: '5',
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
Radius: '5',
|
||||
Length: newInput,
|
||||
CounterClockWise: '',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
@ -1181,14 +1194,14 @@ openSketch = startSketchOn(XY)
|
||||
{
|
||||
selectionType: 'segment',
|
||||
testPoint: { x: 513, y: 221 },
|
||||
expectedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = seg01, length = 100,)`,
|
||||
expectedEditedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = seg01, length = 50,)`,
|
||||
expectedOutput: `helix001 = helix( axis = seg01, radius = 1, length = 100, revolutions = 20, angleStart = 0, ccw = false,)`,
|
||||
expectedEditedOutput: `helix001 = helix( axis = seg01, radius = 1, length = 50, revolutions = 20, angleStart = 0, ccw = false,)`,
|
||||
},
|
||||
{
|
||||
selectionType: 'sweepEdge',
|
||||
testPoint: { x: 564, y: 364 },
|
||||
expectedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = getOppositeEdge(seg01), length = 100,)`,
|
||||
expectedEditedOutput: `helix001 = helix( revolutions = 20, angleStart = 0, ccw = true, radius = 1, axis = getOppositeEdge(seg01), length = 50,)`,
|
||||
expectedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 1, length = 100, revolutions = 20, angleStart = 0, ccw = false,)`,
|
||||
expectedEditedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 1, length = 50, revolutions = 20, angleStart = 0, ccw = false,)`,
|
||||
},
|
||||
]
|
||||
helixCases.map(
|
||||
@ -1225,17 +1238,17 @@ openSketch = startSketchOn(XY)
|
||||
await toolbar.helixButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'axisOrEdge',
|
||||
currentArgKey: 'mode',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
AngleStart: '',
|
||||
AxisOrEdge: '',
|
||||
Mode: '',
|
||||
CounterClockWise: '',
|
||||
Length: '',
|
||||
Radius: '',
|
||||
Revolutions: '',
|
||||
},
|
||||
highlightedHeaderArg: 'axisOrEdge',
|
||||
highlightedHeaderArg: 'mode',
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.selectOption({ name: 'Edge' }).click()
|
||||
@ -1246,7 +1259,6 @@ openSketch = startSketchOn(XY)
|
||||
await cmdBar.progressCmdBar()
|
||||
await page.keyboard.insertText('0')
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.selectOption({ name: 'True' }).click()
|
||||
await page.keyboard.insertText('1')
|
||||
await cmdBar.progressCmdBar()
|
||||
await page.keyboard.insertText('100')
|
||||
@ -1254,13 +1266,13 @@ openSketch = startSketchOn(XY)
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
AngleStart: '0',
|
||||
AxisOrEdge: 'Edge',
|
||||
Mode: 'Edge',
|
||||
Edge: `1 ${selectionType}`,
|
||||
CounterClockWise: '',
|
||||
Length: '100',
|
||||
Radius: '1',
|
||||
AngleStart: '0',
|
||||
Revolutions: '20',
|
||||
Radius: '1',
|
||||
Length: '100',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
@ -1285,17 +1297,18 @@ openSketch = startSketchOn(XY)
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Helix',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: initialInput,
|
||||
currentArgKey: 'CounterClockWise',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
AngleStart: '0',
|
||||
CounterClockWise: '',
|
||||
Length: initialInput,
|
||||
Radius: '1',
|
||||
Revolutions: '20',
|
||||
Radius: '1',
|
||||
Length: initialInput,
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'length',
|
||||
highlightedHeaderArg: 'CounterClockWise',
|
||||
})
|
||||
await page.keyboard.press('Shift+Backspace')
|
||||
await expect(cmdBar.currentArgumentInput).toBeVisible()
|
||||
await cmdBar.currentArgumentInput
|
||||
.locator('.cm-content')
|
||||
@ -1305,10 +1318,10 @@ openSketch = startSketchOn(XY)
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
AngleStart: '0',
|
||||
CounterClockWise: '',
|
||||
Length: newInput,
|
||||
Radius: '1',
|
||||
Revolutions: '20',
|
||||
Radius: '1',
|
||||
Length: newInput,
|
||||
CounterClockWise: '',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
@ -1336,6 +1349,141 @@ openSketch = startSketchOn(XY)
|
||||
}
|
||||
)
|
||||
|
||||
test('Helix point-and-click on cylinder', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn(XY)
|
||||
profile001 = circle(
|
||||
sketch001,
|
||||
center = [0, 0],
|
||||
radius = 100,
|
||||
tag = $seg01,
|
||||
)
|
||||
extrude001 = extrude(profile001, length = 100)
|
||||
`
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 620, y: 257 }
|
||||
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const expectedOutput = `helix001 = helix( cylinder = extrude001, revolutions = 1, angleStart = 360, ccw = false,)`
|
||||
const expectedLine = `cylinder = extrude001,`
|
||||
const expectedEditedOutput = `helix001 = helix( cylinder = extrude001, revolutions = 1, angleStart = 360, ccw = true,)`
|
||||
|
||||
await test.step(`Go through the command bar flow`, async () => {
|
||||
await toolbar.helixButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'mode',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Mode: '',
|
||||
AngleStart: '',
|
||||
Revolutions: '',
|
||||
Length: '',
|
||||
Radius: '',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'mode',
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.selectOption({ name: 'Cylinder' }).click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'cylinder',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Mode: 'Cylinder',
|
||||
Cylinder: '',
|
||||
AngleStart: '',
|
||||
Revolutions: '',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'cylinder',
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await clickOnWall()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Mode: 'Cylinder',
|
||||
Cylinder: '1 face',
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
await editor.expectEditor.toContain(expectedOutput)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [expectedLine],
|
||||
highlightedCode: '',
|
||||
})
|
||||
})
|
||||
|
||||
await test.step(`Edit helix through the feature tree`, async () => {
|
||||
await editor.closePane()
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
|
||||
await operationButton.dblclick()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Helix',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'CounterClockWise',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
CounterClockWise: '',
|
||||
},
|
||||
highlightedHeaderArg: 'CounterClockWise',
|
||||
})
|
||||
await cmdBar.selectOption({ name: 'True' }).click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
AngleStart: '360',
|
||||
Revolutions: '1',
|
||||
CounterClockWise: 'true',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await toolbar.closePane('feature-tree')
|
||||
await toolbar.openPane('code')
|
||||
await editor.expectEditor.toContain(expectedEditedOutput)
|
||||
await editor.closePane()
|
||||
})
|
||||
|
||||
await test.step('Delete helix via feature tree selection', async () => {
|
||||
await toolbar.openPane('feature-tree')
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Delete')
|
||||
await toolbar.closePane('feature-tree')
|
||||
await toolbar.openPane('code')
|
||||
await editor.expectEditor.not.toContain(expectedEditedOutput)
|
||||
})
|
||||
})
|
||||
|
||||
const loftPointAndClickCases = [
|
||||
{ shouldPreselect: true },
|
||||
{ shouldPreselect: false },
|
||||
@ -3321,7 +3469,7 @@ segAng(rectangleSegmentA002),
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
|
||||
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = 'X')`
|
||||
const newCodeToFind = `revolve001 = revolve(sketch002, angle = 360, axis = X)`
|
||||
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
|
||||
})
|
||||
test('revolve surface around edge from an extruded solid2d', async ({
|
||||
|
@ -473,6 +473,9 @@ test.describe('Can export from electron app', () => {
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
}
|
||||
if (runningOnWindows()) {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
}
|
||||
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
|
@ -778,6 +778,19 @@ plane002 = offsetPlane(XZ, offset = -2 * x)`
|
||||
await editor.expectEditor.not.toContain(`plane002`)
|
||||
})
|
||||
})
|
||||
|
||||
test.fail(
|
||||
'Console errors cause tests to fail',
|
||||
async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await homePage.goToModelingScene()
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
await page.getByTestId('custom-cmd-input').fill('foobar')
|
||||
await page.getByTestId('custom-cmd-send-button').scrollIntoViewIfNeeded()
|
||||
await page.getByTestId('custom-cmd-send-button').click()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
async function clickExportButton(page: Page) {
|
||||
|
@ -674,7 +674,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
|> line(end = [12.73, -0.09])
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> close()
|
||||
|> revolve(axis = "X")`
|
||||
|> revolve(axis = X)`
|
||||
)
|
||||
})
|
||||
|
||||
@ -761,7 +761,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
|> tangentialArcTo([24.95, -5.38], %)
|
||||
|> line(end = [1.97, 2.06])
|
||||
|> close()
|
||||
|> revolve(axis = "X")`)
|
||||
|> revolve(axis = X)`)
|
||||
})
|
||||
test('Can add multiple sketches', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
@ -1209,7 +1209,7 @@ profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(
|
||||
|> xLine(endAbsolute = 0 + .001)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> close()
|
||||
|> revolve(axis = "Y")
|
||||
|> revolve(axis = Y)
|
||||
|
||||
return lugSketch
|
||||
}
|
||||
|
@ -76,11 +76,11 @@ part001 = startSketchOn(-XZ)
|
||||
|> xLine(endAbsolute = totalLen, tag = $seg03)
|
||||
|> yLine(length = -armThick, tag = $seg01)
|
||||
|> angledLineThatIntersects({
|
||||
angle = HALF_TURN,
|
||||
angle = turns::HALF_TURN,
|
||||
offset = -armThick,
|
||||
intersectTag = seg04
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg04, %) + 180, ZERO], %)
|
||||
|> angledLineToY([segAng(seg04, %) + 180, turns::ZERO], %)
|
||||
|> angledLineToY({
|
||||
angle = -bottomAng,
|
||||
to = -totalHeightHalf - armThick,
|
||||
@ -88,12 +88,12 @@ part001 = startSketchOn(-XZ)
|
||||
|> xLine(length = endAbsolute = segEndX(seg03) + 0)
|
||||
|> yLine(length = -segLen(seg01, %))
|
||||
|> angledLineThatIntersects({
|
||||
angle = HALF_TURN,
|
||||
angle = turns::HALF_TURN,
|
||||
offset = -armThick,
|
||||
intersectTag = seg02
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg02, %) + 180, -baseHeight], %)
|
||||
|> xLine(endAbsolute = ZERO)
|
||||
|> xLine(endAbsolute = turns::ZERO)
|
||||
|> close()
|
||||
|> extrude(length = 4)`
|
||||
)
|
||||
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
@ -935,47 +935,39 @@ export async function setup(
|
||||
}
|
||||
|
||||
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
||||
// enabled for chrome for now
|
||||
if (page.context().browser()?.browserType().name() === 'chromium') {
|
||||
// No idea wtf exception is
|
||||
page.on('pageerror', (exception: any) => {
|
||||
if (isErrorWhitelisted(exception)) {
|
||||
return
|
||||
}
|
||||
page.on('pageerror', (exception: any) => {
|
||||
if (isErrorWhitelisted(exception)) {
|
||||
return
|
||||
}
|
||||
// Only disable this environment variable if you want to collect console errors
|
||||
if (process.env.FAIL_ON_CONSOLE_ERRORS !== 'false') {
|
||||
// Use expect to prevent page from closing and not cleaning up
|
||||
expect(`An error was detected in the console: \r\n message:${exception.message} \r\n name:${exception.name} \r\n stack:${exception.stack}
|
||||
|
||||
// only set this env var to false if you want to collect console errors
|
||||
// This can be configured in the GH workflow. This should be set to true by default (we want tests to fail when
|
||||
// unwhitelisted console errors are detected).
|
||||
if (process.env.FAIL_ON_CONSOLE_ERRORS === 'true') {
|
||||
// Fail when running on CI and FAIL_ON_CONSOLE_ERRORS is set
|
||||
// use expect to prevent page from closing and not cleaning up
|
||||
expect(`An error was detected in the console: \r\n message:${exception.message} \r\n name:${exception.name} \r\n stack:${exception.stack}
|
||||
|
||||
*Either fix the console error or add it to the whitelist defined in ./lib/console-error-whitelist.ts (if the error can be safely ignored)
|
||||
`).toEqual('Console error detected')
|
||||
} else {
|
||||
// the (test-results/exceptions.txt) file will be uploaded as part of an upload artifact in GH
|
||||
fsp
|
||||
.appendFile(
|
||||
'./test-results/exceptions.txt',
|
||||
[
|
||||
'~~~',
|
||||
`triggered_by_test:${
|
||||
testInfo?.file + ' ' + (testInfo?.title || ' ')
|
||||
}`,
|
||||
`name:${exception.name}`,
|
||||
`message:${exception.message}`,
|
||||
`stack:${exception.stack}`,
|
||||
`project:${testInfo?.project.name}`,
|
||||
'~~~',
|
||||
].join('\n')
|
||||
)
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
*Either fix the console error or add it to the whitelist defined in ./lib/console-error-whitelist.ts (if the error can be safely ignored)
|
||||
`).toEqual('Console error detected')
|
||||
} else {
|
||||
// Add errors to `test-results/exceptions.txt` as a test artifact
|
||||
fsp
|
||||
.appendFile(
|
||||
'./test-results/exceptions.txt',
|
||||
[
|
||||
'~~~',
|
||||
`triggered_by_test:${
|
||||
testInfo?.file + ' ' + (testInfo?.title || ' ')
|
||||
}`,
|
||||
`name:${exception.name}`,
|
||||
`message:${exception.message}`,
|
||||
`stack:${exception.stack}`,
|
||||
`project:${testInfo?.project.name}`,
|
||||
'~~~',
|
||||
].join('\n')
|
||||
)
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
export async function isOutOfViewInScrollContainer(
|
||||
element: Locator,
|
||||
|
@ -486,13 +486,13 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|
||||
testName: 'Add variable, selecting axis',
|
||||
addVariable: true,
|
||||
axisSelect: true,
|
||||
value: 'QUARTER_TURN - angle001',
|
||||
value: 'turns::QUARTER_TURN - angle001',
|
||||
},
|
||||
{
|
||||
testName: 'No variable, selecting axis',
|
||||
addVariable: false,
|
||||
axisSelect: true,
|
||||
value: 'QUARTER_TURN - 7',
|
||||
value: 'turns::QUARTER_TURN - 7',
|
||||
},
|
||||
] as const
|
||||
for (const { testName, addVariable, value, axisSelect } of cases) {
|
||||
@ -935,12 +935,12 @@ part002 = startSketchOn(XZ)
|
||||
test.describe('Axis & segment - no modal constraints', () => {
|
||||
const cases = [
|
||||
{
|
||||
codeAfter: `|> line(endAbsolute = [154.9, ZERO])`,
|
||||
codeAfter: `|> line(endAbsolute = [154.9, turns::ZERO])`,
|
||||
axisClick: { x: 950, y: 250 },
|
||||
constraintName: 'Snap To X',
|
||||
},
|
||||
{
|
||||
codeAfter: `|> line(endAbsolute = [ZERO, 61.34])`,
|
||||
codeAfter: `|> line(endAbsolute = [turns::ZERO, 61.34])`,
|
||||
axisClick: { x: 600, y: 150 },
|
||||
constraintName: 'Snap To Y',
|
||||
},
|
||||
|
@ -319,7 +319,7 @@ part009 = startSketchOn(XY)
|
||||
|> line(end = [0, pipeLength])
|
||||
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|
||||
|> close()
|
||||
rev = revolve(part009, axis = 'y')
|
||||
rev = revolve(part009, axis = Y)
|
||||
sketch006 = startSketchOn(XY)
|
||||
profile001 = circle(
|
||||
sketch006,
|
||||
@ -376,7 +376,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await expect(u.codeLocator).not.toContainText(
|
||||
`rev = revolve(part009, axis: 'y')`
|
||||
`rev = revolve(part009, axis: Y)`
|
||||
)
|
||||
|
||||
// FIXME (commented section below), this test would select a wall that had a sketch on it, and delete the underlying extrude
|
||||
|
@ -67,11 +67,11 @@ part001 = startSketchOn(-XZ)
|
||||
|> xLine(endAbsolute = totalLen, tag = $seg03)
|
||||
|> yLine(length = -armThick, tag = $seg01)
|
||||
|> angledLineThatIntersects({
|
||||
angle = HALF_TURN,
|
||||
angle = turns::HALF_TURN,
|
||||
offset = -armThick,
|
||||
intersectTag = seg04
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg04) + 180, ZERO], %)
|
||||
|> angledLineToY([segAng(seg04) + 180, turns::ZERO], %)
|
||||
|> angledLineToY({
|
||||
angle = -bottomAng,
|
||||
to = -totalHeightHalf - armThick,
|
||||
@ -79,12 +79,12 @@ part001 = startSketchOn(-XZ)
|
||||
|> xLine(endAbsolute = segEndX(seg03) + 0)
|
||||
|> yLine(length = -segLen(seg01))
|
||||
|> angledLineThatIntersects({
|
||||
angle = HALF_TURN,
|
||||
angle = turns::HALF_TURN,
|
||||
offset = -armThick,
|
||||
intersectTag = seg02
|
||||
}, %)
|
||||
|> angledLineToY([segAng(seg02) + 180, -baseHeight], %)
|
||||
|> xLine(endAbsolute = ZERO)
|
||||
|> xLine(endAbsolute = turns::ZERO)
|
||||
|> close()
|
||||
|> extrude(length = 4)`
|
||||
)
|
||||
|
@ -41,6 +41,8 @@ When you submit a PR to add or modify KCL samples, images and STEP files will be
|
||||
[](cycloidal-gear/main.kcl)
|
||||
#### [dodecahedron](dodecahedron/main.kcl) ([screenshot](screenshots/dodecahedron.png))
|
||||
[](dodecahedron/main.kcl)
|
||||
#### [dual-basin-utility-sink](dual-basin-utility-sink/main.kcl) ([screenshot](screenshots/dual-basin-utility-sink.png))
|
||||
[](dual-basin-utility-sink/main.kcl)
|
||||
#### [enclosure](enclosure/main.kcl) ([screenshot](screenshots/enclosure.png))
|
||||
[](enclosure/main.kcl)
|
||||
#### [exhaust-manifold](exhaust-manifold/main.kcl) ([screenshot](screenshots/exhaust-manifold.png))
|
||||
@ -75,6 +77,8 @@ When you submit a PR to add or modify KCL samples, images and STEP files will be
|
||||
[](kitt/main.kcl)
|
||||
#### [lego](lego/main.kcl) ([screenshot](screenshots/lego.png))
|
||||
[](lego/main.kcl)
|
||||
#### [makeup-mirror](makeup-mirror/main.kcl) ([screenshot](screenshots/makeup-mirror.png))
|
||||
[](makeup-mirror/main.kcl)
|
||||
#### [mounting-plate](mounting-plate/main.kcl) ([screenshot](screenshots/mounting-plate.png))
|
||||
[](mounting-plate/main.kcl)
|
||||
#### [multi-axis-robot](multi-axis-robot/main.kcl) ([screenshot](screenshots/multi-axis-robot.png))
|
||||
|
@ -35,7 +35,7 @@ ballsSketch = startSketchOn(XY)
|
||||
|> close()
|
||||
|
||||
// Revolve the ball to make a sphere and pattern around the inside wall
|
||||
balls = revolve(ballsSketch, axis = "X")
|
||||
balls = revolve(ballsSketch, axis = X)
|
||||
|> patternCircular3d(
|
||||
arcDegrees = 360,
|
||||
axis = [0, 0, 1],
|
||||
@ -60,7 +60,7 @@ chainSketch = startSketchOn(XY)
|
||||
|> close()
|
||||
|
||||
// Revolve the chain sketch
|
||||
chainHead = revolve(chainSketch, axis = "X")
|
||||
chainHead = revolve(chainSketch, axis = X)
|
||||
|> patternCircular3d(
|
||||
arcDegrees = 360,
|
||||
axis = [0, 0, 1],
|
||||
@ -80,7 +80,7 @@ linkSketch = startSketchOn(XZ)
|
||||
)
|
||||
|
||||
// Revolve the link sketch
|
||||
linkRevolve = revolve(linkSketch, axis = 'Y', angle = 360 / nBalls)
|
||||
linkRevolve = revolve(linkSketch, axis = Y, angle = 360 / nBalls)
|
||||
|> patternCircular3d(
|
||||
arcDegrees = 360,
|
||||
axis = [0, 0, 1],
|
||||
|
@ -80,5 +80,5 @@ brakeCaliperSketch = startSketchOn(XY)
|
||||
|> close()
|
||||
|
||||
// Revolve the brake caliper sketch
|
||||
revolve(brakeCaliperSketch, axis = "Y", angle = -70)
|
||||
revolve(brakeCaliperSketch, axis = Y, angle = -70)
|
||||
|> appearance(color = "#c82d2d", metalness = 90, roughness = 90)
|
||||
|
@ -41,5 +41,5 @@ tireSketch = startSketchOn(XY)
|
||||
|> close()
|
||||
|
||||
// Revolve the sketch to create the tire
|
||||
revolve(tireSketch, axis = "Y")
|
||||
revolve(tireSketch, axis = Y)
|
||||
|> appearance(color = "#0f0f0f", roughness = 80)
|
||||
|
@ -54,7 +54,7 @@ wheelCenterInner = startSketchOn(XY)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(axis = 'y')
|
||||
|> revolve(axis = Y)
|
||||
|> appearance(color = "#ffffff", metalness = 0, roughness = 0)
|
||||
|
||||
wheelCenterOuter = startSketchOn(XY)
|
||||
@ -68,7 +68,7 @@ wheelCenterOuter = startSketchOn(XY)
|
||||
|> yLine(endAbsolute = -wheelWidth / 20)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(axis = 'y')
|
||||
|> revolve(axis = Y)
|
||||
|> appearance(color = "#ffffff", metalness = 0, roughness = 0)
|
||||
|
||||
// Write a function that defines the spoke geometry, patterns and extrudes it
|
||||
@ -173,5 +173,5 @@ startSketchOn(XY)
|
||||
|> xLine(length = wheelWidth * 0.03)
|
||||
|> yLine(length = wheelWidth * 0.05)
|
||||
|> close()
|
||||
|> revolve(axis = 'y')
|
||||
|> revolve(axis = Y)
|
||||
|> appearance(color = "#ffffff", metalness = 0, roughness = 0)
|
||||
|
@ -32,7 +32,7 @@ fn lug(plane, length, diameter) {
|
||||
|> xLine(endAbsolute = lugThreadDiameter)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> close()
|
||||
|> revolve(axis = "Y")
|
||||
|> revolve(axis = Y)
|
||||
|> appearance(color = "#dbcd70", roughness = 90, metalness = 90)
|
||||
return lugSketch
|
||||
}
|
||||
|
200
public/kcl-samples/dual-basin-utility-sink/main.kcl
Normal file
@ -0,0 +1,200 @@
|
||||
// Dual-Basin Utility Sink
|
||||
// A stainless steel sink unit with dual rectangular basins and six under-counter storage compartments.
|
||||
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// globals
|
||||
tableHeight = 850
|
||||
tableWidth = 3400
|
||||
tableDepth = 400
|
||||
|
||||
profileThickness = 13
|
||||
metalThickness = 2
|
||||
|
||||
blockCount = 3
|
||||
blockWidth = (tableWidth-profileThickness) / 3
|
||||
blockHeight = tableHeight - metalThickness - 0.5
|
||||
blockDepth = tableDepth - profileThickness
|
||||
|
||||
blockSubdivisionCount = 2
|
||||
blockSubdivisionWidth = blockWidth / blockSubdivisionCount
|
||||
|
||||
// Geometry
|
||||
floorPlane = startSketchOn(XY)
|
||||
|
||||
// legs
|
||||
legHeight = blockHeight - profileThickness
|
||||
legCount = blockCount + 1
|
||||
|
||||
legBody = startProfileAt([0, 0], floorPlane)
|
||||
|> yLine(length=profileThickness)
|
||||
|> xLine(length=profileThickness)
|
||||
|> yLine(length=-profileThickness)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(axis = [1, 0], instances = legCount, distance = blockWidth)
|
||||
|> patternLinear2d(axis = [0, 1], instances = 2, distance = blockDepth)
|
||||
|> extrude(length = legHeight)
|
||||
|
||||
// lower belt
|
||||
lowerBeltHeightAboveTheFloor = 150
|
||||
lowerBeltLengthX = blockWidth - profileThickness
|
||||
|
||||
lowerBeltPlane = startSketchOn(offsetPlane(XY, offset = lowerBeltHeightAboveTheFloor))
|
||||
lowerBeltBodyX = startProfileAt([profileThickness, 0], lowerBeltPlane)
|
||||
|> yLine(length=profileThickness)
|
||||
|> xLine(length=lowerBeltLengthX)
|
||||
|> yLine(length=-profileThickness)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(axis = [1, 0], instances = blockCount, distance = blockWidth)
|
||||
|> patternLinear2d(axis = [0, 1], instances = 2, distance = blockDepth)
|
||||
|> extrude(length = profileThickness)
|
||||
|
||||
lowerBeltLengthY = blockDepth - profileThickness
|
||||
lowerBeltBodyY = startProfileAt([0, profileThickness], lowerBeltPlane)
|
||||
|> yLine(length=lowerBeltLengthY)
|
||||
|> xLine(length=profileThickness)
|
||||
|> yLine(length=-lowerBeltLengthY)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(axis = [1, 0], instances = 2, distance = tableWidth-profileThickness)
|
||||
|> extrude(length = profileThickness)
|
||||
|
||||
// pillars
|
||||
pillarHeightAboveTheFloor = lowerBeltHeightAboveTheFloor + profileThickness
|
||||
pillarPlane = startSketchOn(offsetPlane(XY, offset = pillarHeightAboveTheFloor))
|
||||
pillarTotalHeight = blockHeight - profileThickness - pillarHeightAboveTheFloor
|
||||
|
||||
pillarBody = startProfileAt([blockSubdivisionWidth, 0], pillarPlane)
|
||||
|> yLine(length=profileThickness)
|
||||
|> xLine(length=profileThickness)
|
||||
|> yLine(length=-profileThickness)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(axis = [1, 0], instances = blockCount, distance = blockWidth)
|
||||
|> patternLinear2d(axis = [0, 1], instances = 2, distance = blockDepth)
|
||||
|> extrude(length = pillarTotalHeight)
|
||||
|
||||
// upper belt
|
||||
upperBeltPlane = startSketchOn(offsetPlane(XY, offset = blockHeight))
|
||||
|
||||
upperBeltBodyX = startProfileAt([0, 0], upperBeltPlane)
|
||||
|> yLine(length=profileThickness)
|
||||
|> xLine(length=tableWidth)
|
||||
|> yLine(length=-profileThickness)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(axis = [0, 1], instances = 2, distance = blockDepth)
|
||||
|> extrude(length = -profileThickness)
|
||||
|
||||
upperBeltLengthY = blockDepth - profileThickness
|
||||
upperBeltBodyY = startProfileAt([0, profileThickness], upperBeltPlane)
|
||||
|> yLine(length=upperBeltLengthY)
|
||||
|> xLine(length=profileThickness)
|
||||
|> yLine(length=-upperBeltLengthY)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(axis = [1, 0], instances = 2, distance = tableWidth-profileThickness)
|
||||
|> extrude(length = -profileThickness)
|
||||
|
||||
// sink
|
||||
tableTopPlane = startSketchOn(offsetPlane(XY, offset = tableHeight))
|
||||
tableTopBody = startProfileAt([0, 0], tableTopPlane)
|
||||
|> yLine(length=tableDepth)
|
||||
|> xLine(length=tableWidth)
|
||||
|> yLine(length=-tableDepth)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(length = -metalThickness)
|
||||
|
||||
sinkCount = 2
|
||||
sinkWidth = 1000
|
||||
sinkLength = 250
|
||||
sinkDepth = 200
|
||||
sinkOffsetFront = 40
|
||||
sinkOffsetLeft = 350
|
||||
sinkSpacing = tableWidth - sinkWidth - sinkOffsetLeft*2
|
||||
|
||||
sinkPlaneOutside = startSketchOn(tableTopBody, 'START')
|
||||
sinkBodyOutside = startProfileAt([-sinkOffsetLeft, sinkOffsetFront], sinkPlaneOutside)
|
||||
|> yLine(length=sinkLength)
|
||||
|> xLine(length=-sinkWidth)
|
||||
|> yLine(length=-sinkLength)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(axis = [-1, 0], instances = sinkCount, distance = sinkSpacing)
|
||||
|> extrude(length = sinkDepth)
|
||||
|
||||
sinkPlaneInside = startSketchOn(tableTopBody, 'END')
|
||||
sinkBodyInside = startProfileAt([sinkOffsetLeft+metalThickness, sinkOffsetFront+metalThickness], sinkPlaneInside)
|
||||
|> yLine(length=sinkLength-metalThickness*2)
|
||||
|> xLine(length=sinkWidth-metalThickness*2)
|
||||
|> yLine(length=-sinkLength+metalThickness*2)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(axis = [1, 0], instances = sinkCount, distance = sinkSpacing)
|
||||
|> extrude(length = -sinkDepth)
|
||||
|
||||
// door panels
|
||||
doorGap = 2
|
||||
doorWidth = blockSubdivisionWidth - profileThickness - doorGap*2
|
||||
doorStart = profileThickness+doorGap
|
||||
doorHeightAboveTheFloor = pillarHeightAboveTheFloor + doorGap
|
||||
doorHeight = blockHeight - doorHeightAboveTheFloor - profileThickness - doorGap
|
||||
doorCount = blockCount * blockSubdivisionCount
|
||||
|
||||
doorPlane = startSketchOn(offsetPlane(XY, offset = doorHeightAboveTheFloor))
|
||||
doorBody = startProfileAt([doorStart, 0], doorPlane)
|
||||
|> yLine(length=profileThickness)
|
||||
|> xLine(length=doorWidth)
|
||||
|> yLine(length=-profileThickness)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(axis = [1, 0], instances = doorCount, distance = blockSubdivisionWidth)
|
||||
|> extrude(length = doorHeight)
|
||||
|
||||
// side panels
|
||||
panelWidth = blockDepth - profileThickness - doorGap*2
|
||||
panelCount = doorCount + 1
|
||||
panelSpacing = tableWidth - profileThickness
|
||||
panelBody = startProfileAt([0, doorStart], doorPlane)
|
||||
|> yLine(length=panelWidth)
|
||||
|> xLine(length=profileThickness)
|
||||
|> yLine(length=-panelWidth)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> patternLinear2d(axis = [1, 0], instances = 2, distance = panelSpacing)
|
||||
|> extrude(length = doorHeight)
|
||||
|
||||
// handle
|
||||
handleDepth = 40
|
||||
handleWidth = 120
|
||||
handleFillet = 20
|
||||
handleHeightAboveTheFloor = 780
|
||||
handleOffset = doorStart + doorWidth / 2 - (handleWidth / 2)
|
||||
handleLengthSegmentA = handleDepth - handleFillet
|
||||
handleLengthSegmentB = handleWidth - (handleFillet * 2)
|
||||
|
||||
handlePlane = startSketchOn(offsetPlane(XY, offset = handleHeightAboveTheFloor))
|
||||
|
||||
handleProfilePath = startProfileAt([0 + handleOffset, 0], handlePlane)
|
||||
|> yLine(length=-handleLengthSegmentA)
|
||||
|> tangentialArcTo([
|
||||
handleFillet + handleOffset,
|
||||
-handleDepth
|
||||
], %)
|
||||
|> xLine(length=handleLengthSegmentB)
|
||||
|> tangentialArcTo([
|
||||
handleOffset + handleWidth,
|
||||
-handleLengthSegmentA
|
||||
], %)
|
||||
|> yLine(length=handleLengthSegmentA)
|
||||
handleSectionPlane = startSketchOn(XZ)
|
||||
handleProfileSection = circle(
|
||||
handleSectionPlane,
|
||||
center = [handleOffset, handleHeightAboveTheFloor],
|
||||
radius = 2)
|
||||
|
||||
handleBody = sweep(handleProfileSection, path = handleProfilePath)
|
||||
|> patternLinear3d(axis = [1, 0, 0], instances = doorCount, distance = blockSubdivisionWidth)
|
@ -23,7 +23,7 @@ sketch001 = startSketchOn(XZ)
|
||||
], %, $rectangleSegmentC001)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(angle = 360, axis = 'Y')
|
||||
|> revolve(angle = 360, axis = Y)
|
||||
|
||||
// Create an angled plane to sketch the supports
|
||||
plane001 = {
|
||||
@ -132,7 +132,7 @@ sketch005 = startSketchOn(XZ)
|
||||
|> xLine(endAbsolute = 0.15)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(axis = 'y')
|
||||
|> revolve(axis = Y)
|
||||
|
||||
// Plunger and stem
|
||||
sketch006 = startSketchOn(XZ)
|
||||
@ -145,7 +145,7 @@ sketch006 = startSketchOn(XZ)
|
||||
|> tangentialArc({ radius = 0.6, offset = -90 }, %)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(axis = 'y')
|
||||
|> revolve(axis = Y)
|
||||
|
||||
// Spiral plate
|
||||
sketch007 = startSketchOn(offsetPlane(XY, offset = 1.12))
|
||||
@ -201,7 +201,7 @@ sketch011 = startSketchOn(XZ)
|
||||
}, %)
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> revolve(axis = 'y')
|
||||
|> revolve(axis = Y)
|
||||
|
||||
// Draw and extrude handle
|
||||
sketch012 = startSketchOn(offsetPlane(XZ, offset = handleThickness / 2))
|
||||
|
@ -48,10 +48,8 @@ sides = patternCircular3d(
|
||||
|
||||
// define an axis axis000
|
||||
axis000 = {
|
||||
custom = {
|
||||
axis = [0.0, 1.0],
|
||||
origin = [cornerRadius, cornerRadius]
|
||||
}
|
||||
direction = [0.0, 1.0],
|
||||
origin = [cornerRadius, cornerRadius]
|
||||
}
|
||||
|
||||
// create a single corner of the bin
|
||||
|
@ -45,10 +45,8 @@ sides = patternCircular3d(
|
||||
|
||||
// define an axis axis000
|
||||
axis000 = {
|
||||
custom = {
|
||||
axis = [0.0, 1.0],
|
||||
origin = [cornerRadius, cornerRadius]
|
||||
}
|
||||
direction = [0.0, 1.0],
|
||||
origin = [cornerRadius, cornerRadius]
|
||||
}
|
||||
|
||||
// create a single corner of the bin
|
||||
|
@ -65,13 +65,11 @@ sides = patternCircular3d(
|
||||
|
||||
// define an axis axis000
|
||||
axis000 = {
|
||||
custom = {
|
||||
axis = [0.0, 1.0],
|
||||
origin = [
|
||||
cornerRadius + binTol,
|
||||
cornerRadius + binTol
|
||||
]
|
||||
}
|
||||
direction = [0.0, 1.0],
|
||||
origin = [
|
||||
cornerRadius + binTol,
|
||||
cornerRadius + binTol
|
||||
]
|
||||
}
|
||||
|
||||
// create a single corner of the bin
|
||||
@ -272,10 +270,8 @@ lipWidths = patternCircular3d(
|
||||
|
||||
// define an axis axis000
|
||||
axis001 = {
|
||||
custom = {
|
||||
axis = [0.0, 1.0],
|
||||
origin = [cornerRadius, cornerRadius]
|
||||
}
|
||||
direction = [0.0, 1.0],
|
||||
origin = [cornerRadius, cornerRadius]
|
||||
}
|
||||
|
||||
// create a single corner of the bin
|
||||
|
@ -58,13 +58,11 @@ sides = patternCircular3d(
|
||||
|
||||
// define an axis axis000
|
||||
axis000 = {
|
||||
custom = {
|
||||
axis = [0.0, 1.0],
|
||||
origin = [
|
||||
cornerRadius + binTol,
|
||||
cornerRadius + binTol
|
||||
]
|
||||
}
|
||||
direction = [0.0, 1.0],
|
||||
origin = [
|
||||
cornerRadius + binTol,
|
||||
cornerRadius + binTol
|
||||
]
|
||||
}
|
||||
|
||||
// create a single corner of the bin
|
||||
|
@ -20,6 +20,6 @@ sketch001 = startSketchOn(-XZ)
|
||||
|> xLine(endAbsolute = webThickness / 2 + rootRadius)
|
||||
|> tangentialArc({ radius = rootRadius, offset = 90 }, %)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> mirror2d({ axis = 'X' }, %)
|
||||
|> mirror2d({ axis = 'Y' }, %)
|
||||
|> mirror2d(axis = X)
|
||||
|> mirror2d(axis = Y)
|
||||
|> extrude(length = beamLength)
|
||||
|
75
public/kcl-samples/makeup-mirror/main.kcl
Normal file
@ -0,0 +1,75 @@
|
||||
// Makeup Mirror
|
||||
// A circular vanity mirror mounted on a swiveling arm with pivot joints, used for personal grooming.
|
||||
|
||||
// Settings
|
||||
@settings(defaultLengthUnit = mm)
|
||||
|
||||
// hinge
|
||||
hingeRadius = 8
|
||||
hingeHeight = hingeRadius * 3
|
||||
hingeGap = 0.5
|
||||
|
||||
// arm
|
||||
armLength = 170
|
||||
armRadius = 5
|
||||
|
||||
// mirror
|
||||
mirrorRadius = 170 / 2
|
||||
mirrorThickness = 10
|
||||
archToMirrorGap = 5
|
||||
archThickness = 1
|
||||
archRadius = mirrorRadius + archToMirrorGap
|
||||
|
||||
// Geometry
|
||||
// hinge
|
||||
fn hingeFn(x, y, z) {
|
||||
hingeBody = startSketchOn(offsetPlane(XY, offset = z))
|
||||
|> circle(center = [x, y], radius = hingeRadius)
|
||||
|> extrude(length = hingeHeight)
|
||||
return hingeBody
|
||||
}
|
||||
|
||||
hingePartA1 = hingeFn(0, 0, 0)
|
||||
hingePartA2 = hingeFn(0, 0, hingeHeight + hingeGap)
|
||||
hingePartA3 = hingeFn(0, 0, hingeHeight * 2 + hingeGap * 2)
|
||||
|
||||
hingePartB2 = hingeFn(armLength, 0, hingeHeight + hingeGap)
|
||||
hingePartB3 = hingeFn(armLength, 0, hingeHeight * 2 + hingeGap * 2)
|
||||
|
||||
hingePartC2 = hingeFn(armLength, -armLength, hingeHeight * 2 + hingeGap * 2)
|
||||
hingePartC3 = hingeFn(armLength, -armLength, hingeHeight * 3 + hingeGap * 3)
|
||||
|
||||
// arm
|
||||
fn armFn(plane, offset, altitude) {
|
||||
armBody = startSketchOn(plane)
|
||||
|> circle(center = [offset, altitude], radius = armRadius)
|
||||
|> extrude(length = armLength)
|
||||
return armBody
|
||||
}
|
||||
|
||||
armPartA = armFn(YZ, 0, hingeHeight * 1.5 + hingeGap)
|
||||
armPartB = armFn(XZ, armLength, hingeHeight * 2.5 + hingeGap * 2)
|
||||
|
||||
// mirror
|
||||
fn mirrorFn(plane, offsetX, offsetY, altitude, radius, tiefe, gestellR, gestellD) {
|
||||
armPlane = startSketchOn(offsetPlane(plane, offset = offsetY - (tiefe / 2)))
|
||||
armBody = circle(armPlane, center = [offsetX, altitude], radius = radius)
|
||||
|> extrude(length = tiefe)
|
||||
|
||||
archBody = startProfileAt([offsetX-gestellR, altitude], armPlane)
|
||||
|> xLine(length = gestellD)
|
||||
|> arcTo({
|
||||
interior = [offsetX, altitude-gestellR],
|
||||
end = [offsetX+gestellR, altitude]
|
||||
}, %)
|
||||
|> xLine(length = gestellD)
|
||||
|> arcTo({
|
||||
interior = [offsetX, altitude-gestellR-gestellD],
|
||||
end = [profileStartX(%), profileStartY(%)]
|
||||
}, %)
|
||||
|> close()
|
||||
|> extrude(length = tiefe)
|
||||
return armBody
|
||||
}
|
||||
|
||||
mirror = mirrorFn(XZ, armLength, armLength, hingeHeight * 4 + hingeGap * 3 + mirrorRadius+archToMirrorGap+archThickness, mirrorRadius, mirrorThickness, archRadius, archThickness)
|
@ -62,6 +62,13 @@
|
||||
"title": "Hollow 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."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "dual-basin-utility-sink/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Dual-Basin Utility Sink",
|
||||
"description": "A stainless steel sink unit with dual rectangular basins and six under-counter storage compartments."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "enclosure/main.kcl",
|
||||
@ -181,6 +188,13 @@
|
||||
"title": "Lego Brick",
|
||||
"description": "A standard Lego brick. This is a small, plastic construction block toy that can be interlocked with other blocks to build various structures, models, and figures. There are a lot of hacks used in this code."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "makeup-mirror/main.kcl",
|
||||
"multipleFiles": false,
|
||||
"title": "Makeup Mirror",
|
||||
"description": "A circular vanity mirror mounted on a swiveling arm with pivot joints, used for personal grooming."
|
||||
},
|
||||
{
|
||||
"file": "main.kcl",
|
||||
"pathFromProjectDirectoryToFirstFile": "mounting-plate/main.kcl",
|
||||
|
@ -24,4 +24,4 @@ pipeProfile = outerProfile
|
||||
|> hole(innerProfile, %)
|
||||
|
||||
// revolve the pipe profile at the desired angle
|
||||
pipe = revolve(pipeProfile, axis = 'Y', angle = bendAngle)
|
||||
pipe = revolve(pipeProfile, axis = Y, angle = bendAngle)
|
||||
|
@ -33,4 +33,4 @@ pipeSketch = startSketchOn(XY)
|
||||
|> close()
|
||||
|
||||
// Revolve the sketch to create the pipe
|
||||
pipe = revolve(pipeSketch, axis = 'y')
|
||||
pipe = revolve(pipeSketch, axis = Y)
|
||||
|
@ -34,10 +34,8 @@ part001 = revolve(
|
||||
sketch001,
|
||||
angle = 90,
|
||||
axis = {
|
||||
custom = {
|
||||
axis = [1.0, 0.0],
|
||||
origin = [0.0, height + .0001]
|
||||
}
|
||||
direction = [1.0, 0.0],
|
||||
origin = [0.0, height + .0001]
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -14,7 +14,7 @@ radius = 10
|
||||
depth = 30
|
||||
distanceToInsideEdge = slateWidthHalf + templateThickness + templateGap
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([ZERO, depth + templateGap], %)
|
||||
|> startProfileAt([turns::ZERO, depth + templateGap], %)
|
||||
|> xLine(length = slateWidthHalf - radius, tag = $seg01)
|
||||
|> arc({
|
||||
angleEnd = 0,
|
||||
@ -28,7 +28,7 @@ sketch001 = startSketchOn(XZ)
|
||||
|> yLine(length = templateThickness * 2, tag = $seg08)
|
||||
|> xLine(endAbsolute = segEndX(seg02) + 0, tag = $seg05)
|
||||
|> yLine(endAbsolute = segEndY(seg01) + templateThickness, tag = $seg10)
|
||||
|> xLine(endAbsolute = ZERO, tag = $seg04)
|
||||
|> xLine(endAbsolute = turns::ZERO, tag = $seg04)
|
||||
|> xLine(length = -segLen(seg04))
|
||||
|> yLine(length = -segLen(seg10))
|
||||
|> xLine(length = -segLen(seg05))
|
||||
|
@ -28,7 +28,7 @@ sketch001 = startSketchOn(XZ)
|
||||
|> yLine(endAbsolute = -templateGap * 2 - (templateDiameter / 2), tag = $seg05)
|
||||
|> xLine(endAbsolute = slateWidthHalf + templateThickness, tag = $seg04)
|
||||
|> yLine(length = -length002, tag = $seg03)
|
||||
|> xLine(endAbsolute = ZERO, tag = $seg02)
|
||||
|> xLine(endAbsolute = turns::ZERO, tag = $seg02)
|
||||
// |> line(end = [7.78, 11.16])
|
||||
|> xLine(length = -segLen(seg02))
|
||||
|> yLine(length = segLen(seg03))
|
||||
|
BIN
public/kcl-samples/screenshots/dual-basin-utility-sink.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
public/kcl-samples/screenshots/makeup-mirror.png
Normal file
After Width: | Height: | Size: 62 KiB |
@ -21,7 +21,7 @@ export fn knob() {
|
||||
}, %)
|
||||
|> xLine(endAbsolute = 0.0001)
|
||||
|> close()
|
||||
|> revolve(axis = "Y")
|
||||
|> revolve(axis = Y)
|
||||
|> appearance(color = '#D0FF01', metalness = 90, roughness = 50)
|
||||
|
||||
return knob
|
||||
|
@ -876,7 +876,7 @@ async fn kcl_test_simple_revolve() {
|
||||
|> line(end = [0, -5.5])
|
||||
|> line(end = [-2, 0])
|
||||
|> close()
|
||||
|> revolve(axis = 'y')
|
||||
|> revolve(axis = Y)
|
||||
|
||||
"#;
|
||||
|
||||
@ -896,7 +896,7 @@ async fn kcl_test_simple_revolve_uppercase() {
|
||||
|> line(end = [0, -5.5])
|
||||
|> line(end = [-2, 0])
|
||||
|> close()
|
||||
|> revolve(axis = 'Y')
|
||||
|> revolve(axis = Y)
|
||||
|
||||
"#;
|
||||
|
||||
@ -916,7 +916,7 @@ async fn kcl_test_simple_revolve_negative() {
|
||||
|> line(end = [0, -5.5])
|
||||
|> line(end = [-2, 0])
|
||||
|> close()
|
||||
|> revolve(axis = '-Y', angle = 180)
|
||||
|> revolve(axis = -Y, angle = 180)
|
||||
|
||||
"#;
|
||||
|
||||
@ -936,7 +936,7 @@ async fn kcl_test_revolve_bad_angle_low() {
|
||||
|> line(end = [0, -5.5])
|
||||
|> line(end = [-2, 0])
|
||||
|> close()
|
||||
|> revolve(axis = 'y', angle = -455)
|
||||
|> revolve(axis = Y, angle = -455)
|
||||
|
||||
"#;
|
||||
|
||||
@ -962,7 +962,7 @@ async fn kcl_test_revolve_bad_angle_high() {
|
||||
|> line(end = [0, -5.5])
|
||||
|> line(end = [-2, 0])
|
||||
|> close()
|
||||
|> revolve(axis = 'y', angle = 455)
|
||||
|> revolve(axis = Y, angle = 455)
|
||||
|
||||
"#;
|
||||
|
||||
@ -988,7 +988,7 @@ async fn kcl_test_simple_revolve_custom_angle() {
|
||||
|> line(end = [0, -5.5])
|
||||
|> line(end = [-2, 0])
|
||||
|> close()
|
||||
|> revolve(axis = 'y', angle = 180)
|
||||
|> revolve(axis = Y, angle = 180)
|
||||
|
||||
"#;
|
||||
|
||||
@ -1008,7 +1008,7 @@ async fn kcl_test_simple_revolve_custom_axis() {
|
||||
|> line(end = [0, -5.5])
|
||||
|> line(end = [-2, 0])
|
||||
|> close()
|
||||
|> revolve(axis = {custom: {axis: [0, -1], origin: [0,0]}}, angle = 180)
|
||||
|> revolve(axis = { direction = [0, -1], origin: [0,0] }, angle = 180)
|
||||
|
||||
"#;
|
||||
|
||||
@ -1106,7 +1106,7 @@ sketch001 = startSketchOn(box, "END")
|
||||
|> circle(center = [10,10], radius= 4 )
|
||||
|> revolve(
|
||||
angle = -90,
|
||||
axis = 'y'
|
||||
axis = Y
|
||||
)
|
||||
"#;
|
||||
|
||||
@ -1131,7 +1131,7 @@ sketch001 = startSketchOn(box, "end")
|
||||
|> line(end = [0, 10])
|
||||
|> close()
|
||||
|> revolve(
|
||||
axis = 'y',
|
||||
axis = Y,
|
||||
angle = -90,
|
||||
)
|
||||
"#;
|
||||
@ -1146,7 +1146,7 @@ async fn kcl_test_basic_revolve_circle() {
|
||||
|> circle(center = [15, 0], radius= 5)
|
||||
|> revolve(
|
||||
angle = 360,
|
||||
axis = 'y'
|
||||
axis = Y
|
||||
)
|
||||
"#;
|
||||
|
||||
@ -1166,7 +1166,7 @@ async fn kcl_test_simple_revolve_sketch_on_edge() {
|
||||
|> line(end = [0, -5.5])
|
||||
|> line(end = [-2, 0])
|
||||
|> close()
|
||||
|> revolve(axis = 'y', angle = 180)
|
||||
|> revolve(axis = Y, angle = 180)
|
||||
|
||||
part002 = startSketchOn(part001, 'end')
|
||||
|> startProfileAt([4.5, -5], %)
|
||||
|
@ -23,8 +23,9 @@ use crate::{
|
||||
const TYPES_DIR: &str = "../../docs/kcl/types";
|
||||
const LANG_TOPICS: [&str; 5] = ["Types", "Modules", "Settings", "Known Issues", "Constants"];
|
||||
// These types are declared in std.
|
||||
const DECLARED_TYPES: [&str; 11] = [
|
||||
"number", "string", "tag", "bool", "Sketch", "Solid", "Plane", "Helix", "Face", "Point2d", "Point3d",
|
||||
const DECLARED_TYPES: [&str; 14] = [
|
||||
"number", "string", "tag", "bool", "Sketch", "Solid", "Plane", "Helix", "Face", "Edge", "Point2d", "Point3d",
|
||||
"Axis2d", "Axis3d",
|
||||
];
|
||||
|
||||
fn init_handlebars() -> Result<handlebars::Handlebars<'static>> {
|
||||
@ -339,9 +340,9 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &[Doc
|
||||
}
|
||||
|
||||
functions.entry(d.mod_name()).or_default().push(match d {
|
||||
DocData::Fn(f) => (f.name.clone(), d.file_name()),
|
||||
DocData::Const(c) => (c.name.clone(), d.file_name()),
|
||||
DocData::Ty(t) => (t.name.clone(), d.file_name()),
|
||||
DocData::Fn(f) => (f.preferred_name.clone(), d.file_name()),
|
||||
DocData::Const(c) => (c.preferred_name.clone(), d.file_name()),
|
||||
DocData::Ty(t) => (t.preferred_name.clone(), d.file_name()),
|
||||
});
|
||||
|
||||
if let DocData::Const(c) = d {
|
||||
|
@ -9,7 +9,7 @@ use tower_lsp::lsp_types::{
|
||||
use crate::{
|
||||
execution::annotations,
|
||||
parsing::{
|
||||
ast::types::{Annotation, Node, PrimitiveType, Type, VariableKind},
|
||||
ast::types::{Annotation, ImportSelector, Node, PrimitiveType, Type, VariableKind},
|
||||
token::NumericSuffix,
|
||||
},
|
||||
ModuleId,
|
||||
@ -17,7 +17,7 @@ use crate::{
|
||||
|
||||
pub fn walk_prelude() -> Vec<DocData> {
|
||||
let mut visitor = CollectionVisitor::default();
|
||||
visitor.visit_module("prelude").unwrap();
|
||||
visitor.visit_module("prelude", "").unwrap();
|
||||
visitor.result
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ struct CollectionVisitor {
|
||||
}
|
||||
|
||||
impl CollectionVisitor {
|
||||
fn visit_module(&mut self, name: &str) -> Result<(), String> {
|
||||
fn visit_module(&mut self, name: &str, preferred_prefix: &str) -> Result<(), String> {
|
||||
let old_name = std::mem::replace(&mut self.name, name.to_owned());
|
||||
let source = crate::modules::read_std(name).unwrap();
|
||||
let parsed = crate::parsing::parse_str(source, ModuleId::from_usize(self.id))
|
||||
@ -40,14 +40,16 @@ impl CollectionVisitor {
|
||||
for n in &parsed.body {
|
||||
match n {
|
||||
crate::parsing::ast::types::BodyItem::ImportStatement(import) if !import.visibility.is_default() => {
|
||||
// Only supports glob imports for now.
|
||||
assert!(matches!(
|
||||
import.selector,
|
||||
crate::parsing::ast::types::ImportSelector::Glob(..)
|
||||
));
|
||||
match &import.path {
|
||||
crate::parsing::ast::types::ImportPath::Std { path } => {
|
||||
self.visit_module(&path[1])?;
|
||||
match import.selector {
|
||||
ImportSelector::Glob(..) => self.visit_module(&path[1], "")?,
|
||||
ImportSelector::None { .. } => {
|
||||
self.visit_module(&path[1], &format!("{}::", import.module_name().unwrap()))?
|
||||
}
|
||||
// Only supports glob or whole-module imports for now.
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
p => return Err(format!("Unexpected import: `{p}`")),
|
||||
}
|
||||
@ -59,8 +61,8 @@ impl CollectionVisitor {
|
||||
format!("std::{}::", self.name)
|
||||
};
|
||||
let mut dd = match var.kind {
|
||||
VariableKind::Fn => DocData::Fn(FnData::from_ast(var, qual_name)),
|
||||
VariableKind::Const => DocData::Const(ConstData::from_ast(var, qual_name)),
|
||||
VariableKind::Fn => DocData::Fn(FnData::from_ast(var, qual_name, preferred_prefix)),
|
||||
VariableKind::Const => DocData::Const(ConstData::from_ast(var, qual_name, preferred_prefix)),
|
||||
};
|
||||
|
||||
dd.with_meta(&var.outer_attrs);
|
||||
@ -77,7 +79,7 @@ impl CollectionVisitor {
|
||||
} else {
|
||||
format!("std::{}::", self.name)
|
||||
};
|
||||
let mut dd = DocData::Ty(TyData::from_ast(ty, qual_name));
|
||||
let mut dd = DocData::Ty(TyData::from_ast(ty, qual_name, preferred_prefix));
|
||||
|
||||
dd.with_meta(&ty.outer_attrs);
|
||||
for a in &ty.outer_attrs {
|
||||
@ -200,6 +202,8 @@ impl DocData {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConstData {
|
||||
pub name: String,
|
||||
/// How the const is indexed, etc.
|
||||
pub preferred_name: String,
|
||||
/// The fully qualified name.
|
||||
pub qual_name: String,
|
||||
pub value: Option<String>,
|
||||
@ -216,7 +220,11 @@ pub struct ConstData {
|
||||
}
|
||||
|
||||
impl ConstData {
|
||||
fn from_ast(var: &crate::parsing::ast::types::VariableDeclaration, mut qual_name: String) -> Self {
|
||||
fn from_ast(
|
||||
var: &crate::parsing::ast::types::VariableDeclaration,
|
||||
mut qual_name: String,
|
||||
preferred_prefix: &str,
|
||||
) -> Self {
|
||||
assert_eq!(var.kind, crate::parsing::ast::types::VariableKind::Const);
|
||||
|
||||
let (value, ty) = match &var.declaration.init {
|
||||
@ -240,6 +248,7 @@ impl ConstData {
|
||||
let name = var.declaration.id.name.clone();
|
||||
qual_name.push_str(&name);
|
||||
ConstData {
|
||||
preferred_name: format!("{preferred_prefix}{name}"),
|
||||
name,
|
||||
qual_name,
|
||||
value,
|
||||
@ -272,7 +281,7 @@ impl ConstData {
|
||||
detail.push_str(ty);
|
||||
}
|
||||
CompletionItem {
|
||||
label: self.name.clone(),
|
||||
label: self.preferred_name.clone(),
|
||||
label_details: Some(CompletionItemLabelDetails {
|
||||
detail: self.value.clone(),
|
||||
description: None,
|
||||
@ -306,6 +315,8 @@ impl ConstData {
|
||||
pub struct FnData {
|
||||
/// The name of the function.
|
||||
pub name: String,
|
||||
/// How the function is indexed, etc.
|
||||
pub preferred_name: String,
|
||||
/// The fully qualified name.
|
||||
pub qual_name: String,
|
||||
/// The args of the function.
|
||||
@ -326,7 +337,11 @@ pub struct FnData {
|
||||
}
|
||||
|
||||
impl FnData {
|
||||
fn from_ast(var: &crate::parsing::ast::types::VariableDeclaration, mut qual_name: String) -> Self {
|
||||
fn from_ast(
|
||||
var: &crate::parsing::ast::types::VariableDeclaration,
|
||||
mut qual_name: String,
|
||||
preferred_prefix: &str,
|
||||
) -> Self {
|
||||
assert_eq!(var.kind, crate::parsing::ast::types::VariableKind::Fn);
|
||||
let crate::parsing::ast::types::Expr::FunctionExpression(expr) = &var.declaration.init else {
|
||||
unreachable!();
|
||||
@ -345,6 +360,7 @@ impl FnData {
|
||||
}
|
||||
|
||||
FnData {
|
||||
preferred_name: format!("{preferred_prefix}{name}"),
|
||||
name,
|
||||
qual_name,
|
||||
args: expr.params.iter().map(ArgData::from_ast).collect(),
|
||||
@ -443,7 +459,7 @@ impl FnData {
|
||||
}
|
||||
// We end with ${} so you can jump to the end of the snippet.
|
||||
// After the last argument.
|
||||
format!("{}({})${{}}", self.name, args.join(", "))
|
||||
format!("{}({})${{}}", self.preferred_name, args.join(", "))
|
||||
}
|
||||
|
||||
fn to_signature_help(&self) -> SignatureHelp {
|
||||
@ -452,7 +468,7 @@ impl FnData {
|
||||
|
||||
SignatureHelp {
|
||||
signatures: vec![SignatureInformation {
|
||||
label: self.name.clone(),
|
||||
label: self.preferred_name.clone(),
|
||||
documentation: self.short_docs().map(|s| {
|
||||
Documentation::MarkupContent(MarkupContent {
|
||||
kind: MarkupKind::Markdown,
|
||||
@ -492,6 +508,7 @@ pub struct ArgData {
|
||||
pub ty: Option<String>,
|
||||
/// If the argument is required.
|
||||
pub kind: ArgKind,
|
||||
pub override_in_snippet: Option<bool>,
|
||||
/// Additional information that could be used instead of the type's description.
|
||||
/// This is helpful if the type is really basic, like "number" -- that won't tell the user much about
|
||||
/// how this argument is meant to be used.
|
||||
@ -512,6 +529,7 @@ impl ArgData {
|
||||
name: arg.identifier.name.clone(),
|
||||
ty: arg.type_.as_ref().map(|t| t.to_string()),
|
||||
docs: None,
|
||||
override_in_snippet: None,
|
||||
kind: if arg.labeled {
|
||||
ArgKind::Labelled(arg.optional())
|
||||
} else {
|
||||
@ -519,26 +537,54 @@ impl ArgData {
|
||||
},
|
||||
};
|
||||
|
||||
for attr in &arg.identifier.outer_attrs {
|
||||
if let Annotation {
|
||||
name: None,
|
||||
properties: Some(props),
|
||||
..
|
||||
} = &attr.inner
|
||||
{
|
||||
for p in props {
|
||||
match &*p.key.name {
|
||||
"include_in_snippet" => {
|
||||
if let Some(b) = p.value.literal_bool() {
|
||||
result.override_in_snippet = Some(b);
|
||||
} else {
|
||||
panic!(
|
||||
"Invalid value for `include_in_snippet`, expected bool literal, found {:?}",
|
||||
p.value
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.with_comments(&arg.identifier.pre_comments);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn get_autocomplete_snippet(&self, index: usize) -> Option<(usize, String)> {
|
||||
match self.override_in_snippet {
|
||||
Some(false) => return None,
|
||||
None if !self.kind.required() => return None,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let label = if self.kind == ArgKind::Special {
|
||||
String::new()
|
||||
} else {
|
||||
format!("{} = ", self.name)
|
||||
};
|
||||
match self.ty.as_deref() {
|
||||
Some(s) if ["Sketch", "Solid", "Plane | Face", "Sketch | Plane | Face"].contains(&s) => {
|
||||
Some((index, format!("{label}${{{}:{}}}", index, "%")))
|
||||
}
|
||||
Some("number") if self.kind.required() => Some((index, format!(r#"{label}${{{}:3.14}}"#, index))),
|
||||
Some("Point2d") if self.kind.required() => Some((
|
||||
Some(s) if s.starts_with("number") => Some((index, format!(r#"{label}${{{}:3.14}}"#, index))),
|
||||
Some("Point2d") => Some((
|
||||
index + 1,
|
||||
format!(r#"{label}[${{{}:3.14}}, ${{{}:3.14}}]"#, index, index + 1),
|
||||
)),
|
||||
Some("Point3d") if self.kind.required() => Some((
|
||||
Some("Point3d") => Some((
|
||||
index + 2,
|
||||
format!(
|
||||
r#"{label}[${{{}:3.14}}, ${{{}:3.14}}, ${{{}:3.14}}]"#,
|
||||
@ -547,8 +593,10 @@ impl ArgData {
|
||||
index + 2
|
||||
),
|
||||
)),
|
||||
Some("string") if self.kind.required() => Some((index, format!(r#"{label}${{{}:"string"}}"#, index))),
|
||||
Some("bool") if self.kind.required() => Some((index, format!(r#"{label}${{{}:false}}"#, index))),
|
||||
Some("Axis2d | Edge") | Some("Axis3d | Edge") => Some((index, format!(r#"{label}${{{}:X}}"#, index))),
|
||||
|
||||
Some("string") => Some((index, format!(r#"{label}${{{}:"string"}}"#, index))),
|
||||
Some("bool") => Some((index, format!(r#"{label}${{{}:false}}"#, index))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -580,6 +628,8 @@ impl ArgKind {
|
||||
pub struct TyData {
|
||||
/// The name of the function.
|
||||
pub name: String,
|
||||
/// How the type is indexed, etc.
|
||||
pub preferred_name: String,
|
||||
/// The fully qualified name.
|
||||
pub qual_name: String,
|
||||
pub properties: Properties,
|
||||
@ -597,7 +647,11 @@ pub struct TyData {
|
||||
}
|
||||
|
||||
impl TyData {
|
||||
fn from_ast(ty: &crate::parsing::ast::types::TypeDeclaration, mut qual_name: String) -> Self {
|
||||
fn from_ast(
|
||||
ty: &crate::parsing::ast::types::TypeDeclaration,
|
||||
mut qual_name: String,
|
||||
preferred_prefix: &str,
|
||||
) -> Self {
|
||||
let name = ty.name.name.clone();
|
||||
qual_name.push_str(&name);
|
||||
let mut referenced_types = HashSet::new();
|
||||
@ -606,6 +660,7 @@ impl TyData {
|
||||
}
|
||||
|
||||
TyData {
|
||||
preferred_name: format!("{preferred_prefix}{name}"),
|
||||
name,
|
||||
qual_name,
|
||||
properties: Properties {
|
||||
@ -641,7 +696,7 @@ impl TyData {
|
||||
|
||||
fn to_completion_item(&self) -> CompletionItem {
|
||||
CompletionItem {
|
||||
label: self.name.clone(),
|
||||
label: self.preferred_name.clone(),
|
||||
label_details: self.alias.as_ref().map(|t| CompletionItemLabelDetails {
|
||||
detail: Some(format!("type {} = {t}", self.name)),
|
||||
description: None,
|
||||
@ -658,7 +713,7 @@ impl TyData {
|
||||
preselect: None,
|
||||
sort_text: None,
|
||||
filter_text: None,
|
||||
insert_text: Some(self.name.clone()),
|
||||
insert_text: Some(self.preferred_name.clone()),
|
||||
insert_text_format: Some(InsertTextFormat::SNIPPET),
|
||||
insert_text_mode: None,
|
||||
text_edit: None,
|
||||
|
@ -1000,9 +1000,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn get_autocomplete_snippet_revolve() {
|
||||
let revolve_fn: Box<dyn StdLibFn> = Box::new(crate::std::revolve::Revolve);
|
||||
let snippet = revolve_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(snippet, r#"revolve(${0:%}, axis = ${1:"X"})${}"#);
|
||||
let data = kcl_doc::walk_prelude();
|
||||
let DocData::Fn(revolve_fn) = data.into_iter().find(|d| d.name() == "revolve").unwrap() else {
|
||||
panic!();
|
||||
};
|
||||
let snippet = revolve_fn.to_autocomplete_snippet();
|
||||
assert_eq!(snippet, r#"revolve(axis = ${0:X})${}"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1015,7 +1018,7 @@ mod tests {
|
||||
let snippet = circle_fn.to_autocomplete_snippet();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"circle(${0:%}, center = [${1:3.14}, ${2:3.14}], radius = ${3:3.14})${}"#
|
||||
r#"circle(center = [${0:3.14}, ${1:3.14}], radius = ${2:3.14})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -1089,11 +1092,14 @@ mod tests {
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_helix() {
|
||||
let helix_fn: Box<dyn StdLibFn> = Box::new(crate::std::helix::Helix);
|
||||
let snippet = helix_fn.to_autocomplete_snippet().unwrap();
|
||||
let data = kcl_doc::walk_prelude();
|
||||
let DocData::Fn(helix_fn) = data.into_iter().find(|d| d.name() == "helix").unwrap() else {
|
||||
panic!();
|
||||
};
|
||||
let snippet = helix_fn.to_autocomplete_snippet();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"helix(revolutions = ${0:3.14}, angleStart = ${1:3.14}, radius = ${2:3.14}, axis = ${3:"X"}, length = ${4:3.14})${}"#
|
||||
r#"helix(revolutions = ${0:3.14}, angleStart = ${1:3.14}, radius = ${2:3.14}, axis = ${3:X}, length = ${4:3.14})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,19 @@ pub enum Operation {
|
||||
is_error: bool,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
KclStdLibCall {
|
||||
name: String,
|
||||
/// The unlabeled argument to the function.
|
||||
unlabeled_arg: Option<OpArg>,
|
||||
/// The labeled keyword arguments to the function.
|
||||
labeled_args: IndexMap<String, OpArg>,
|
||||
/// The source range of the operation in the source code.
|
||||
source_range: SourceRange,
|
||||
/// True if the operation resulted in an error.
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
is_error: bool,
|
||||
},
|
||||
#[serde(rename_all = "camelCase")]
|
||||
UserDefinedFunctionCall {
|
||||
/// The name of the user-defined function being called. Anonymous
|
||||
/// functions have no name.
|
||||
@ -49,6 +62,7 @@ impl Operation {
|
||||
pub(crate) fn set_std_lib_call_is_error(&mut self, is_err: bool) {
|
||||
match self {
|
||||
Self::StdLibCall { ref mut is_error, .. } => *is_error = is_err,
|
||||
Self::KclStdLibCall { ref mut is_error, .. } => *is_error = is_err,
|
||||
Self::UserDefinedFunctionCall { .. } | Self::UserDefinedFunctionReturn => {}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use std::collections::HashMap;
|
||||
use async_recursion::async_recursion;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::kcl_value::TypeDef;
|
||||
use super::{kcl_value::TypeDef, types::PrimitiveType};
|
||||
use crate::{
|
||||
engine::ExecutionKind,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
@ -1031,6 +1031,15 @@ impl Node<UnaryExpression> {
|
||||
}
|
||||
|
||||
let value = &self.argument.get_result(exec_state, ctx).await?;
|
||||
let err = || {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"You can only negate numbers, planes, or lines, but this is a {}",
|
||||
value.human_friendly_type()
|
||||
),
|
||||
source_ranges: vec![self.into()],
|
||||
})
|
||||
};
|
||||
match value {
|
||||
KclValue::Number { value, ty, .. } => {
|
||||
let meta = vec![Metadata {
|
||||
@ -1052,13 +1061,63 @@ impl Node<UnaryExpression> {
|
||||
plane.id = exec_state.next_uuid();
|
||||
Ok(KclValue::Plane { value: plane })
|
||||
}
|
||||
_ => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"You can only negate numbers or planes, but this is a {}",
|
||||
value.human_friendly_type()
|
||||
),
|
||||
source_ranges: vec![self.into()],
|
||||
})),
|
||||
KclValue::Object { value: values, meta } => {
|
||||
// Special-case for negating line-like objects.
|
||||
let Some(direction) = values.get("direction") else {
|
||||
return Err(err());
|
||||
};
|
||||
|
||||
let direction = match direction {
|
||||
KclValue::MixedArray { value: values, meta } => {
|
||||
let values = values
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
|
||||
value: *value * -1.0,
|
||||
ty: ty.clone(),
|
||||
meta: meta.clone(),
|
||||
}),
|
||||
_ => Err(err()),
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
KclValue::MixedArray {
|
||||
value: values,
|
||||
meta: meta.clone(),
|
||||
}
|
||||
}
|
||||
KclValue::HomArray {
|
||||
value: values,
|
||||
ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
|
||||
} => {
|
||||
let values = values
|
||||
.iter()
|
||||
.map(|v| match v {
|
||||
KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
|
||||
value: *value * -1.0,
|
||||
ty: ty.clone(),
|
||||
meta: meta.clone(),
|
||||
}),
|
||||
_ => Err(err()),
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
KclValue::HomArray {
|
||||
value: values,
|
||||
ty: ty.clone(),
|
||||
}
|
||||
}
|
||||
_ => return Err(err()),
|
||||
};
|
||||
|
||||
let mut value = values.clone();
|
||||
value.insert("direction".to_owned(), direction);
|
||||
Ok(KclValue::Object {
|
||||
value,
|
||||
meta: meta.clone(),
|
||||
})
|
||||
}
|
||||
_ => Err(err()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2145,6 +2204,27 @@ impl FunctionSource {
|
||||
}
|
||||
}
|
||||
|
||||
let op = if props.include_in_feature_tree {
|
||||
let op_labeled_args = args
|
||||
.kw_args
|
||||
.labeled
|
||||
.iter()
|
||||
.map(|(k, arg)| (k.clone(), OpArg::new(OpKclValue::from(&arg.value), arg.source_range)))
|
||||
.collect();
|
||||
Some(Operation::KclStdLibCall {
|
||||
// TODO
|
||||
name: String::new(),
|
||||
unlabeled_arg: args
|
||||
.unlabeled_kw_arg_unconverted()
|
||||
.map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
|
||||
labeled_args: op_labeled_args,
|
||||
source_range: callsite,
|
||||
is_error: false,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Attempt to call the function.
|
||||
exec_state.mut_stack().push_new_env_for_rust_call();
|
||||
let mut result = {
|
||||
@ -2152,7 +2232,15 @@ impl FunctionSource {
|
||||
let result = func(exec_state, args).await;
|
||||
exec_state.mut_stack().pop_env();
|
||||
|
||||
// TODO support recording op into the feature tree
|
||||
if let Some(mut op) = op {
|
||||
op.set_std_lib_call_is_error(result.is_err());
|
||||
// Track call operation. We do this after the call
|
||||
// since things like patternTransform may call user code
|
||||
// before running, and we will likely want to use the
|
||||
// return value. The call takes ownership of the args,
|
||||
// so we need to build the op before the call.
|
||||
exec_state.global.operations.push(op);
|
||||
}
|
||||
result
|
||||
}?;
|
||||
|
||||
|
@ -56,6 +56,20 @@ impl RuntimeType {
|
||||
RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
|
||||
}
|
||||
|
||||
/// `[number; 2]`
|
||||
pub fn point2d() -> Self {
|
||||
RuntimeType::Array(Box::new(RuntimeType::number_any()), ArrayLen::Known(2))
|
||||
}
|
||||
|
||||
/// `[number; 3]`
|
||||
pub fn point3d() -> Self {
|
||||
RuntimeType::Array(Box::new(RuntimeType::number_any()), ArrayLen::Known(3))
|
||||
}
|
||||
|
||||
pub fn number_any() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))
|
||||
}
|
||||
|
||||
pub fn from_parsed(
|
||||
value: Type,
|
||||
exec_state: &mut ExecState,
|
||||
@ -93,24 +107,30 @@ impl RuntimeType {
|
||||
AstPrimitiveType::Number(suffix) => RuntimeType::Primitive(PrimitiveType::Number(
|
||||
NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
|
||||
)),
|
||||
AstPrimitiveType::Named(name) => {
|
||||
let ty_val = exec_state
|
||||
.stack()
|
||||
.get(&format!("{}{}", memory::TYPE_PREFIX, name.name), source_range)
|
||||
.map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", name.name)))?;
|
||||
|
||||
match ty_val {
|
||||
KclValue::Type { value, .. } => match value {
|
||||
TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()),
|
||||
TypeDef::Alias(ty) => ty.clone(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?,
|
||||
AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_alias(
|
||||
alias: &str,
|
||||
exec_state: &mut ExecState,
|
||||
source_range: SourceRange,
|
||||
) -> Result<Self, CompilationError> {
|
||||
let ty_val = exec_state
|
||||
.stack()
|
||||
.get(&format!("{}{}", memory::TYPE_PREFIX, alias), source_range)
|
||||
.map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", alias)))?;
|
||||
|
||||
Ok(match ty_val {
|
||||
KclValue::Type { value, .. } => match value {
|
||||
TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()),
|
||||
TypeDef::Alias(ty) => ty.clone(),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn human_friendly_type(&self) -> String {
|
||||
match self {
|
||||
RuntimeType::Primitive(ty) => ty.to_string(),
|
||||
@ -143,6 +163,35 @@ impl RuntimeType {
|
||||
(Object(t1), Object(t2)) => t2
|
||||
.iter()
|
||||
.all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))),
|
||||
// Equality between Axis types and there object representation.
|
||||
(Object(t1), Primitive(PrimitiveType::Axis2d)) => {
|
||||
t1.iter()
|
||||
.any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
|
||||
&& t1
|
||||
.iter()
|
||||
.any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
|
||||
}
|
||||
(Object(t1), Primitive(PrimitiveType::Axis3d)) => {
|
||||
t1.iter()
|
||||
.any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
|
||||
&& t1
|
||||
.iter()
|
||||
.any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
|
||||
}
|
||||
(Primitive(PrimitiveType::Axis2d), Object(t2)) => {
|
||||
t2.iter()
|
||||
.any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
|
||||
&& t2
|
||||
.iter()
|
||||
.any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
|
||||
}
|
||||
(Primitive(PrimitiveType::Axis3d), Object(t2)) => {
|
||||
t2.iter()
|
||||
.any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
|
||||
&& t2
|
||||
.iter()
|
||||
.any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -213,11 +262,11 @@ impl ArrayLen {
|
||||
}
|
||||
|
||||
/// True if the length constraint is satisfied by the supplied length.
|
||||
fn satisfied(self, len: usize) -> bool {
|
||||
fn satisfied(self, len: usize, allow_shrink: bool) -> Option<usize> {
|
||||
match self {
|
||||
ArrayLen::None => true,
|
||||
ArrayLen::NonEmpty => len > 0,
|
||||
ArrayLen::Known(s) => len == s,
|
||||
ArrayLen::None => Some(len),
|
||||
ArrayLen::NonEmpty => (len > 0).then_some(len),
|
||||
ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -233,6 +282,9 @@ pub enum PrimitiveType {
|
||||
Plane,
|
||||
Helix,
|
||||
Face,
|
||||
Edge,
|
||||
Axis2d,
|
||||
Axis3d,
|
||||
ImportedGeometry,
|
||||
}
|
||||
|
||||
@ -248,6 +300,9 @@ impl PrimitiveType {
|
||||
PrimitiveType::Plane => "Planes".to_owned(),
|
||||
PrimitiveType::Helix => "Helices".to_owned(),
|
||||
PrimitiveType::Face => "Faces".to_owned(),
|
||||
PrimitiveType::Edge => "Edges".to_owned(),
|
||||
PrimitiveType::Axis2d => "2d axes".to_owned(),
|
||||
PrimitiveType::Axis3d => "3d axes".to_owned(),
|
||||
PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
|
||||
PrimitiveType::Tag => "tags".to_owned(),
|
||||
}
|
||||
@ -273,6 +328,9 @@ impl fmt::Display for PrimitiveType {
|
||||
PrimitiveType::Solid => write!(f, "Solid"),
|
||||
PrimitiveType::Plane => write!(f, "Plane"),
|
||||
PrimitiveType::Face => write!(f, "Face"),
|
||||
PrimitiveType::Edge => write!(f, "Edge"),
|
||||
PrimitiveType::Axis2d => write!(f, "Axis2d"),
|
||||
PrimitiveType::Axis3d => write!(f, "Axis3d"),
|
||||
PrimitiveType::Helix => write!(f, "Helix"),
|
||||
PrimitiveType::ImportedGeometry => write!(f, "imported geometry"),
|
||||
}
|
||||
@ -298,6 +356,10 @@ impl NumericType {
|
||||
NumericType::Known(UnitType::Count)
|
||||
}
|
||||
|
||||
pub fn mm() -> Self {
|
||||
NumericType::Known(UnitType::Length(UnitLen::Mm))
|
||||
}
|
||||
|
||||
/// Combine two types when we expect them to be equal.
|
||||
pub fn combine_eq(self, other: &NumericType) -> NumericType {
|
||||
if &self == other {
|
||||
@ -541,7 +603,7 @@ impl KclValue {
|
||||
pub fn coerce(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Option<KclValue> {
|
||||
match ty {
|
||||
RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, exec_state),
|
||||
RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, *len, exec_state),
|
||||
RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, *len, exec_state, false),
|
||||
RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, exec_state),
|
||||
RuntimeType::Union(tys) => self.coerce_to_union_type(tys, exec_state),
|
||||
RuntimeType::Object(tys) => self.coerce_to_object_type(tys, exec_state),
|
||||
@ -609,6 +671,55 @@ impl KclValue {
|
||||
KclValue::Helix { .. } => Some(value.clone()),
|
||||
_ => None,
|
||||
},
|
||||
PrimitiveType::Edge => match value {
|
||||
KclValue::Uuid { .. } => Some(value.clone()),
|
||||
KclValue::TagIdentifier { .. } => Some(value.clone()),
|
||||
_ => None,
|
||||
},
|
||||
PrimitiveType::Axis2d => match value {
|
||||
KclValue::Object { value: values, meta } => {
|
||||
if values.get("origin")?.has_type(&RuntimeType::point2d())
|
||||
&& values.get("direction")?.has_type(&RuntimeType::point2d())
|
||||
{
|
||||
return Some(value.clone());
|
||||
}
|
||||
|
||||
let origin = values.get("origin").and_then(|p| {
|
||||
p.coerce_to_array_type(&RuntimeType::number_any(), ArrayLen::Known(2), exec_state, true)
|
||||
})?;
|
||||
let direction = values.get("direction").and_then(|p| {
|
||||
p.coerce_to_array_type(&RuntimeType::number_any(), ArrayLen::Known(2), exec_state, true)
|
||||
})?;
|
||||
|
||||
Some(KclValue::Object {
|
||||
value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
|
||||
meta: meta.clone(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
PrimitiveType::Axis3d => match value {
|
||||
KclValue::Object { value: values, meta } => {
|
||||
if values.get("origin")?.has_type(&RuntimeType::point3d())
|
||||
&& values.get("direction")?.has_type(&RuntimeType::point3d())
|
||||
{
|
||||
return Some(value.clone());
|
||||
}
|
||||
|
||||
let origin = values.get("origin").and_then(|p| {
|
||||
p.coerce_to_array_type(&RuntimeType::number_any(), ArrayLen::Known(3), exec_state, true)
|
||||
})?;
|
||||
let direction = values.get("direction").and_then(|p| {
|
||||
p.coerce_to_array_type(&RuntimeType::number_any(), ArrayLen::Known(3), exec_state, true)
|
||||
})?;
|
||||
|
||||
Some(KclValue::Object {
|
||||
value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
|
||||
meta: meta.clone(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
PrimitiveType::ImportedGeometry => match value {
|
||||
KclValue::ImportedGeometry { .. } => Some(value.clone()),
|
||||
_ => None,
|
||||
@ -621,60 +732,35 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_to_array_type(&self, ty: &RuntimeType, len: ArrayLen, exec_state: &mut ExecState) -> Option<KclValue> {
|
||||
fn coerce_to_array_type(
|
||||
&self,
|
||||
ty: &RuntimeType,
|
||||
len: ArrayLen,
|
||||
exec_state: &mut ExecState,
|
||||
allow_shrink: bool,
|
||||
) -> Option<KclValue> {
|
||||
match self {
|
||||
KclValue::HomArray { value, ty: aty } if aty == ty => {
|
||||
let value = match len {
|
||||
ArrayLen::None => value.clone(),
|
||||
ArrayLen::NonEmpty => {
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
value.clone()
|
||||
}
|
||||
ArrayLen::Known(n) => {
|
||||
if n != value.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
value[..n].to_vec()
|
||||
}
|
||||
};
|
||||
|
||||
Some(KclValue::HomArray { value, ty: ty.clone() })
|
||||
KclValue::HomArray { value, ty: aty } if aty.subtype(ty) => {
|
||||
len.satisfied(value.len(), allow_shrink).map(|len| KclValue::HomArray {
|
||||
value: value[..len].to_vec(),
|
||||
ty: aty.clone(),
|
||||
})
|
||||
}
|
||||
value if len.satisfied(1) && value.has_type(ty) => Some(KclValue::HomArray {
|
||||
value if len.satisfied(1, false).is_some() && value.has_type(ty) => Some(KclValue::HomArray {
|
||||
value: vec![value.clone()],
|
||||
ty: ty.clone(),
|
||||
}),
|
||||
KclValue::MixedArray { value, .. } => {
|
||||
let value = match len {
|
||||
ArrayLen::None => value.clone(),
|
||||
ArrayLen::NonEmpty => {
|
||||
if value.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let len = len.satisfied(value.len(), allow_shrink)?;
|
||||
|
||||
value.clone()
|
||||
}
|
||||
ArrayLen::Known(n) => {
|
||||
if n != value.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
value[..n].to_vec()
|
||||
}
|
||||
};
|
||||
|
||||
let value = value
|
||||
let value = value[..len]
|
||||
.iter()
|
||||
.map(|v| v.coerce(ty, exec_state))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
|
||||
Some(KclValue::HomArray { value, ty: ty.clone() })
|
||||
}
|
||||
KclValue::KclNone { .. } if len.satisfied(0) => Some(KclValue::HomArray {
|
||||
KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Some(KclValue::HomArray {
|
||||
value: Vec::new(),
|
||||
ty: ty.clone(),
|
||||
}),
|
||||
@ -1251,4 +1337,119 @@ mod test {
|
||||
assert!(count.coerce(&tyb, &mut exec_state).is_none());
|
||||
assert!(count.coerce(&tyb2, &mut exec_state).is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn coerce_axes() {
|
||||
let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
|
||||
|
||||
// Subtyping
|
||||
assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
|
||||
assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
|
||||
assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
|
||||
assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
|
||||
|
||||
// Coercion
|
||||
let a2d = KclValue::Object {
|
||||
value: [
|
||||
(
|
||||
"origin".to_owned(),
|
||||
KclValue::HomArray {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::mm(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::mm(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
],
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
|
||||
},
|
||||
),
|
||||
(
|
||||
"direction".to_owned(),
|
||||
KclValue::HomArray {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::mm(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::mm(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
],
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
|
||||
},
|
||||
),
|
||||
]
|
||||
.into(),
|
||||
meta: Vec::new(),
|
||||
};
|
||||
let a3d = KclValue::Object {
|
||||
value: [
|
||||
(
|
||||
"origin".to_owned(),
|
||||
KclValue::HomArray {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::mm(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::mm(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::mm(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
],
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
|
||||
},
|
||||
),
|
||||
(
|
||||
"direction".to_owned(),
|
||||
KclValue::HomArray {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::mm(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::mm(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::mm(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
],
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
|
||||
},
|
||||
),
|
||||
]
|
||||
.into(),
|
||||
meta: Vec::new(),
|
||||
};
|
||||
|
||||
let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
|
||||
let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
|
||||
|
||||
assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
|
||||
assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
|
||||
assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
|
||||
assert!(a2d.coerce(&ty3d, &mut exec_state).is_none());
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ const Part001 = startSketchOn('XY')
|
||||
|> line([0, pipeLength], %)
|
||||
|> angledLineToX({ angle: 60, to: pipeLargeDia }, %)
|
||||
|> close()
|
||||
|> revolve({ axis: 'y' }, %)
|
||||
|> revolve({ axis = Y }, %)
|
||||
"
|
||||
);
|
||||
|
||||
@ -156,7 +156,7 @@ const part001 = startSketchOn('XY')
|
||||
|> line([0, pipeLength], %)
|
||||
|> angledLineToX({ angle: 60, to: pipeLargeDia }, %)
|
||||
|> close()
|
||||
|> revolve({ axis: 'y' }, %)
|
||||
|> revolve({ axis = Y }, %)
|
||||
"
|
||||
);
|
||||
|
||||
|
@ -1632,7 +1632,7 @@ insideRevolve = startSketchOn(XZ)
|
||||
|> line(end = [0, -thickness])
|
||||
|> line(end = [-overHangLength, 0])
|
||||
|> close()
|
||||
|> revolve({ axis: 'y' }, %)
|
||||
|> revolve({ axis = Y }, %)
|
||||
|
||||
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
|
||||
sphere = startSketchOn(XZ)
|
||||
@ -1647,7 +1647,7 @@ sphere = startSketchOn(XZ)
|
||||
radius: sphereDia / 2 - 0.05
|
||||
}, %)
|
||||
|> close()
|
||||
|> revolve({ axis: 'x' }, %)
|
||||
|> revolve({ axis = X }, %)
|
||||
|> patternCircular3d(
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
@ -1671,7 +1671,7 @@ outsideRevolve = startSketchOn(XZ)
|
||||
|> line(end = [0, thickness])
|
||||
|> line(end = [overHangLength - thickness, 0])
|
||||
|> close()
|
||||
|> revolve({ axis: 'y' }, %)"#
|
||||
|> revolve({ axis = Y }, %)"#
|
||||
.to_string(),
|
||||
},
|
||||
})
|
||||
@ -1705,7 +1705,7 @@ outsideRevolve = startSketchOn(XZ)
|
||||
start: tower_lsp::lsp_types::Position { line: 0, character: 0 },
|
||||
end: tower_lsp::lsp_types::Position {
|
||||
line: 60,
|
||||
character: 30
|
||||
character: 29
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -1732,7 +1732,7 @@ insideRevolve = startSketchOn(XZ)
|
||||
|> line(end = [0, -thickness])
|
||||
|> line(end = [-overHangLength, 0])
|
||||
|> close()
|
||||
|> revolve({ axis = 'y' }, %)
|
||||
|> revolve({ axis = Y }, %)
|
||||
|
||||
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
|
||||
sphere = startSketchOn(XZ)
|
||||
@ -1747,7 +1747,7 @@ sphere = startSketchOn(XZ)
|
||||
radius = sphereDia / 2 - 0.05
|
||||
}, %)
|
||||
|> close()
|
||||
|> revolve({ axis = 'x' }, %)
|
||||
|> revolve({ axis = X }, %)
|
||||
|> patternCircular3d(
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
@ -1771,7 +1771,7 @@ outsideRevolve = startSketchOn(XZ)
|
||||
|> line(end = [0, thickness])
|
||||
|> line(end = [overHangLength - thickness, 0])
|
||||
|> close()
|
||||
|> revolve({ axis = 'y' }, %)"#
|
||||
|> revolve({ axis = Y }, %)"#
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -88,6 +88,7 @@ pub(crate) fn read_std(mod_name: &str) -> Option<&'static str> {
|
||||
"prelude" => Some(include_str!("../std/prelude.kcl")),
|
||||
"math" => Some(include_str!("../std/math.kcl")),
|
||||
"sketch" => Some(include_str!("../std/sketch.kcl")),
|
||||
"turns" => Some(include_str!("../std/turns.kcl")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -1609,19 +1609,21 @@ impl ImportStatement {
|
||||
return Some(alias.name.clone());
|
||||
}
|
||||
|
||||
let mut parts = match &self.path {
|
||||
ImportPath::Kcl { filename: s } | ImportPath::Foreign { path: s } => s.split('.'),
|
||||
_ => return None,
|
||||
};
|
||||
let path = parts.next()?;
|
||||
let _ext = parts.next()?;
|
||||
let rest = parts.next();
|
||||
match &self.path {
|
||||
ImportPath::Kcl { filename: s } | ImportPath::Foreign { path: s } => {
|
||||
let mut parts = s.split('.');
|
||||
let path = parts.next()?;
|
||||
let _ext = parts.next()?;
|
||||
let rest = parts.next();
|
||||
|
||||
if rest.is_some() {
|
||||
return None;
|
||||
if rest.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
path.rsplit(&['/', '\\']).next().map(str::to_owned)
|
||||
}
|
||||
ImportPath::Std { path } => path.last().cloned(),
|
||||
}
|
||||
|
||||
path.rsplit(&['/', '\\']).next().map(str::to_owned)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,7 +152,12 @@ const STR_DEPRECATIONS: [(&str, &str); 6] = [
|
||||
("-YZ", "-YZ"),
|
||||
];
|
||||
const FN_DEPRECATIONS: [(&str, &str); 3] = [("pi", "PI"), ("e", "E"), ("tau", "TAU")];
|
||||
const CONST_DEPRECATIONS: [(&str, &str); 0] = [];
|
||||
const CONST_DEPRECATIONS: [(&str, &str); 4] = [
|
||||
("ZERO", "turns::ZERO"),
|
||||
("QUARTER_TURN", "turns::QUARTER_TURN"),
|
||||
("HALF_TURN", "turns::HALF_TURN"),
|
||||
("THREE_QUARTER_TURN", "turns::THREE_QUARTER_TURN"),
|
||||
];
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DeprecationKind {
|
||||
|
@ -286,6 +286,11 @@ fn non_code_node(i: &mut TokenSlice) -> PResult<Node<NonCodeNode>> {
|
||||
alt((non_code_node_leading_whitespace, non_code_node_no_leading_whitespace)).parse_next(i)
|
||||
}
|
||||
|
||||
fn outer_annotation(i: &mut TokenSlice) -> PResult<Node<Annotation>> {
|
||||
peek((at_sign, open_paren)).parse_next(i)?;
|
||||
annotation(i)
|
||||
}
|
||||
|
||||
fn annotation(i: &mut TokenSlice) -> PResult<Node<Annotation>> {
|
||||
let at = at_sign.parse_next(i)?;
|
||||
let name = opt(binding_name).parse_next(i)?;
|
||||
@ -1823,14 +1828,6 @@ fn import_stmt(i: &mut TokenSlice) -> PResult<BoxNode<ImportStatement>> {
|
||||
)
|
||||
.into(),
|
||||
));
|
||||
} else if matches!(path, ImportPath::Std { .. }) && matches!(selector, ImportSelector::None { .. }) {
|
||||
return Err(ErrMode::Cut(
|
||||
CompilationError::fatal(
|
||||
SourceRange::new(start, end, module_id),
|
||||
"the standard library cannot be imported as a part",
|
||||
)
|
||||
.into(),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Node::boxed(
|
||||
@ -2910,13 +2907,17 @@ struct ParamDescription {
|
||||
arg_name: Token,
|
||||
type_: std::option::Option<Node<Type>>,
|
||||
default_value: Option<DefaultParamVal>,
|
||||
attr: Option<Node<Annotation>>,
|
||||
comments: Option<Node<Vec<String>>>,
|
||||
}
|
||||
|
||||
fn parameter(i: &mut TokenSlice) -> PResult<ParamDescription> {
|
||||
let (_, comments, found_at_sign, arg_name, question_mark, _, type_, _ws, default_literal) = (
|
||||
let (_, comments, _, attr, _, found_at_sign, arg_name, question_mark, _, type_, _ws, default_literal) = (
|
||||
opt(whitespace),
|
||||
opt(comments),
|
||||
opt(whitespace),
|
||||
opt(outer_annotation),
|
||||
opt(whitespace),
|
||||
opt(at_sign),
|
||||
any.verify(|token: &Token| !matches!(token.token_type, TokenType::Brace) || token.value != ")"),
|
||||
opt(question_mark),
|
||||
@ -2941,6 +2942,7 @@ fn parameter(i: &mut TokenSlice) -> PResult<ParamDescription> {
|
||||
return Err(ErrMode::Backtrack(ContextError::from(e)));
|
||||
}
|
||||
},
|
||||
attr,
|
||||
comments,
|
||||
})
|
||||
}
|
||||
@ -2962,6 +2964,7 @@ fn parameters(i: &mut TokenSlice) -> PResult<Vec<Parameter>> {
|
||||
arg_name,
|
||||
type_,
|
||||
default_value,
|
||||
attr,
|
||||
comments,
|
||||
}| {
|
||||
let mut identifier = Node::<Identifier>::try_from(arg_name)?;
|
||||
@ -2969,6 +2972,9 @@ fn parameters(i: &mut TokenSlice) -> PResult<Vec<Parameter>> {
|
||||
identifier.comment_start = comments.start;
|
||||
identifier.pre_comments = comments.inner;
|
||||
}
|
||||
if let Some(attr) = attr {
|
||||
identifier.outer_attrs.push(attr);
|
||||
}
|
||||
|
||||
Ok(Parameter {
|
||||
identifier,
|
||||
|
@ -75,7 +75,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
/// This will work on any solid, including extruded solids, revolved solids, and shelled solids.
|
||||
/// ```no_run
|
||||
/// // Add color to an extruded solid.
|
||||
/// exampleSketch = startSketchOn("XZ")
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(endAbsolute = [10, 0])
|
||||
/// |> line(endAbsolute = [0, 10])
|
||||
@ -89,9 +89,9 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Add color to a revolved solid.
|
||||
/// sketch001 = startSketchOn('XY')
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> circle( center = [15, 0], radius = 5 )
|
||||
/// |> revolve( angle = 360, axis = 'y')
|
||||
/// |> revolve( angle = 360, axis = Y)
|
||||
/// |> appearance(
|
||||
/// color = '#ff0000',
|
||||
/// metalness = 90,
|
||||
@ -102,7 +102,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
/// ```no_run
|
||||
/// // Add color to different solids.
|
||||
/// fn cube(center) {
|
||||
/// return startSketchOn('XY')
|
||||
/// return startSketchOn(XY)
|
||||
/// |> startProfileAt([center[0] - 10, center[1] - 10], %)
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] - 10])
|
||||
/// |> line(endAbsolute = [center[0] + 10, center[1] + 10])
|
||||
@ -122,7 +122,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
/// ```no_run
|
||||
/// // You can set the appearance before or after you shell it will yield the same result.
|
||||
/// // This example shows setting the appearance _after_ the shell.
|
||||
/// firstSketch = startSketchOn('XY')
|
||||
/// firstSketch = startSketchOn(XY)
|
||||
/// |> startProfileAt([-12, 12], %)
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
@ -145,7 +145,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
/// ```no_run
|
||||
/// // You can set the appearance before or after you shell it will yield the same result.
|
||||
/// // This example shows setting the appearance _before_ the shell.
|
||||
/// firstSketch = startSketchOn('XY')
|
||||
/// firstSketch = startSketchOn(XY)
|
||||
/// |> startProfileAt([-12, 12], %)
|
||||
/// |> line(end = [24, 0])
|
||||
/// |> line(end = [0, -24])
|
||||
@ -168,7 +168,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
/// ```no_run
|
||||
/// // Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.
|
||||
/// // This example shows _before_ the pattern.
|
||||
/// exampleSketch = startSketchOn('XZ')
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 2])
|
||||
/// |> line(end = [3, 1])
|
||||
@ -191,7 +191,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
/// ```no_run
|
||||
/// // Setting the appearance of a 3D pattern can be done _before_ or _after_ the pattern.
|
||||
/// // This example shows _after_ the pattern.
|
||||
/// exampleSketch = startSketchOn('XZ')
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 2])
|
||||
/// |> line(end = [3, 1])
|
||||
@ -213,7 +213,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Color the result of a 2D pattern that was extruded.
|
||||
/// exampleSketch = startSketchOn('XZ')
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfileAt([.5, 25], %)
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [-1, 0])
|
||||
@ -238,7 +238,7 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
/// // Color the result of a sweep.
|
||||
///
|
||||
/// // Create a path for the sweep.
|
||||
/// sweepPath = startSketchOn('XZ')
|
||||
/// sweepPath = startSketchOn(XZ)
|
||||
/// |> startProfileAt([0.05, 0.05], %)
|
||||
/// |> line(end = [0, 7])
|
||||
/// |> tangentialArc({
|
||||
@ -252,13 +252,13 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
|
||||
/// }, %)
|
||||
/// |> line(end = [0, 7])
|
||||
///
|
||||
/// pipeHole = startSketchOn('XY')
|
||||
/// pipeHole = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 1.5,
|
||||
/// )
|
||||
///
|
||||
/// sweepSketch = startSketchOn('XY')
|
||||
/// sweepSketch = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 2,
|
||||
|
@ -154,6 +154,22 @@ impl Args {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn get_kw_arg_opt_typed<T>(
|
||||
&self,
|
||||
label: &str,
|
||||
ty: &RuntimeType,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<Option<T>, KclError>
|
||||
where
|
||||
T: for<'a> FromKclValue<'a>,
|
||||
{
|
||||
if self.kw_args.labeled.get(label).is_none() {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
self.get_kw_arg_typed(label, ty, exec_state).map(Some)
|
||||
}
|
||||
|
||||
/// Get a keyword argument. If not set, returns Err.
|
||||
pub(crate) fn get_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
|
||||
where
|
||||
@ -675,37 +691,6 @@ impl Args {
|
||||
FromArgs::from_args(self, 0)
|
||||
}
|
||||
|
||||
pub(crate) fn get_data_and_sketches<'a, T>(
|
||||
&'a self,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<(T, Vec<Sketch>), KclError>
|
||||
where
|
||||
T: serde::de::DeserializeOwned + FromArgs<'a>,
|
||||
{
|
||||
let data: T = FromArgs::from_args(self, 0)?;
|
||||
let Some(arg1) = self.args.get(1) else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Expected one or more sketches for second argument".to_owned(),
|
||||
source_ranges: vec![self.source_range],
|
||||
}));
|
||||
};
|
||||
let sarg = arg1
|
||||
.value
|
||||
.coerce(&RuntimeType::sketches(), exec_state)
|
||||
.ok_or(KclError::Type(KclErrorDetails {
|
||||
message: format!(
|
||||
"Expected one or more sketches for second argument, found {}",
|
||||
arg1.value.human_friendly_type()
|
||||
),
|
||||
source_ranges: vec![self.source_range],
|
||||
}))?;
|
||||
let sketches = match sarg {
|
||||
KclValue::HomArray { value, .. } => value.iter().map(|v| v.as_sketch().unwrap().clone()).collect(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok((data, sketches))
|
||||
}
|
||||
|
||||
pub(crate) fn get_data_and_sketch_and_tag<'a, T>(
|
||||
&'a self,
|
||||
exec_state: &mut ExecState,
|
||||
@ -1583,50 +1568,6 @@ impl<'a> FromKclValue<'a> for super::sketch::SketchData {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for super::axis_or_reference::AxisAndOrigin2d {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
// Case 1: predefined planes.
|
||||
if let Some(s) = arg.as_str() {
|
||||
return match s {
|
||||
"X" | "x" => Some(Self::X),
|
||||
"Y" | "y" => Some(Self::Y),
|
||||
"-X" | "-x" => Some(Self::NegX),
|
||||
"-Y" | "-y" => Some(Self::NegY),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
// Case 2: custom planes.
|
||||
let obj = arg.as_object()?;
|
||||
let_field_of!(obj, custom, &KclObjectFields);
|
||||
let_field_of!(custom, origin);
|
||||
let_field_of!(custom, axis);
|
||||
Some(Self::Custom { axis, origin })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for super::axis_or_reference::AxisAndOrigin3d {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
// Case 1: predefined planes.
|
||||
if let Some(s) = arg.as_str() {
|
||||
return match s {
|
||||
"X" | "x" => Some(Self::X),
|
||||
"Y" | "y" => Some(Self::Y),
|
||||
"Z" | "z" => Some(Self::Z),
|
||||
"-X" | "-x" => Some(Self::NegX),
|
||||
"-Y" | "-y" => Some(Self::NegY),
|
||||
"-Z" | "-z" => Some(Self::NegZ),
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
// Case 2: custom planes.
|
||||
let obj = arg.as_object()?;
|
||||
let_field_of!(obj, custom, &KclObjectFields);
|
||||
let_field_of!(custom, origin);
|
||||
let_field_of!(custom, axis);
|
||||
Some(Self::Custom { axis, origin })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for super::fillet::EdgeReference {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let id = arg.as_uuid().map(Self::Uuid);
|
||||
@ -1637,25 +1578,27 @@ impl<'a> FromKclValue<'a> for super::fillet::EdgeReference {
|
||||
|
||||
impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis2dOrEdgeReference {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let case1 = super::axis_or_reference::AxisAndOrigin2d::from_kcl_val;
|
||||
let case1 = |arg: &KclValue| {
|
||||
let obj = arg.as_object()?;
|
||||
let_field_of!(obj, direction);
|
||||
let_field_of!(obj, origin);
|
||||
Some(Self::Axis { direction, origin })
|
||||
};
|
||||
let case2 = super::fillet::EdgeReference::from_kcl_val;
|
||||
case1(arg).map(Self::Axis).or_else(|| case2(arg).map(Self::Edge))
|
||||
case1(arg).or_else(|| case2(arg).map(Self::Edge))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis3dOrEdgeReference {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let case1 = super::axis_or_reference::AxisAndOrigin3d::from_kcl_val;
|
||||
let case1 = |arg: &KclValue| {
|
||||
let obj = arg.as_object()?;
|
||||
let_field_of!(obj, direction);
|
||||
let_field_of!(obj, origin);
|
||||
Some(Self::Axis { direction, origin })
|
||||
};
|
||||
let case2 = super::fillet::EdgeReference::from_kcl_val;
|
||||
case1(arg).map(Self::Axis).or_else(|| case2(arg).map(Self::Edge))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> FromKclValue<'a> for super::mirror::Mirror2dData {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let obj = arg.as_object()?;
|
||||
let_field_of!(obj, axis);
|
||||
Some(Self { axis })
|
||||
case1(arg).or_else(|| case2(arg).map(Self::Edge))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,233 +1,21 @@
|
||||
//! Types for referencing an axis or edge.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcmc::length_unit::LengthUnit;
|
||||
use kittycad_modeling_cmds::{self as kcmc};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{errors::KclError, std::fillet::EdgeReference};
|
||||
use crate::std::fillet::EdgeReference;
|
||||
|
||||
/// A 2D axis or tagged edge.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(untagged)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Axis2dOrEdgeReference {
|
||||
/// 2D axis and origin.
|
||||
Axis(AxisAndOrigin2d),
|
||||
Axis { direction: [f64; 2], origin: [f64; 2] },
|
||||
/// Tagged edge.
|
||||
Edge(EdgeReference),
|
||||
}
|
||||
|
||||
/// A 2D axis and origin.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum AxisAndOrigin2d {
|
||||
/// X-axis.
|
||||
#[serde(rename = "X", alias = "x")]
|
||||
X,
|
||||
/// Y-axis.
|
||||
#[serde(rename = "Y", alias = "y")]
|
||||
Y,
|
||||
/// Flip the X-axis.
|
||||
#[serde(rename = "-X", alias = "-x")]
|
||||
NegX,
|
||||
/// Flip the Y-axis.
|
||||
#[serde(rename = "-Y", alias = "-y")]
|
||||
NegY,
|
||||
Custom {
|
||||
/// The axis.
|
||||
axis: [f64; 2],
|
||||
/// The origin.
|
||||
origin: [f64; 2],
|
||||
},
|
||||
}
|
||||
|
||||
impl AxisAndOrigin2d {
|
||||
/// Get the axis and origin.
|
||||
pub fn axis_and_origin(&self) -> Result<(kcmc::shared::Point3d<f64>, kcmc::shared::Point3d<LengthUnit>), KclError> {
|
||||
let (axis, origin) = match self {
|
||||
AxisAndOrigin2d::X => ([1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
|
||||
AxisAndOrigin2d::Y => ([0.0, 1.0, 0.0], [0.0, 0.0, 0.0]),
|
||||
AxisAndOrigin2d::NegX => ([-1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
|
||||
AxisAndOrigin2d::NegY => ([0.0, -1.0, 0.0], [0.0, 0.0, 0.0]),
|
||||
AxisAndOrigin2d::Custom { axis, origin } => ([axis[0], axis[1], 0.0], [origin[0], origin[1], 0.0]),
|
||||
};
|
||||
|
||||
Ok((
|
||||
kcmc::shared::Point3d {
|
||||
x: axis[0],
|
||||
y: axis[1],
|
||||
z: axis[2],
|
||||
},
|
||||
kcmc::shared::Point3d {
|
||||
x: LengthUnit(origin[0]),
|
||||
y: LengthUnit(origin[1]),
|
||||
z: LengthUnit(origin[2]),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// A 3D axis or tagged edge.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(untagged)]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Axis3dOrEdgeReference {
|
||||
/// 3D axis and origin.
|
||||
Axis(AxisAndOrigin3d),
|
||||
Axis { direction: [f64; 3], origin: [f64; 3] },
|
||||
/// Tagged edge.
|
||||
Edge(EdgeReference),
|
||||
}
|
||||
|
||||
/// A 3D axis and origin.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum AxisAndOrigin3d {
|
||||
/// X-axis.
|
||||
#[serde(rename = "X", alias = "x")]
|
||||
X,
|
||||
/// Y-axis.
|
||||
#[serde(rename = "Y", alias = "y")]
|
||||
Y,
|
||||
/// Z-axis.
|
||||
#[serde(rename = "Z", alias = "z")]
|
||||
Z,
|
||||
/// Flip the X-axis.
|
||||
#[serde(rename = "-X", alias = "-x")]
|
||||
NegX,
|
||||
/// Flip the Y-axis.
|
||||
#[serde(rename = "-Y", alias = "-y")]
|
||||
NegY,
|
||||
/// Flip the Z-axis.
|
||||
#[serde(rename = "-Z", alias = "-z")]
|
||||
NegZ,
|
||||
Custom {
|
||||
/// The axis.
|
||||
axis: [f64; 3],
|
||||
/// The origin.
|
||||
origin: [f64; 3],
|
||||
},
|
||||
}
|
||||
|
||||
impl AxisAndOrigin3d {
|
||||
/// Get the axis and origin.
|
||||
pub fn axis_and_origin(&self) -> Result<(kcmc::shared::Point3d<f64>, kcmc::shared::Point3d<LengthUnit>), KclError> {
|
||||
let (axis, origin) = match self {
|
||||
AxisAndOrigin3d::X => ([1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
|
||||
AxisAndOrigin3d::Y => ([0.0, 1.0, 0.0], [0.0, 0.0, 0.0]),
|
||||
AxisAndOrigin3d::Z => ([0.0, 0.0, 1.0], [0.0, 0.0, 0.0]),
|
||||
AxisAndOrigin3d::NegX => ([-1.0, 0.0, 0.0], [0.0, 0.0, 0.0]),
|
||||
AxisAndOrigin3d::NegY => ([0.0, -1.0, 0.0], [0.0, 0.0, 0.0]),
|
||||
AxisAndOrigin3d::NegZ => ([0.0, 0.0, -1.0], [0.0, 0.0, 0.0]),
|
||||
AxisAndOrigin3d::Custom { axis, origin } => {
|
||||
([axis[0], axis[1], axis[2]], [origin[0], origin[1], origin[2]])
|
||||
}
|
||||
};
|
||||
|
||||
Ok((
|
||||
kcmc::shared::Point3d {
|
||||
x: axis[0],
|
||||
y: axis[1],
|
||||
z: axis[2],
|
||||
},
|
||||
kcmc::shared::Point3d {
|
||||
x: LengthUnit(origin[0]),
|
||||
y: LengthUnit(origin[1]),
|
||||
z: LengthUnit(origin[2]),
|
||||
},
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use crate::std::axis_or_reference::{
|
||||
Axis2dOrEdgeReference, Axis3dOrEdgeReference, AxisAndOrigin2d, AxisAndOrigin3d,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_revolve_axis_2d() {
|
||||
let data = Axis2dOrEdgeReference::Axis(AxisAndOrigin2d::X);
|
||||
let mut str_json = serde_json::to_string(&data).unwrap();
|
||||
assert_eq!(str_json, "\"X\"");
|
||||
|
||||
str_json = "\"Y\"".to_string();
|
||||
let data: Axis2dOrEdgeReference = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(data, Axis2dOrEdgeReference::Axis(AxisAndOrigin2d::Y));
|
||||
|
||||
str_json = "\"-Y\"".to_string();
|
||||
let data: Axis2dOrEdgeReference = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(data, Axis2dOrEdgeReference::Axis(AxisAndOrigin2d::NegY));
|
||||
|
||||
str_json = "\"-x\"".to_string();
|
||||
let data: Axis2dOrEdgeReference = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(data, Axis2dOrEdgeReference::Axis(AxisAndOrigin2d::NegX));
|
||||
|
||||
let data = Axis2dOrEdgeReference::Axis(AxisAndOrigin2d::Custom {
|
||||
axis: [0.0, -1.0],
|
||||
origin: [1.0, 0.0],
|
||||
});
|
||||
str_json = serde_json::to_string(&data).unwrap();
|
||||
assert_eq!(str_json, r#"{"custom":{"axis":[0.0,-1.0],"origin":[1.0,0.0]}}"#);
|
||||
|
||||
str_json = r#"{"custom": {"axis": [0,-1], "origin": [1,2.0]}}"#.to_string();
|
||||
let data: Axis2dOrEdgeReference = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(
|
||||
data,
|
||||
Axis2dOrEdgeReference::Axis(AxisAndOrigin2d::Custom {
|
||||
axis: [0.0, -1.0],
|
||||
origin: [1.0, 2.0]
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_revolve_axis_3d() {
|
||||
let data = Axis3dOrEdgeReference::Axis(AxisAndOrigin3d::X);
|
||||
let mut str_json = serde_json::to_string(&data).unwrap();
|
||||
assert_eq!(str_json, "\"X\"");
|
||||
|
||||
str_json = "\"Y\"".to_string();
|
||||
let data: Axis3dOrEdgeReference = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(data, Axis3dOrEdgeReference::Axis(AxisAndOrigin3d::Y));
|
||||
|
||||
str_json = "\"Z\"".to_string();
|
||||
let data: Axis3dOrEdgeReference = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(data, Axis3dOrEdgeReference::Axis(AxisAndOrigin3d::Z));
|
||||
|
||||
str_json = "\"-Y\"".to_string();
|
||||
let data: Axis3dOrEdgeReference = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(data, Axis3dOrEdgeReference::Axis(AxisAndOrigin3d::NegY));
|
||||
|
||||
str_json = "\"-x\"".to_string();
|
||||
let data: Axis3dOrEdgeReference = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(data, Axis3dOrEdgeReference::Axis(AxisAndOrigin3d::NegX));
|
||||
|
||||
str_json = "\"-z\"".to_string();
|
||||
let data: Axis3dOrEdgeReference = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(data, Axis3dOrEdgeReference::Axis(AxisAndOrigin3d::NegZ));
|
||||
|
||||
let data = Axis3dOrEdgeReference::Axis(AxisAndOrigin3d::Custom {
|
||||
axis: [0.0, -1.0, 0.0],
|
||||
origin: [1.0, 0.0, 0.0],
|
||||
});
|
||||
str_json = serde_json::to_string(&data).unwrap();
|
||||
assert_eq!(str_json, r#"{"custom":{"axis":[0.0,-1.0,0.0],"origin":[1.0,0.0,0.0]}}"#);
|
||||
|
||||
str_json = r#"{"custom": {"axis": [0,-1,0], "origin": [1,2.0,0]}}"#.to_string();
|
||||
let data: Axis3dOrEdgeReference = serde_json::from_str(&str_json).unwrap();
|
||||
assert_eq!(
|
||||
data,
|
||||
Axis3dOrEdgeReference::Axis(AxisAndOrigin3d::Custom {
|
||||
axis: [0.0, -1.0, 0.0],
|
||||
origin: [1.0, 2.0, 0.0]
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
//! Standard library helices.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, ModelingCmd};
|
||||
use kittycad_modeling_cmds as kcmc;
|
||||
use kittycad_modeling_cmds::{self as kcmc, shared::Point3d};
|
||||
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
execution::{ExecState, Helix as HelixValue, KclValue, Solid},
|
||||
execution::{
|
||||
types::{PrimitiveType, RuntimeType},
|
||||
ExecState, Helix as HelixValue, KclValue, Solid,
|
||||
},
|
||||
std::{axis_or_reference::Axis3dOrEdgeReference, Args},
|
||||
};
|
||||
|
||||
@ -17,7 +19,14 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
let revolutions = args.get_kw_arg("revolutions")?;
|
||||
let ccw = args.get_kw_arg_opt("ccw")?;
|
||||
let radius = args.get_kw_arg_opt("radius")?;
|
||||
let axis = args.get_kw_arg_opt("axis")?;
|
||||
let axis: Option<Axis3dOrEdgeReference> = args.get_kw_arg_opt_typed(
|
||||
"axis",
|
||||
&RuntimeType::Union(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Edge),
|
||||
RuntimeType::Primitive(PrimitiveType::Axis3d),
|
||||
]),
|
||||
exec_state,
|
||||
)?;
|
||||
let length = args.get_kw_arg_opt("length")?;
|
||||
let cylinder = args.get_kw_arg_opt("cylinder")?;
|
||||
|
||||
@ -84,100 +93,6 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
Ok(KclValue::Helix { value })
|
||||
}
|
||||
|
||||
/// Create a helix.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Create a helix around the Z axis.
|
||||
/// helixPath = helix(
|
||||
/// angleStart = 0,
|
||||
/// ccw = true,
|
||||
/// revolutions = 5,
|
||||
/// length = 10,
|
||||
/// radius = 5,
|
||||
/// axis = 'Z',
|
||||
/// )
|
||||
///
|
||||
///
|
||||
/// // Create a spring by sweeping around the helix path.
|
||||
/// springSketch = startSketchOn('YZ')
|
||||
/// |> circle( center = [0, 0], radius = 0.5)
|
||||
/// |> sweep(path = helixPath)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Create a helix around an edge.
|
||||
/// helper001 = startSketchOn('XZ')
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 10], tag = $edge001)
|
||||
///
|
||||
/// helixPath = helix(
|
||||
/// angleStart = 0,
|
||||
/// ccw = true,
|
||||
/// revolutions = 5,
|
||||
/// length = 10,
|
||||
/// radius = 5,
|
||||
/// axis = edge001,
|
||||
/// )
|
||||
///
|
||||
/// // Create a spring by sweeping around the helix path.
|
||||
/// springSketch = startSketchOn('XY')
|
||||
/// |> circle( center = [0, 0], radius = 0.5 )
|
||||
/// |> sweep(path = helixPath)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Create a helix around a custom axis.
|
||||
/// helixPath = helix(
|
||||
/// angleStart = 0,
|
||||
/// ccw = true,
|
||||
/// revolutions = 5,
|
||||
/// length = 10,
|
||||
/// radius = 5,
|
||||
/// axis = {
|
||||
/// custom = {
|
||||
/// axis = [0, 0, 1.0],
|
||||
/// origin = [0, 0.25, 0]
|
||||
/// }
|
||||
/// }
|
||||
/// )
|
||||
///
|
||||
/// // Create a spring by sweeping around the helix path.
|
||||
/// springSketch = startSketchOn('XY')
|
||||
/// |> circle( center = [0, 0], radius = 1 )
|
||||
/// |> sweep(path = helixPath)
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Create a helix on a cylinder.
|
||||
///
|
||||
/// part001 = startSketchOn('XY')
|
||||
/// |> circle( center= [5, 5], radius= 10 )
|
||||
/// |> extrude(length = 10)
|
||||
///
|
||||
/// helix(
|
||||
/// angleStart = 0,
|
||||
/// ccw = true,
|
||||
/// revolutions = 16,
|
||||
/// cylinder = part001,
|
||||
/// )
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "helix",
|
||||
keywords = true,
|
||||
unlabeled_first = false,
|
||||
args = {
|
||||
revolutions = { docs = "Number of revolutions."},
|
||||
angle_start = { docs = "Start angle (in degrees)."},
|
||||
ccw = { docs = "Is the helix rotation counter clockwise? The default is `false`.", include_in_snippet = false},
|
||||
radius = { docs = "Radius of the helix.", include_in_snippet = true},
|
||||
axis = { docs = "Axis to use for the helix.", include_in_snippet = true},
|
||||
length = { docs = "Length of the helix. This is not necessary if the helix is created around an edge. If not given the length of the edge is used.", include_in_snippet = true},
|
||||
cylinder = { docs = "Cylinder to create the helix on.", include_in_snippet = false},
|
||||
},
|
||||
feature_tree_operation = true,
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_helix(
|
||||
revolutions: f64,
|
||||
@ -221,9 +136,7 @@ async fn inner_helix(
|
||||
.await?;
|
||||
} else if let (Some(axis), Some(radius)) = (axis, radius) {
|
||||
match axis {
|
||||
Axis3dOrEdgeReference::Axis(axis) => {
|
||||
let (axis, origin) = axis.axis_and_origin()?;
|
||||
|
||||
Axis3dOrEdgeReference::Axis { direction, origin } => {
|
||||
// Make sure they gave us a length.
|
||||
let Some(length) = length else {
|
||||
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
|
||||
@ -240,8 +153,16 @@ async fn inner_helix(
|
||||
length: LengthUnit(length),
|
||||
revolutions,
|
||||
start_angle: Angle::from_degrees(angle_start),
|
||||
axis,
|
||||
center: origin,
|
||||
axis: Point3d {
|
||||
x: direction[0],
|
||||
y: direction[1],
|
||||
z: direction[2],
|
||||
},
|
||||
center: Point3d {
|
||||
x: LengthUnit(origin[0]),
|
||||
y: LengthUnit(origin[1]),
|
||||
z: LengthUnit(origin[2]),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
@ -1,109 +1,39 @@
|
||||
//! Standard library mirror.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{each_cmd as mcmd, ModelingCmd};
|
||||
use kittycad_modeling_cmds::{self as kcmc};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use kittycad_modeling_cmds::{self as kcmc, length_unit::LengthUnit, shared::Point3d};
|
||||
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
execution::{ExecState, KclValue, Sketch},
|
||||
execution::{
|
||||
types::{PrimitiveType, RuntimeType},
|
||||
ExecState, KclValue, Sketch,
|
||||
},
|
||||
std::{axis_or_reference::Axis2dOrEdgeReference, Args},
|
||||
};
|
||||
|
||||
/// Data for a mirror.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Mirror2dData {
|
||||
/// Axis to use as mirror.
|
||||
pub axis: Axis2dOrEdgeReference,
|
||||
}
|
||||
|
||||
/// Mirror a sketch.
|
||||
///
|
||||
/// Only works on unclosed sketches for now.
|
||||
pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let (data, sketch_set): (Mirror2dData, Vec<Sketch>) = args.get_data_and_sketches(exec_state)?;
|
||||
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
||||
let axis = args.get_kw_arg_typed(
|
||||
"axis",
|
||||
&RuntimeType::Union(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Edge),
|
||||
RuntimeType::Primitive(PrimitiveType::Axis2d),
|
||||
]),
|
||||
exec_state,
|
||||
)?;
|
||||
|
||||
let sketches = inner_mirror_2d(data, sketch_set, exec_state, args).await?;
|
||||
let sketches = inner_mirror_2d(sketches, axis, exec_state, args).await?;
|
||||
Ok(sketches.into())
|
||||
}
|
||||
|
||||
/// Mirror a sketch.
|
||||
///
|
||||
/// Only works on unclosed sketches for now.
|
||||
///
|
||||
/// Mirror occurs around a local sketch axis rather than a global axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Mirror an un-closed sketch across the Y axis.
|
||||
/// sketch001 = startSketchOn('XZ')
|
||||
/// |> startProfileAt([0, 10], %)
|
||||
/// |> line(end = [15, 0])
|
||||
/// |> line(end = [-7, -3])
|
||||
/// |> line(end = [9, -1])
|
||||
/// |> line(end = [-8, -5])
|
||||
/// |> line(end = [9, -3])
|
||||
/// |> line(end = [-8, -3])
|
||||
/// |> line(end = [9, -1])
|
||||
/// |> line(end = [-19, -0])
|
||||
/// |> mirror2d({axis = 'Y'}, %)
|
||||
///
|
||||
/// example = extrude(sketch001, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Mirror a un-closed sketch across the Y axis.
|
||||
/// sketch001 = startSketchOn('XZ')
|
||||
/// |> startProfileAt([0, 8.5], %)
|
||||
/// |> line(end = [20, -8.5])
|
||||
/// |> line(end = [-20, -8.5])
|
||||
/// |> mirror2d({axis = 'Y'}, %)
|
||||
///
|
||||
/// example = extrude(sketch001, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Mirror a un-closed sketch across an edge.
|
||||
/// helper001 = startSketchOn('XZ')
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 10], tag = $edge001)
|
||||
///
|
||||
/// sketch001 = startSketchOn('XZ')
|
||||
/// |> startProfileAt([0, 8.5], %)
|
||||
/// |> line(end = [20, -8.5])
|
||||
/// |> line(end = [-20, -8.5])
|
||||
/// |> mirror2d({axis = edge001}, %)
|
||||
///
|
||||
/// // example = extrude(sketch001, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Mirror an un-closed sketch across a custom axis.
|
||||
/// sketch001 = startSketchOn('XZ')
|
||||
/// |> startProfileAt([0, 8.5], %)
|
||||
/// |> line(end = [20, -8.5])
|
||||
/// |> line(end = [-20, -8.5])
|
||||
/// |> mirror2d({
|
||||
/// axis = {
|
||||
/// custom = {
|
||||
/// axis = [0.0, 1.0],
|
||||
/// origin = [0.0, 0.0]
|
||||
/// }
|
||||
/// }
|
||||
/// }, %)
|
||||
///
|
||||
/// example = extrude(sketch001, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "mirror2d",
|
||||
}]
|
||||
async fn inner_mirror_2d(
|
||||
data: Mirror2dData,
|
||||
sketches: Vec<Sketch>,
|
||||
axis: Axis2dOrEdgeReference,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Vec<Sketch>, KclError> {
|
||||
@ -113,16 +43,22 @@ async fn inner_mirror_2d(
|
||||
return Ok(starting_sketches);
|
||||
}
|
||||
|
||||
match data.axis {
|
||||
Axis2dOrEdgeReference::Axis(axis) => {
|
||||
let (axis, origin) = axis.axis_and_origin()?;
|
||||
|
||||
match axis {
|
||||
Axis2dOrEdgeReference::Axis { direction, origin } => {
|
||||
args.batch_modeling_cmd(
|
||||
exec_state.next_uuid(),
|
||||
ModelingCmd::from(mcmd::EntityMirror {
|
||||
ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
|
||||
axis,
|
||||
point: origin,
|
||||
axis: Point3d {
|
||||
x: direction[0],
|
||||
y: direction[1],
|
||||
z: 0.0,
|
||||
},
|
||||
point: Point3d {
|
||||
x: LengthUnit(origin[0]),
|
||||
y: LengthUnit(origin[1]),
|
||||
z: LengthUnit(0.0),
|
||||
},
|
||||
}),
|
||||
)
|
||||
.await?;
|
||||
|
@ -26,7 +26,6 @@ pub mod shell;
|
||||
pub mod sketch;
|
||||
pub mod sweep;
|
||||
pub mod transform;
|
||||
pub mod types;
|
||||
pub mod units;
|
||||
pub mod utils;
|
||||
|
||||
@ -96,7 +95,6 @@ lazy_static! {
|
||||
Box::new(crate::std::sketch::TangentialArcToRelative),
|
||||
Box::new(crate::std::sketch::BezierCurve),
|
||||
Box::new(crate::std::sketch::Hole),
|
||||
Box::new(crate::std::mirror::Mirror2D),
|
||||
Box::new(crate::std::patterns::PatternLinear2D),
|
||||
Box::new(crate::std::patterns::PatternLinear3D),
|
||||
Box::new(crate::std::patterns::PatternCircular2D),
|
||||
@ -113,10 +111,8 @@ lazy_static! {
|
||||
Box::new(crate::std::edge::GetNextAdjacentEdge),
|
||||
Box::new(crate::std::edge::GetPreviousAdjacentEdge),
|
||||
Box::new(crate::std::edge::GetCommonEdge),
|
||||
Box::new(crate::std::helix::Helix),
|
||||
Box::new(crate::std::shell::Shell),
|
||||
Box::new(crate::std::shell::Hollow),
|
||||
Box::new(crate::std::revolve::Revolve),
|
||||
Box::new(crate::std::sweep::Sweep),
|
||||
Box::new(crate::std::loft::Loft),
|
||||
Box::new(crate::std::planes::OffsetPlane),
|
||||
@ -177,6 +173,7 @@ pub fn get_stdlib_fn(name: &str) -> Option<Box<dyn StdLibFn>> {
|
||||
pub struct StdFnProps {
|
||||
pub name: String,
|
||||
pub deprecated: bool,
|
||||
pub include_in_feature_tree: bool,
|
||||
}
|
||||
|
||||
impl StdFnProps {
|
||||
@ -184,8 +181,14 @@ impl StdFnProps {
|
||||
Self {
|
||||
name: name.to_owned(),
|
||||
deprecated: false,
|
||||
include_in_feature_tree: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn include_in_feature_tree(mut self) -> Self {
|
||||
self.include_in_feature_tree = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProps) {
|
||||
@ -206,6 +209,18 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|
||||
|e, a| Box::pin(crate::std::shapes::circle(e, a)),
|
||||
StdFnProps::default("std::sketch::circle"),
|
||||
),
|
||||
("prelude", "helix") => (
|
||||
|e, a| Box::pin(crate::std::helix::helix(e, a)),
|
||||
StdFnProps::default("std::helix").include_in_feature_tree(),
|
||||
),
|
||||
("sketch", "mirror2d") => (
|
||||
|e, a| Box::pin(crate::std::mirror::mirror_2d(e, a)),
|
||||
StdFnProps::default("std::sketch::mirror2d"),
|
||||
),
|
||||
("prelude", "revolve") => (
|
||||
|e, a| Box::pin(crate::std::revolve::revolve(e, a)),
|
||||
StdFnProps::default("std::revolve").include_in_feature_tree(),
|
||||
),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@ -217,6 +232,9 @@ pub(crate) fn std_ty(path: &str, fn_name: &str) -> (PrimitiveType, StdFnProps) {
|
||||
("prelude", "Plane") => (PrimitiveType::Plane, StdFnProps::default("std::Plane")),
|
||||
("prelude", "Face") => (PrimitiveType::Face, StdFnProps::default("std::Face")),
|
||||
("prelude", "Helix") => (PrimitiveType::Helix, StdFnProps::default("std::Helix")),
|
||||
("prelude", "Edge") => (PrimitiveType::Edge, StdFnProps::default("std::Edge")),
|
||||
("prelude", "Axis2d") => (PrimitiveType::Axis2d, StdFnProps::default("std::Axis2d")),
|
||||
("prelude", "Axis3d") => (PrimitiveType::Axis3d, StdFnProps::default("std::Axis3d")),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
//! Standard library revolution surfaces.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, ModelingCmd};
|
||||
use kittycad_modeling_cmds::{self as kcmc};
|
||||
use kittycad_modeling_cmds::{self as kcmc, shared::Point3d};
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{types::RuntimeType, ExecState, KclValue, Sketch, Solid},
|
||||
execution::{
|
||||
types::{PrimitiveType, RuntimeType},
|
||||
ExecState, KclValue, Sketch, Solid,
|
||||
},
|
||||
parsing::ast::types::TagNode,
|
||||
std::{axis_or_reference::Axis2dOrEdgeReference, extrude::do_post_extrude, fillet::default_tolerance, Args},
|
||||
};
|
||||
@ -15,7 +17,14 @@ use crate::{
|
||||
/// Revolve a sketch or set of sketches around an axis.
|
||||
pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
||||
let axis: Axis2dOrEdgeReference = args.get_kw_arg("axis")?;
|
||||
let axis = args.get_kw_arg_typed(
|
||||
"axis",
|
||||
&RuntimeType::Union(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Edge),
|
||||
RuntimeType::Primitive(PrimitiveType::Axis2d),
|
||||
]),
|
||||
exec_state,
|
||||
)?;
|
||||
let angle = args.get_kw_arg_opt("angle")?;
|
||||
let tolerance = args.get_kw_arg_opt("tolerance")?;
|
||||
let tag_start = args.get_kw_arg_opt("tagStart")?;
|
||||
@ -25,215 +34,6 @@ pub async fn revolve(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
Ok(value.into())
|
||||
}
|
||||
|
||||
/// Rotate a sketch around some provided axis, creating a solid from its extent.
|
||||
///
|
||||
/// This, like extrude, is able to create a 3-dimensional solid from a
|
||||
/// 2-dimensional sketch. However, unlike extrude, this creates a solid
|
||||
/// by using the extent of the sketch as its revolved around an axis rather
|
||||
/// than using the extent of the sketch linearly translated through a third
|
||||
/// dimension.
|
||||
///
|
||||
/// Revolve occurs around a local sketch axis rather than a global axis.
|
||||
///
|
||||
/// You can provide more than one sketch to revolve, and they will all be
|
||||
/// revolved around the same axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// part001 = 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()
|
||||
/// |> revolve(axis = 'y') // default angle is 360
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // A donut shape.
|
||||
/// sketch001 = startSketchOn('XY')
|
||||
/// |> circle( center = [15, 0], radius = 5 )
|
||||
/// |> revolve(
|
||||
/// angle = 360,
|
||||
/// axis = 'y'
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// part001 = 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()
|
||||
/// |> revolve(axis = 'y', angle = 180)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// part001 = 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()
|
||||
/// |> revolve(axis = 'y', angle = 180)
|
||||
///
|
||||
/// part002 = startSketchOn(part001, 'end')
|
||||
/// |> startProfileAt([4.5, -5], %)
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [0, -5])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// box = startSketchOn('XY')
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 20])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, -20])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 20)
|
||||
///
|
||||
/// sketch001 = startSketchOn(box, "END")
|
||||
/// |> circle( center = [10,10], radius = 4 )
|
||||
/// |> revolve(
|
||||
/// angle = -90,
|
||||
/// axis = 'y'
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// box = startSketchOn('XY')
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 20])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, -20], tag = $revolveAxis)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 20)
|
||||
///
|
||||
/// sketch001 = startSketchOn(box, "END")
|
||||
/// |> circle( center = [10,10], radius = 4 )
|
||||
/// |> revolve(
|
||||
/// angle = 90,
|
||||
/// axis = getOppositeEdge(revolveAxis)
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// box = startSketchOn('XY')
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 20])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, -20], tag = $revolveAxis)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 20)
|
||||
///
|
||||
/// sketch001 = startSketchOn(box, "END")
|
||||
/// |> circle( center = [10,10], radius = 4 )
|
||||
/// |> revolve(
|
||||
/// angle = 90,
|
||||
/// axis = getOppositeEdge(revolveAxis),
|
||||
/// tolerance = 0.0001
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// sketch001 = startSketchOn('XY')
|
||||
/// |> startProfileAt([10, 0], %)
|
||||
/// |> line(end = [5, -5])
|
||||
/// |> line(end = [5, 5])
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// part001 = revolve(
|
||||
/// sketch001,
|
||||
/// axis = {
|
||||
/// custom: {
|
||||
/// axis = [0.0, 1.0],
|
||||
/// origin: [0.0, 0.0]
|
||||
/// }
|
||||
/// }
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Revolve two sketches around the same axis.
|
||||
///
|
||||
/// sketch001 = startSketchOn('XY')
|
||||
/// profile001 = startProfileAt([4, 8], sketch001)
|
||||
/// |> xLine(length = 3)
|
||||
/// |> yLine(length = -3)
|
||||
/// |> xLine(length = -3)
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// profile002 = startProfileAt([-5, 8], sketch001)
|
||||
/// |> xLine(length = 3)
|
||||
/// |> yLine(length = -3)
|
||||
/// |> xLine(length = -3)
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// revolve(
|
||||
/// [profile001, profile002],
|
||||
/// axis = "X",
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Revolve around a path that has not been extruded.
|
||||
///
|
||||
/// profile001 = startSketchOn('XY')
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 20], tag = $revolveAxis)
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, -20])
|
||||
/// |> close(%)
|
||||
///
|
||||
/// sketch001 = startSketchOn('XY')
|
||||
/// |> circle(center = [-10, 10], radius = 4)
|
||||
/// |> revolve(angle = 90, axis = revolveAxis)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Revolve around a path that has not been extruded or closed.
|
||||
///
|
||||
/// profile001 = startSketchOn('XY')
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 20], tag = $revolveAxis)
|
||||
/// |> line(end = [20, 0])
|
||||
///
|
||||
/// sketch001 = startSketchOn('XY')
|
||||
/// |> circle(center = [-10, 10], radius = 4)
|
||||
/// |> revolve(angle = 90, axis = revolveAxis)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "revolve",
|
||||
feature_tree_operation = true,
|
||||
keywords = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketches = { docs = "The sketch or set of sketches that should be revolved" },
|
||||
axis = { docs = "Axis of revolution." },
|
||||
angle = { docs = "Angle to revolve (in degrees). Default is 360." },
|
||||
tolerance = { docs = "Tolerance for the revolve operation." },
|
||||
tag_start = { docs = "A named tag for the face at the start of the revolve, i.e. the original sketch" },
|
||||
tag_end = { docs = "A named tag for the face at the end of the revolve" },
|
||||
}
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_revolve(
|
||||
sketches: Vec<Sketch>,
|
||||
@ -264,15 +64,22 @@ async fn inner_revolve(
|
||||
let id = exec_state.next_uuid();
|
||||
|
||||
match &axis {
|
||||
Axis2dOrEdgeReference::Axis(axis) => {
|
||||
let (axis, origin) = axis.axis_and_origin()?;
|
||||
Axis2dOrEdgeReference::Axis { direction, origin } => {
|
||||
args.batch_modeling_cmd(
|
||||
id,
|
||||
ModelingCmd::from(mcmd::Revolve {
|
||||
angle,
|
||||
target: sketch.id.into(),
|
||||
axis,
|
||||
origin,
|
||||
axis: Point3d {
|
||||
x: direction[0],
|
||||
y: direction[1],
|
||||
z: 0.0,
|
||||
},
|
||||
origin: Point3d {
|
||||
x: LengthUnit(origin[0]),
|
||||
y: LengthUnit(origin[1]),
|
||||
z: LengthUnit(0.0),
|
||||
},
|
||||
tolerance: LengthUnit(tolerance.unwrap_or(default_tolerance(&args.ctx.settings.units))),
|
||||
axis_is_2d: true,
|
||||
}),
|
||||
|
@ -1021,7 +1021,7 @@ pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<K
|
||||
/// |> line(end = [-2, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = revolve(exampleSketch, axis = 'y', angle = 180)
|
||||
/// example = revolve(exampleSketch, axis = Y, angle = 180)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, 'end')
|
||||
/// |> startProfileAt([4.5, -5], %)
|
||||
@ -1047,7 +1047,7 @@ pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<K
|
||||
/// |> line(end = [-2, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = revolve(exampleSketch, axis = 'y', angle = 180, tagEnd = $end01)
|
||||
/// example = revolve(exampleSketch, axis = Y, angle = 180, tagEnd = $end01)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, end01)
|
||||
/// |> startProfileAt([4.5, -5], %)
|
||||
|
@ -54,7 +54,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
/// // Create a pipe using a sweep.
|
||||
///
|
||||
/// // Create a path for the sweep.
|
||||
/// sweepPath = startSketchOn('XZ')
|
||||
/// sweepPath = startSketchOn(XZ)
|
||||
/// |> startProfileAt([0.05, 0.05], %)
|
||||
/// |> line(end = [0, 7])
|
||||
/// |> tangentialArc({
|
||||
@ -69,13 +69,13 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
/// |> line(end = [0, 7])
|
||||
///
|
||||
/// // Create a hole for the pipe.
|
||||
/// pipeHole = startSketchOn('XY')
|
||||
/// pipeHole = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 1.5,
|
||||
/// )
|
||||
///
|
||||
/// sweepSketch = startSketchOn('XY')
|
||||
/// sweepSketch = startSketchOn(XY)
|
||||
/// |> circle(
|
||||
/// center = [0, 0],
|
||||
/// radius = 2,
|
||||
@ -94,12 +94,12 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
/// revolutions = 4,
|
||||
/// length = 10,
|
||||
/// radius = 5,
|
||||
/// axis = 'Z',
|
||||
/// axis = Z,
|
||||
/// )
|
||||
///
|
||||
///
|
||||
/// // Create a spring by sweeping around the helix path.
|
||||
/// springSketch = startSketchOn('YZ')
|
||||
/// springSketch = startSketchOn(YZ)
|
||||
/// |> circle( center = [0, 0], radius = 1)
|
||||
/// |> sweep(path = helixPath)
|
||||
/// ```
|
||||
@ -107,7 +107,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
/// ```
|
||||
/// // Sweep two sketches along the same path.
|
||||
///
|
||||
/// sketch001 = startSketchOn('XY')
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// rectangleSketch = startProfileAt([-200, 23.86], sketch001)
|
||||
/// |> angledLine([0, 73.47], %, $rectangleSegmentA001)
|
||||
/// |> angledLine([
|
||||
@ -123,7 +123,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
///
|
||||
/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
|
||||
///
|
||||
/// sketch002 = startSketchOn('YZ')
|
||||
/// sketch002 = startSketchOn(YZ)
|
||||
/// sweepPath = startProfileAt([0, 0], sketch002)
|
||||
/// |> yLine(length = 231.81)
|
||||
/// |> tangentialArc({
|
||||
@ -137,7 +137,7 @@ pub async fn sweep(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
/// ```
|
||||
/// // Sectionally sweep one sketch along the path
|
||||
///
|
||||
/// sketch001 = startSketchOn('XY')
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// circleSketch = circle(sketch001, center = [200, -30.29], radius = 32.63)
|
||||
///
|
||||
/// sketch002 = startSketchOn('YZ')
|
||||
|
@ -1 +0,0 @@
|
||||
|
@ -1235,7 +1235,7 @@ insideRevolve = startSketchOn(XZ)
|
||||
|> line([0, -thickness], %)
|
||||
|> line([-overHangLength, 0], %)
|
||||
|> close()
|
||||
|> revolve({ axis: 'y' }, %)
|
||||
|> revolve({ axis = Y }, %)
|
||||
|
||||
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
|
||||
sphere = startSketchOn(XZ)
|
||||
@ -1250,7 +1250,7 @@ sphere = startSketchOn(XZ)
|
||||
radius = sphereDia / 2 - 0.05
|
||||
}, %)
|
||||
|> close()
|
||||
|> revolve({ axis: 'x' }, %)
|
||||
|> revolve({ axis = X }, %)
|
||||
|> patternCircular3d(
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
@ -1274,7 +1274,7 @@ outsideRevolve = startSketchOn(XZ)
|
||||
|> line([0, thickness], %)
|
||||
|> line([overHangLength - thickness, 0], %)
|
||||
|> close()
|
||||
|> revolve({ axis: 'y' }, %)"#;
|
||||
|> revolve({ axis = Y }, %)"#;
|
||||
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
|
||||
|
||||
let recasted = program.recast(&Default::default(), 0);
|
||||
@ -1301,7 +1301,7 @@ insideRevolve = startSketchOn(XZ)
|
||||
|> line([0, -thickness], %)
|
||||
|> line([-overHangLength, 0], %)
|
||||
|> close()
|
||||
|> revolve({ axis = 'y' }, %)
|
||||
|> revolve({ axis = Y }, %)
|
||||
|
||||
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
|
||||
sphere = startSketchOn(XZ)
|
||||
@ -1316,7 +1316,7 @@ sphere = startSketchOn(XZ)
|
||||
radius = sphereDia / 2 - 0.05
|
||||
}, %)
|
||||
|> close()
|
||||
|> revolve({ axis = 'x' }, %)
|
||||
|> revolve({ axis = X }, %)
|
||||
|> patternCircular3d(
|
||||
axis = [0, 0, 1],
|
||||
center = [0, 0, 0],
|
||||
@ -1340,7 +1340,7 @@ outsideRevolve = startSketchOn(XZ)
|
||||
|> line([0, thickness], %)
|
||||
|> line([overHangLength - thickness, 0], %)
|
||||
|> close()
|
||||
|> revolve({ axis = 'y' }, %)
|
||||
|> revolve({ axis = Y }, %)
|
||||
"#
|
||||
);
|
||||
}
|
||||
@ -1613,9 +1613,9 @@ fn bracketSketch = (w, d, t) => {
|
||||
s = startSketchOn({
|
||||
plane: {
|
||||
origin: { x = 0, y = length / 2 + thk, z = 0 },
|
||||
x_axis: { x = 1, y = 0, z = 0 },
|
||||
y_axis: { x = 0, y = 0, z = 1 },
|
||||
z_axis: { x = 0, y = 1, z = 0 }
|
||||
x_axis = { x = 1, y = 0, z = 0 },
|
||||
y_axis = { x = 0, y = 0, z = 1 },
|
||||
z_axis = { x = 0, y = 1, z = 0 }
|
||||
}
|
||||
})
|
||||
|> startProfileAt([-w / 2 - t, d + t], %)
|
||||
@ -1645,9 +1645,9 @@ bracket_body = bracketSketch(width, depth, thk)
|
||||
tabs_r = startSketchOn({
|
||||
plane: {
|
||||
origin: { x = 0, y = 0, z = depth + thk },
|
||||
x_axis: { x = 1, y = 0, z = 0 },
|
||||
y_axis: { x = 0, y = 1, z = 0 },
|
||||
z_axis: { x = 0, y = 0, z = 1 }
|
||||
x_axis = { x = 1, y = 0, z = 0 },
|
||||
y_axis = { x = 0, y = 1, z = 0 },
|
||||
z_axis = { x = 0, y = 0, z = 1 }
|
||||
}
|
||||
})
|
||||
|> startProfileAt([width / 2 + thk, length / 2 + thk], %)
|
||||
@ -2528,9 +2528,9 @@ fn f() {
|
||||
sketch002 = startSketchOn({
|
||||
plane: {
|
||||
origin: { x = 1, y = 2, z = 3 },
|
||||
x_axis: { x = 4, y = 5, z = 6 },
|
||||
y_axis: { x = 7, y = 8, z = 9 },
|
||||
z_axis: { x = 10, y = 11, z = 12 }
|
||||
x_axis = { x = 4, y = 5, z = 6 },
|
||||
y_axis = { x = 7, y = 8, z = 9 },
|
||||
z_axis = { x = 10, y = 11, z = 12 }
|
||||
}
|
||||
})
|
||||
"#;
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
export import * from "std::math"
|
||||
export import * from "std::sketch"
|
||||
export import "std::turns"
|
||||
|
||||
/// A number
|
||||
///
|
||||
@ -233,6 +234,10 @@ export type Face
|
||||
@(impl = std_rust)
|
||||
export type Helix
|
||||
|
||||
/// The edge of a solid.
|
||||
@(impl = std_rust)
|
||||
export type Edge
|
||||
|
||||
/// A point in two dimensional space.
|
||||
///
|
||||
/// `Point2d` is an alias for a two-element array of [number](/docs/kcl/types/number)s. To write a value
|
||||
@ -245,11 +250,6 @@ export type Point2d = [number; 2]
|
||||
/// with type `Point3d`, use an array, e.g., `[0, 0, 0]` or `[5.0, 3.14, 6.8]`.
|
||||
export type Point3d = [number; 3]
|
||||
|
||||
export ZERO = 0
|
||||
export QUARTER_TURN = 90deg
|
||||
export HALF_TURN = 180deg
|
||||
export THREE_QUARTER_TURN = 270deg
|
||||
|
||||
export XY = {
|
||||
origin = { x = 0, y = 0, z = 0 },
|
||||
xAxis = { x = 1, y = 0, z = 0 },
|
||||
@ -270,3 +270,330 @@ export YZ = {
|
||||
yAxis = { x = 0, y = 0, z = 1 },
|
||||
zAxis = { x = 1, y = 0, z = 0 },
|
||||
}: Plane
|
||||
|
||||
/// An infinte line in 2d space.
|
||||
@(impl = std_rust)
|
||||
export type Axis2d
|
||||
|
||||
/// An infinte line in 3d space.
|
||||
@(impl = std_rust)
|
||||
export type Axis3d
|
||||
|
||||
export X = {
|
||||
origin = [0, 0, 0],
|
||||
direction = [1, 0, 0],
|
||||
}: Axis3d
|
||||
|
||||
export Y = {
|
||||
origin = [0, 0, 0],
|
||||
direction = [0, 1, 0],
|
||||
}: Axis3d
|
||||
|
||||
export Z = {
|
||||
origin = [0, 0, 0],
|
||||
direction = [0, 0, 1],
|
||||
}: Axis3d
|
||||
|
||||
/// Create a helix.
|
||||
///
|
||||
/// ```
|
||||
/// // Create a helix around the Z axis.
|
||||
/// helixPath = helix(
|
||||
/// angleStart = 0,
|
||||
/// ccw = true,
|
||||
/// revolutions = 5,
|
||||
/// length = 10,
|
||||
/// radius = 5,
|
||||
/// axis = Z,
|
||||
/// )
|
||||
///
|
||||
/// // Create a spring by sweeping around the helix path.
|
||||
/// springSketch = startSketchOn(YZ)
|
||||
/// |> circle( center = [0, 0], radius = 0.5)
|
||||
/// |> sweep(path = helixPath)
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Create a helix around an edge.
|
||||
/// helper001 = startSketchOn(XZ)
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 10], tag = $edge001)
|
||||
///
|
||||
/// helixPath = helix(
|
||||
/// angleStart = 0,
|
||||
/// ccw = true,
|
||||
/// revolutions = 5,
|
||||
/// length = 10,
|
||||
/// radius = 5,
|
||||
/// axis = edge001,
|
||||
/// )
|
||||
///
|
||||
/// // Create a spring by sweeping around the helix path.
|
||||
/// springSketch = startSketchOn(XY)
|
||||
/// |> circle( center = [0, 0], radius = 0.5 )
|
||||
/// |> sweep(path = helixPath)
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Create a helix around a custom axis.
|
||||
/// helixPath = helix(
|
||||
/// angleStart = 0,
|
||||
/// ccw = true,
|
||||
/// revolutions = 5,
|
||||
/// length = 10,
|
||||
/// radius = 5,
|
||||
/// axis = {
|
||||
/// direction = [0, 0, 1.0],
|
||||
/// origin = [0, 0.25, 0]
|
||||
/// }
|
||||
/// )
|
||||
///
|
||||
/// // Create a spring by sweeping around the helix path.
|
||||
/// springSketch = startSketchOn(XY)
|
||||
/// |> circle( center = [0, 0], radius = 1 )
|
||||
/// |> sweep(path = helixPath)
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Create a helix on a cylinder.
|
||||
///
|
||||
/// part001 = startSketchOn(XY)
|
||||
/// |> circle( center= [5, 5], radius= 10 )
|
||||
/// |> extrude(length = 10)
|
||||
///
|
||||
/// helix(
|
||||
/// angleStart = 0,
|
||||
/// ccw = true,
|
||||
/// revolutions = 16,
|
||||
/// cylinder = part001,
|
||||
/// )
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn helix(
|
||||
/// Number of revolutions.
|
||||
revolutions: number(_),
|
||||
/// Start angle (in degrees).
|
||||
angleStart: number(deg),
|
||||
/// Is the helix rotation counter clockwise? The default is `false`.
|
||||
ccw?: bool,
|
||||
/// Radius of the helix.
|
||||
@(include_in_snippet = true)
|
||||
radius?: number(mm),
|
||||
/// Axis to use for the helix.
|
||||
@(include_in_snippet = true)
|
||||
axis?: Axis3d | Edge,
|
||||
/// Length of the helix. This is not necessary if the helix is created around an edge. If not given the length of the edge is used.
|
||||
@(include_in_snippet = true)
|
||||
length?: number(mm),
|
||||
/// Cylinder to create the helix on.
|
||||
cylinder?: Solid,
|
||||
): Helix {}
|
||||
|
||||
/// Rotate a sketch around some provided axis, creating a solid from its extent.
|
||||
///
|
||||
/// This, like extrude, is able to create a 3-dimensional solid from a
|
||||
/// 2-dimensional sketch. However, unlike extrude, this creates a solid
|
||||
/// by using the extent of the sketch as its revolved around an axis rather
|
||||
/// than using the extent of the sketch linearly translated through a third
|
||||
/// dimension.
|
||||
///
|
||||
/// Revolve occurs around a local sketch axis rather than a global axis.
|
||||
///
|
||||
/// You can provide more than one sketch to revolve, and they will all be
|
||||
/// revolved around the same axis.
|
||||
///
|
||||
/// ```
|
||||
/// part001 = 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()
|
||||
/// |> revolve(axis = Y) // default angle is 360
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // A donut shape.
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> circle( center = [15, 0], radius = 5 )
|
||||
/// |> revolve(
|
||||
/// angle = 360,
|
||||
/// axis = Y,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// part001 = 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()
|
||||
/// |> revolve(axis = Y, angle = 180)
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// part001 = 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()
|
||||
/// |> revolve(axis = Y, angle = 180)
|
||||
///
|
||||
/// part002 = startSketchOn(part001, 'end')
|
||||
/// |> startProfileAt([4.5, -5], %)
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [0, -5])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// box = startSketchOn(XY)
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 20])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, -20])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 20)
|
||||
///
|
||||
/// sketch001 = startSketchOn(box, "END")
|
||||
/// |> circle( center = [10,10], radius = 4 )
|
||||
/// |> revolve(
|
||||
/// angle = -90,
|
||||
/// axis = Y
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// box = startSketchOn(XY)
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 20])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, -20], tag = $revolveAxis)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 20)
|
||||
///
|
||||
/// sketch001 = startSketchOn(box, "END")
|
||||
/// |> circle( center = [10,10], radius = 4 )
|
||||
/// |> revolve(
|
||||
/// angle = 90,
|
||||
/// axis = getOppositeEdge(revolveAxis)
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// box = startSketchOn(XY)
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 20])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, -20], tag = $revolveAxis)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 20)
|
||||
///
|
||||
/// sketch001 = startSketchOn(box, "END")
|
||||
/// |> circle( center = [10,10], radius = 4 )
|
||||
/// |> revolve(
|
||||
/// angle = 90,
|
||||
/// axis = getOppositeEdge(revolveAxis),
|
||||
/// tolerance = 0.0001
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfileAt([10, 0], %)
|
||||
/// |> line(end = [5, -5])
|
||||
/// |> line(end = [5, 5])
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// part001 = revolve(
|
||||
/// sketch001,
|
||||
/// axis = {
|
||||
/// direction = [0.0, 1.0],
|
||||
/// origin: [0.0, 0.0]
|
||||
/// }
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Revolve two sketches around the same axis.
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// profile001 = startProfileAt([4, 8], sketch001)
|
||||
/// |> xLine(length = 3)
|
||||
/// |> yLine(length = -3)
|
||||
/// |> xLine(length = -3)
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// profile002 = startProfileAt([-5, 8], sketch001)
|
||||
/// |> xLine(length = 3)
|
||||
/// |> yLine(length = -3)
|
||||
/// |> xLine(length = -3)
|
||||
/// |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
/// |> close()
|
||||
///
|
||||
/// revolve(
|
||||
/// [profile001, profile002],
|
||||
/// axis = X,
|
||||
/// )
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Revolve around a path that has not been extruded.
|
||||
///
|
||||
/// profile001 = startSketchOn(XY)
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 20], tag = $revolveAxis)
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, -20])
|
||||
/// |> close(%)
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> circle(center = [-10, 10], radius = 4)
|
||||
/// |> revolve(angle = 90, axis = revolveAxis)
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Revolve around a path that has not been extruded or closed.
|
||||
///
|
||||
/// profile001 = startSketchOn(XY)
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 20], tag = $revolveAxis)
|
||||
/// |> line(end = [20, 0])
|
||||
///
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> circle(center = [-10, 10], radius = 4)
|
||||
/// |> revolve(angle = 90, axis = revolveAxis)
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn revolve(
|
||||
/// The sketch or set of sketches that should be revolved
|
||||
@sketches: [Sketch; 1+],
|
||||
/// Axis of revolution.
|
||||
axis: Axis2d | Edge,
|
||||
/// Angle to revolve (in degrees). Default is 360.
|
||||
angle?: number(deg),
|
||||
/// Tolerance for the revolve operation.
|
||||
tolerance?: number(mm),
|
||||
/// A named tag for the face at the start of the revolve, i.e. the original sketch.
|
||||
tagStart?: tag,
|
||||
/// A named tag for the face at the end of the revolve.
|
||||
tagEnd?: tag,
|
||||
): Solid {}
|
||||
|
@ -32,3 +32,74 @@ export fn circle(
|
||||
/// Create a new tag which refers to this circle.
|
||||
tag?: tag,
|
||||
): Sketch {}
|
||||
|
||||
/// Mirror a sketch.
|
||||
///
|
||||
/// Only works on unclosed sketches for now.
|
||||
///
|
||||
/// Mirror occurs around a local sketch axis rather than a global axis.
|
||||
///
|
||||
/// ```
|
||||
/// // Mirror an un-closed sketch across the Y axis.
|
||||
/// sketch001 = startSketchOn(XZ)
|
||||
/// |> startProfileAt([0, 10], %)
|
||||
/// |> line(end = [15, 0])
|
||||
/// |> line(end = [-7, -3])
|
||||
/// |> line(end = [9, -1])
|
||||
/// |> line(end = [-8, -5])
|
||||
/// |> line(end = [9, -3])
|
||||
/// |> line(end = [-8, -3])
|
||||
/// |> line(end = [9, -1])
|
||||
/// |> line(end = [-19, -0])
|
||||
/// |> mirror2d(axis = Y)
|
||||
///
|
||||
/// example = extrude(sketch001, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Mirror a un-closed sketch across the Y axis.
|
||||
/// sketch001 = startSketchOn(XZ)
|
||||
/// |> startProfileAt([0, 8.5], %)
|
||||
/// |> line(end = [20, -8.5])
|
||||
/// |> line(end = [-20, -8.5])
|
||||
/// |> mirror2d(axis = Y)
|
||||
///
|
||||
/// example = extrude(sketch001, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Mirror a un-closed sketch across an edge.
|
||||
/// helper001 = startSketchOn(XZ)
|
||||
/// |> startProfileAt([0, 0], %)
|
||||
/// |> line(end = [0, 10], tag = $edge001)
|
||||
///
|
||||
/// sketch001 = startSketchOn(XZ)
|
||||
/// |> startProfileAt([0, 8.5], %)
|
||||
/// |> line(end = [20, -8.5])
|
||||
/// |> line(end = [-20, -8.5])
|
||||
/// |> mirror2d(axis = edge001)
|
||||
///
|
||||
/// // example = extrude(sketch001, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```
|
||||
/// // Mirror an un-closed sketch across a custom axis.
|
||||
/// sketch001 = startSketchOn(XZ)
|
||||
/// |> startProfileAt([0, 8.5], %)
|
||||
/// |> line(end = [20, -8.5])
|
||||
/// |> line(end = [-20, -8.5])
|
||||
/// |> mirror2d(
|
||||
/// axis = {
|
||||
/// direction = [0.0, 1.0],
|
||||
/// origin = [0.0, 0.0]
|
||||
/// })
|
||||
///
|
||||
/// example = extrude(sketch001, length = 10)
|
||||
/// ```
|
||||
@(impl = std_rust)
|
||||
export fn mirror2d(
|
||||
/// The sketch or sketches to be reflected.
|
||||
@sketches: [Sketch; 1+],
|
||||
/// The axis to refect around.
|
||||
axis: Axis2d | Edge,
|
||||
): Sketch {}
|
||||
|
6
rust/kcl-lib/std/turns.kcl
Normal file
@ -0,0 +1,6 @@
|
||||
@no_std
|
||||
|
||||
export ZERO = 0
|
||||
export QUARTER_TURN = 90deg
|
||||
export HALF_TURN = 180deg
|
||||
export THREE_QUARTER_TURN = 270deg
|
@ -1,25 +1,25 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph path2 [Path]
|
||||
2["Path<br>[76, 113, 4]"]
|
||||
3["Segment<br>[119, 136, 4]"]
|
||||
4["Segment<br>[142, 160, 4]"]
|
||||
5["Segment<br>[166, 184, 4]"]
|
||||
6["Segment<br>[190, 246, 4]"]
|
||||
7["Segment<br>[252, 259, 4]"]
|
||||
2["Path<br>[76, 113, 5]"]
|
||||
3["Segment<br>[119, 136, 5]"]
|
||||
4["Segment<br>[142, 160, 5]"]
|
||||
5["Segment<br>[166, 184, 5]"]
|
||||
6["Segment<br>[190, 246, 5]"]
|
||||
7["Segment<br>[252, 259, 5]"]
|
||||
8[Solid2d]
|
||||
end
|
||||
subgraph path25 [Path]
|
||||
25["Path<br>[76, 111, 5]"]
|
||||
26["Segment<br>[117, 134, 5]"]
|
||||
27["Segment<br>[140, 158, 5]"]
|
||||
28["Segment<br>[164, 182, 5]"]
|
||||
29["Segment<br>[188, 244, 5]"]
|
||||
30["Segment<br>[250, 257, 5]"]
|
||||
25["Path<br>[76, 111, 6]"]
|
||||
26["Segment<br>[117, 134, 6]"]
|
||||
27["Segment<br>[140, 158, 6]"]
|
||||
28["Segment<br>[164, 182, 6]"]
|
||||
29["Segment<br>[188, 244, 6]"]
|
||||
30["Segment<br>[250, 257, 6]"]
|
||||
31[Solid2d]
|
||||
end
|
||||
1["Plane<br>[47, 66, 4]"]
|
||||
9["Sweep Extrusion<br>[265, 287, 4]"]
|
||||
1["Plane<br>[47, 66, 5]"]
|
||||
9["Sweep Extrusion<br>[265, 287, 5]"]
|
||||
10[Wall]
|
||||
11[Wall]
|
||||
12[Wall]
|
||||
@ -34,8 +34,8 @@ flowchart LR
|
||||
21["SweepEdge Adjacent"]
|
||||
22["SweepEdge Opposite"]
|
||||
23["SweepEdge Adjacent"]
|
||||
24["Plane<br>[47, 66, 5]"]
|
||||
32["Sweep Extrusion<br>[263, 285, 5]"]
|
||||
24["Plane<br>[47, 66, 6]"]
|
||||
32["Sweep Extrusion<br>[263, 285, 6]"]
|
||||
33[Wall]
|
||||
34[Wall]
|
||||
35[Wall]
|
||||
|
@ -5,10 +5,10 @@ description: Variables in memory after executing assembly_mixed_units_cubes.kcl
|
||||
{
|
||||
"cubeIn": {
|
||||
"type": "Module",
|
||||
"value": 4
|
||||
"value": 5
|
||||
},
|
||||
"cubeMm": {
|
||||
"type": "Module",
|
||||
"value": 5
|
||||
"value": 6
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph path2 [Path]
|
||||
2["Path<br>[197, 232, 4]"]
|
||||
3["Segment<br>[197, 232, 4]"]
|
||||
2["Path<br>[197, 232, 5]"]
|
||||
3["Segment<br>[197, 232, 5]"]
|
||||
4[Solid2d]
|
||||
end
|
||||
subgraph path6 [Path]
|
||||
6["Path<br>[113, 148, 5]"]
|
||||
7["Segment<br>[113, 148, 5]"]
|
||||
6["Path<br>[113, 148, 6]"]
|
||||
7["Segment<br>[113, 148, 6]"]
|
||||
8[Solid2d]
|
||||
end
|
||||
1["Plane<br>[172, 191, 4]"]
|
||||
5["Plane<br>[88, 107, 5]"]
|
||||
1["Plane<br>[172, 191, 5]"]
|
||||
5["Plane<br>[88, 107, 6]"]
|
||||
1 --- 2
|
||||
2 --- 3
|
||||
2 --- 4
|
||||
|
@ -5,10 +5,10 @@ description: Variables in memory after executing assembly_non_default_units.kcl
|
||||
{
|
||||
"other1": {
|
||||
"type": "Module",
|
||||
"value": 4
|
||||
"value": 5
|
||||
},
|
||||
"other2": {
|
||||
"type": "Module",
|
||||
"value": 5
|
||||
"value": 6
|
||||
}
|
||||
}
|
||||
|
@ -148,6 +148,22 @@ description: Operations executed crazy_multi_profile.kcl
|
||||
"type": "UserDefinedFunctionReturn"
|
||||
},
|
||||
{
|
||||
"type": "UserDefinedFunctionCall",
|
||||
"name": "revolve",
|
||||
"functionSourceRange": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"unlabeledArg": {
|
||||
"value": {
|
||||
"type": "Sketch",
|
||||
"value": {
|
||||
"artifactId": "[uuid]"
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
"labeledArgs": {
|
||||
"angle": {
|
||||
"value": {
|
||||
@ -173,18 +189,54 @@ description: Operations executed crazy_multi_profile.kcl
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"name": "revolve",
|
||||
"sourceRange": [],
|
||||
"type": "StdLibCall",
|
||||
"sourceRange": []
|
||||
},
|
||||
{
|
||||
"type": "KclStdLibCall",
|
||||
"name": "",
|
||||
"unlabeledArg": {
|
||||
"value": {
|
||||
"type": "Sketch",
|
||||
"value": {
|
||||
"artifactId": "[uuid]"
|
||||
}
|
||||
"type": "Array",
|
||||
"value": [
|
||||
{
|
||||
"type": "Sketch",
|
||||
"value": {
|
||||
"artifactId": "[uuid]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"labeledArgs": {
|
||||
"angle": {
|
||||
"value": {
|
||||
"type": "Number",
|
||||
"value": 45.0,
|
||||
"ty": {
|
||||
"type": "Default",
|
||||
"len": {
|
||||
"type": "Mm"
|
||||
},
|
||||
"angle": {
|
||||
"type": "Degrees"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
"axis": {
|
||||
"value": {
|
||||
"type": "Uuid",
|
||||
"value": "[uuid]"
|
||||
},
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
{
|
||||
"type": "UserDefinedFunctionReturn"
|
||||
},
|
||||
{
|
||||
"labeledArgs": {
|
||||
@ -338,6 +390,22 @@ description: Operations executed crazy_multi_profile.kcl
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "UserDefinedFunctionCall",
|
||||
"name": "revolve",
|
||||
"functionSourceRange": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"unlabeledArg": {
|
||||
"value": {
|
||||
"type": "Sketch",
|
||||
"value": {
|
||||
"artifactId": "[uuid]"
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
"labeledArgs": {
|
||||
"angle": {
|
||||
"value": {
|
||||
@ -364,17 +432,54 @@ description: Operations executed crazy_multi_profile.kcl
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"name": "revolve",
|
||||
"sourceRange": [],
|
||||
"type": "StdLibCall",
|
||||
"sourceRange": []
|
||||
},
|
||||
{
|
||||
"type": "KclStdLibCall",
|
||||
"name": "",
|
||||
"unlabeledArg": {
|
||||
"value": {
|
||||
"type": "Sketch",
|
||||
"value": {
|
||||
"artifactId": "[uuid]"
|
||||
}
|
||||
"type": "Array",
|
||||
"value": [
|
||||
{
|
||||
"type": "Sketch",
|
||||
"value": {
|
||||
"artifactId": "[uuid]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"labeledArgs": {
|
||||
"angle": {
|
||||
"value": {
|
||||
"type": "Number",
|
||||
"value": 45.0,
|
||||
"ty": {
|
||||
"type": "Default",
|
||||
"len": {
|
||||
"type": "Mm"
|
||||
},
|
||||
"angle": {
|
||||
"type": "Degrees"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
"axis": {
|
||||
"value": {
|
||||
"type": "TagIdentifier",
|
||||
"value": "seg02",
|
||||
"artifact_id": "[uuid]"
|
||||
},
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
{
|
||||
"type": "UserDefinedFunctionReturn"
|
||||
}
|
||||
]
|
||||
|
@ -117,6 +117,14 @@ description: Operations executed helix_ccw.kcl
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "UserDefinedFunctionCall",
|
||||
"name": "helix",
|
||||
"functionSourceRange": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"unlabeledArg": null,
|
||||
"labeledArgs": {
|
||||
"angleStart": {
|
||||
"value": {
|
||||
@ -167,9 +175,11 @@ description: Operations executed helix_ccw.kcl
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"name": "helix",
|
||||
"sourceRange": [],
|
||||
"type": "StdLibCall",
|
||||
"sourceRange": []
|
||||
},
|
||||
{
|
||||
"type": "KclStdLibCall",
|
||||
"name": "",
|
||||
"unlabeledArg": {
|
||||
"value": {
|
||||
"type": "Solid",
|
||||
@ -178,6 +188,60 @@ description: Operations executed helix_ccw.kcl
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"labeledArgs": {
|
||||
"angleStart": {
|
||||
"value": {
|
||||
"type": "Number",
|
||||
"value": 0.0,
|
||||
"ty": {
|
||||
"type": "Default",
|
||||
"len": {
|
||||
"type": "Mm"
|
||||
},
|
||||
"angle": {
|
||||
"type": "Degrees"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
"ccw": {
|
||||
"value": {
|
||||
"type": "Bool",
|
||||
"value": true
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
"cylinder": {
|
||||
"value": {
|
||||
"type": "Solid",
|
||||
"value": {
|
||||
"artifactId": "[uuid]"
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
"revolutions": {
|
||||
"value": {
|
||||
"type": "Number",
|
||||
"value": 16.0,
|
||||
"ty": {
|
||||
"type": "Default",
|
||||
"len": {
|
||||
"type": "Mm"
|
||||
},
|
||||
"angle": {
|
||||
"type": "Degrees"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"sourceRange": []
|
||||
},
|
||||
{
|
||||
"type": "UserDefinedFunctionReturn"
|
||||
}
|
||||
]
|
||||
|