Compare commits

..

3 Commits

Author SHA1 Message Date
87dfda28a9 Move axes to std constants; move helix, revolve, and mirror2d to be declated in KCL
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-04-02 17:05:28 +13:00
42f44e11f5 Add Edge type to std
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-03-28 11:18:10 +08:00
16ad7ff77a Move turns to a submodule of std
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-03-28 11:10:51 +08:00
269 changed files with 19156 additions and 47018 deletions

View File

@ -20,20 +20,9 @@
"plugin:react-hooks/recommended"
],
"rules": {
"no-array-constructor": "off", // This is wrong; use the @typescript-eslint one instead.
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-array-delete": "error",
"@typescript-eslint/no-duplicate-enum-values": "error",
"@typescript-eslint/no-duplicate-type-constituents": "error",
"@typescript-eslint/no-empty-object-type": "error",
"@typescript-eslint/no-floating-promises": "error",
"no-implied-eval": "off", // This is wrong; use the @typescript-eslint one instead.
"@typescript-eslint/no-implied-eval": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-unnecessary-type-constraint": "error",
"no-unused-vars": "off", // This is wrong; use the @typescript-eslint one instead.
"@typescript-eslint/no-unused-vars": ["error", {
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_",
@ -41,7 +30,6 @@
"vars": "all",
"args": "none"
}],
"@typescript-eslint/prefer-as-const": "warn",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-autofocus": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",

View File

@ -28,87 +28,43 @@ jobs:
- run: yarn fmt-check
yarn-build-wasm:
# Build the wasm blob once on the fastest runner.
runs-on: runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn install
- name: Use correct Rust toolchain
shell: bash
run: |
[ -e rust-toolchain.toml ] || cp rust/rust-toolchain.toml ./
- name: Install rust
uses: actions-rust-lang/setup-rust-toolchain@v1
- run: yarn install
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
cache: false # Configured below.
tool: wasm-pack
- run: yarn build:wasm
yarn-tsc:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- run: yarn --cwd ./rust/kcl-language-server --modules-folder node_modules install
- uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
tool: wasm-pack
- name: Rust Cache
uses: Swatinem/rust-cache@v2
with:
workspaces: './rust'
- name: Build Wasm
shell: bash
run: yarn build:wasm
- uses: actions/upload-artifact@v4
with:
name: prepared-wasm
path: |
rust/kcl-wasm-lib/pkg/kcl_wasm_lib*
- uses: actions/upload-artifact@v4
with:
name: prepared-ts-rs-bindings
path: |
rust/kcl-lib/bindings/*
yarn-tsc:
runs-on: ubuntu-latest
needs: yarn-build-wasm
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn build:wasm
- run: yarn tsc
yarn-lint:
runs-on: ubuntu-latest
needs: yarn-build-wasm
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
@ -117,23 +73,7 @@ jobs:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn --cwd ./rust/kcl-language-server --modules-folder node_modules install
- run: yarn lint
python-codespell:
@ -151,7 +91,6 @@ jobs:
yarn-unit-test-kcl-samples:
runs-on: ubuntu-latest
needs: yarn-build-wasm
steps:
- uses: actions/checkout@v4
@ -164,22 +103,7 @@ jobs:
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
tool: wasm-pack
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn build:wasm
- run: yarn simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}
@ -196,7 +120,6 @@ jobs:
yarn-unit-test:
runs-on: ubuntu-latest
needs: yarn-build-wasm
steps:
- uses: actions/checkout@v4
@ -209,22 +132,7 @@ jobs:
- uses: taiki-e/install-action@37bdc826eaedac215f638a96472df572feab0f9b
with:
tool: wasm-pack
- name: Download all artifacts
uses: actions/download-artifact@v4
- name: Copy prepared wasm
run: |
ls -R prepared-wasm
cp prepared-wasm/kcl_wasm_lib_bg.wasm public
mkdir rust/kcl-wasm-lib/pkg
cp prepared-wasm/kcl_wasm_lib* rust/kcl-wasm-lib/pkg
- name: Copy prepared ts-rs bindings
run: |
ls -R prepared-ts-rs-bindings
mkdir rust/kcl-lib/bindings
cp -r prepared-ts-rs-bindings/* rust/kcl-lib/bindings/
- run: yarn build:wasm
- run: yarn simpleserver:bg
if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }}

View File

@ -37,7 +37,7 @@ build-web: public/kcl_wasm_lib_bg.wasm build/index.html
build-desktop: public/kcl_wasm_lib_bg.wasm .vite/build/main.js
public/kcl_wasm_lib_bg.wasm: $(CARGO_SOURCES)$(RUST_SOURCES)
yarn build:wasm:dev
yarn build:wasm
build/index.html: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
yarn build:local
@ -99,7 +99,7 @@ test-e2e-web: install build-web ## Run the web e2e tests
.PHONY: test-e2e-desktop
test-e2e-desktop: install build-desktop ## Run the desktop e2e tests
yarn test:playwright:electron --workers=$(E2E_WORKERS) --max-failures=$(E2E_FAILURES) --grep="$(E2E_GREP)"
yarn test:playwright:electron --workers=$(E2E_WORKERS) --max-failures=$(E2E_FAILURES) --grep=$(E2E_GREP)
###############################################################################
# CLEAN

View File

@ -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)
```

View File

@ -9,9 +9,12 @@ layout: manual
### `std`
- [`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)
- [`Z`](/docs/kcl/consts/std-Z)
### `std::math`

15
docs/kcl/consts/std-X.md Normal file
View File

@ -0,0 +1,15 @@
---
title: "std::X"
excerpt: ""
layout: manual
---
```js
std::X
```

15
docs/kcl/consts/std-Y.md Normal file
View File

@ -0,0 +1,15 @@
---
title: "std::Y"
excerpt: ""
layout: manual
---
```js
std::Y
```

15
docs/kcl/consts/std-Z.md Normal file
View File

@ -0,0 +1,15 @@
---
title: "std::Z"
excerpt: ""
layout: manual
---
```js
std::Z
```

File diff suppressed because one or more lines are too long

View File

@ -22,6 +22,9 @@ 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)
* [`Helix`](kcl/types/Helix)
* [`Plane`](kcl/types/Plane)
@ -29,9 +32,12 @@ layout: manual
* [`Point3d`](kcl/types/Point3d)
* [`Sketch`](kcl/types/Sketch)
* [`Solid`](kcl/types/Solid)
* [`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)
* [`Z`](kcl/consts/std-Z)
* [`abs`](kcl/abs)
* [`acos`](kcl/acos)
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
@ -68,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)
@ -87,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)
@ -106,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)
@ -142,6 +147,7 @@ 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)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -6,10 +6,6 @@ layout: manual
Rotate a solid or a sketch.
This is really useful for assembling parts together. You can create a part and then rotate it to the correct orientation.
For sketches, you can use this to rotate a sketch and then loft it with another sketch.
### Using Roll, Pitch, and Yaw
When rotating a part in 3D space, "roll," "pitch," and "yaw" refer to the three rotational axes used to describe its orientation: roll is rotation around the longitudinal axis (front-to-back), pitch is rotation around the lateral axis (wing-to-wing), and yaw is rotation around the vertical axis (up-down); essentially, it's like tilting the part on its side (roll), tipping the nose up or down (pitch), and turning it left or right (yaw).
@ -170,7 +166,7 @@ fn square() {
profile001 = square()
profile002 = square()
|> translate(x = 0, y = 0, z = 20)
|> translate(translate = [0, 0, 20])
|> rotate(axis = [0, 0, 1.0], angle = 45)
loft([profile001, profile002])

File diff suppressed because one or more lines are too long

View File

@ -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

File diff suppressed because one or more lines are too long

246
docs/kcl/std-revolve.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -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.

File diff suppressed because one or more lines are too long

12
docs/kcl/types/Axis2d.md Normal file
View 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
View 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
View File

@ -0,0 +1,12 @@
---
title: "std::Edge"
excerpt: "The edge of a solid."
layout: manual
---
The edge of a solid.

View File

@ -1,117 +0,0 @@
import { test, expect } from './zoo-test'
import fs from 'node:fs/promises'
import path from 'node:path'
test.describe('Point and click for boolean workflows', () => {
// Boolean operations to test
const booleanOperations = [
{
name: 'union',
code: 'union([extrude001, extrude006])',
},
{
name: 'subtract',
code: 'subtract([extrude001], tools = [extrude006])',
},
{
name: 'intersect',
code: 'intersect([extrude001, extrude006])',
},
] as const
for (let i = 0; i < booleanOperations.length; i++) {
const operation = booleanOperations[i]
const operationName = operation.name
const commandName = `Boolean ${
operationName.charAt(0).toUpperCase() + operationName.slice(1)
}`
test(`Create boolean operation -- ${operationName}`, async ({
context,
homePage,
cmdBar,
editor,
toolbar,
scene,
page,
}) => {
const file = await fs.readFile(
path.resolve(
__dirname,
'../../',
'./rust/kcl-lib/e2e/executor/inputs/boolean-setup-with'
),
'utf-8'
)
await context.addInitScript((file) => {
localStorage.setItem('persistCode', file)
}, file)
await homePage.goToModelingScene()
await scene.waitForExecutionDone()
await scene.settled(cmdBar)
// Test coordinates for selection - these might need adjustment based on actual scene layout
const cylinderPoint = { x: 592, y: 174 }
const secondObjectPoint = { x: 683, y: 273 }
// Create mouse helpers for selecting objects
const [clickFirstObject] = scene.makeMouseHelpers(
cylinderPoint.x,
cylinderPoint.y,
{ steps: 10 }
)
const [clickSecondObject] = scene.makeMouseHelpers(
secondObjectPoint.x,
secondObjectPoint.y,
{ steps: 10 }
)
await test.step(`Test ${operationName} operation`, async () => {
// Click the boolean operation button in the toolbar
await toolbar.selectBoolean(operationName)
// Verify command bar is showing the right command
await expect(cmdBar.page.getByTestId('command-name')).toContainText(
commandName
)
// Select first object in the scene, expect there to be a pixel diff from the selection color change
await clickFirstObject({ pixelDiff: 50 })
// For subtract, we need to proceed to the next step before selecting the second object
if (operationName !== 'subtract') {
// should down shift key to select multiple objects
await page.keyboard.down('Shift')
}
// Select second object
await clickSecondObject({ pixelDiff: 50 })
// Confirm the operation in the command bar
await cmdBar.progressCmdBar()
if (operationName === 'union' || operationName === 'intersect') {
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Solids: '2 paths',
},
commandName,
})
} else if (operationName === 'subtract') {
await cmdBar.expectState({
stage: 'review',
headerArguments: {
Tool: '1 path',
Target: '1 path',
},
commandName,
})
}
await cmdBar.submit()
await editor.expectEditor.toContain(operation.code)
})
})
}
})

View File

@ -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',

View File

@ -181,14 +181,6 @@ export class ToolbarFixture {
).toBeVisible()
await this.page.getByTestId('dropdown-center-rectangle').click()
}
selectBoolean = async (operation: 'union' | 'subtract' | 'intersect') => {
await this.page
.getByRole('button', { name: 'caret down Union: open menu' })
.click()
const operationTestId = `dropdown-boolean-${operation}`
await expect(this.page.getByTestId(operationTestId)).toBeVisible()
await this.page.getByTestId(operationTestId).click()
}
selectCircleThreePoint = async () => {
await this.page

View File

@ -258,6 +258,14 @@ export const isErrorWhitelisted = (exception: Error) => {
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',
@ -265,6 +273,7 @@ export const isErrorWhitelisted = (exception: Error) => {
project: 'Google Chrome',
foundInSpec: 'e2e/playwright/snapshot-tests.spec.ts',
},
// TODO: fix this error in the code
{
name: 'TypeError',
message: 'Failed to fetch',
@ -273,6 +282,14 @@ export const isErrorWhitelisted = (exception: Error) => {
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',
@ -280,14 +297,6 @@ export const isErrorWhitelisted = (exception: Error) => {
project: 'Google Chrome',
foundInSpec: 'e2e/playwright/testing-constraints.spec.ts',
},
{
name: 'Error',
message: 'The "path" argument must be of type string. Received undefined',
stack:
'Error: The "path" argument must be of type string. Received undefined',
project: 'Google Chrome',
foundInSpec: '', // many tests are impacted by this error
},
]
const cleanString = (str: string) => str.replace(/[`"]/g, '')

View File

@ -230,9 +230,9 @@ test.describe('Onboarding tests', () => {
// Override beforeEach test setup
await context.addInitScript(
async ({ settingsKey, settings, code }) => {
async ({ settingsKey, settings }) => {
// Give some initial code, so we can test that it's cleared
localStorage.setItem('persistCode', code)
localStorage.setItem('persistCode', originalCode)
localStorage.setItem(settingsKey, settings)
},
{
@ -240,7 +240,6 @@ test.describe('Onboarding tests', () => {
settings: settingsToToml({
settings: TEST_SETTINGS_ONBOARDING_EXPORT,
}),
code: originalCode,
}
)

View File

@ -1097,6 +1097,7 @@ openSketch = startSketchOn(XY)
Mode: '',
AngleStart: '',
Revolutions: '',
Length: '',
Radius: '',
CounterClockWise: '',
},
@ -1193,14 +1194,14 @@ openSketch = startSketchOn(XY)
{
selectionType: 'segment',
testPoint: { x: 513, y: 221 },
expectedOutput: `helix001 = helix( axis = seg01, radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
expectedEditedOutput: `helix001 = helix( axis = seg01, radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
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( axis = getOppositeEdge(seg01), radius = 1, revolutions = 20, angleStart = 0, ccw = false,)`,
expectedEditedOutput: `helix001 = helix( axis = getOppositeEdge(seg01), radius = 5, revolutions = 20, angleStart = 0, ccw = false,)`,
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(
@ -1243,6 +1244,7 @@ openSketch = startSketchOn(XY)
AngleStart: '',
Mode: '',
CounterClockWise: '',
Length: '',
Radius: '',
Revolutions: '',
},
@ -1259,6 +1261,8 @@ openSketch = startSketchOn(XY)
await cmdBar.progressCmdBar()
await page.keyboard.insertText('1')
await cmdBar.progressCmdBar()
await page.keyboard.insertText('100')
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: {
@ -1267,6 +1271,7 @@ openSketch = startSketchOn(XY)
AngleStart: '0',
Revolutions: '20',
Radius: '1',
Length: '100',
CounterClockWise: '',
},
commandName: 'Helix',
@ -1287,8 +1292,8 @@ openSketch = startSketchOn(XY)
0
)
await operationButton.dblclick()
const initialInput = '1'
const newInput = '5'
const initialInput = '100'
const newInput = '50'
await cmdBar.expectState({
commandName: 'Helix',
stage: 'arguments',
@ -1297,14 +1302,13 @@ openSketch = startSketchOn(XY)
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: initialInput,
Radius: '1',
Length: initialInput,
CounterClockWise: '',
},
highlightedHeaderArg: 'CounterClockWise',
})
await page
.getByRole('button', { name: 'radius', exact: false })
.click()
await page.keyboard.press('Shift+Backspace')
await expect(cmdBar.currentArgumentInput).toBeVisible()
await cmdBar.currentArgumentInput
.locator('.cm-content')
@ -1315,7 +1319,8 @@ openSketch = startSketchOn(XY)
headerArguments: {
AngleStart: '0',
Revolutions: '20',
Radius: newInput,
Radius: '1',
Length: newInput,
CounterClockWise: '',
},
commandName: 'Helix',
@ -1386,6 +1391,7 @@ extrude001 = extrude(profile001, length = 100)
Mode: '',
AngleStart: '',
Revolutions: '',
Length: '',
Radius: '',
CounterClockWise: '',
},
@ -3463,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 ({

View File

@ -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
}

View File

@ -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

View File

@ -94,11 +94,9 @@
"fetch:wasm": "./scripts/get-latest-wasm-bundle.sh",
"fetch:wasm:windows": "./scripts/get-latest-wasm-bundle.ps1",
"fetch:samples": "rm -rf public/kcl-samples* && curl -L -o public/kcl-samples.zip https://github.com/KittyCAD/kcl-samples/archive/refs/heads/achalmers/kw-args-xylineto.zip && unzip -o public/kcl-samples.zip -d public && mv public/kcl-samples-* public/kcl-samples",
"build:wasm-dev": "yarn wasm-prep && (cd rust && wasm-pack build kcl-wasm-lib --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt:generated",
"build:wasm": "./scripts/build-wasm.sh",
"build:wasm:windows": "./scripts/build-wasm.ps1",
"build:wasm-dev": "yarn build:wasm:dev",
"build:wasm:dev": "./scripts/build-wasm-dev.sh",
"build:wasm:dev:windows": "./scripts/build-wasm-dev.ps1",
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./rust/kcl-wasm-lib/pkg/kcl_wasm_lib.js\" || echo \"sed for both mac and linux\"",
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src",
"lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src rust/kcl-language-server/client/src",

View File

@ -90,7 +90,7 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
}
add(bytes: Uint8Array): void {
const message = Codec.decode<vsrpc.Message>(bytes)
const message = Codec.decode(bytes) as vsrpc.Message
if (this.trace) {
Tracer.server(message)
}

View File

@ -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],

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -12,7 +12,7 @@ import "car-tire.kcl" as carTire
import lugCount from "globals.kcl"
carRotor
|> translate(x = 0, y = 0.5, z = 0)
|> translate(translate = [0, 0.5, 0])
carWheel
lugNut
|> patternCircular3d(
@ -23,5 +23,5 @@ lugNut
rotateDuplicates = false,
)
brakeCaliper
|> translate(x = 0, y = 0.5, z = 0)
|> translate(translate = [0, 0.5, 0])
carTire

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -19,27 +19,27 @@ import pipe from "1120t74-pipe.kcl"
flange()
flange()
|> rotate(axis = [0, 1, 0], angle = 180)
|> translate(
x = 0,
y = 0,
z = flangeBackHeight * 2 + gasketThickness,
)
|> translate(translate = [
0,
0,
flangeBackHeight * 2 + gasketThickness
])
// place gasket between the flanges
gasket()
|> translate(
x = 0,
y = 0,
z = -flangeBackHeight - gasketThickness
)
|> translate(translate = [
0,
0,
-flangeBackHeight - gasketThickness
])
// place eight washers (four front, four back)
washer()
|> translate(
x = mountingHolePlacementDiameter / 2,
y = 0,
z = flangeBaseThickness
)
|> translate(translate = [
mountingHolePlacementDiameter / 2,
0,
flangeBaseThickness
])
|> patternCircular3d(
%,
instances = 4,
@ -57,11 +57,11 @@ washer()
// place four bolts
bolt()
|> translate(
x = mountingHolePlacementDiameter / 2,
y = 0,
z = flangeBaseThickness + washerThickness,
)
|> translate(translate = [
mountingHolePlacementDiameter / 2,
0,
flangeBaseThickness + washerThickness
])
|> rotate(roll = 90, pitch = 0, yaw = 0)
|> patternCircular3d(
%,
@ -74,11 +74,11 @@ bolt()
// place four hex nuts
hexNut()
|> translate(
x = mountingHolePlacementDiameter / 2,
y = 0,
z = -(flangeBackHeight * 2 + gasketThickness + flangeBaseThickness + washerThickness + hexNutThickness),
)
|> translate(translate = [
mountingHolePlacementDiameter / 2,
0,
-(flangeBackHeight * 2 + gasketThickness + flangeBaseThickness + washerThickness + hexNutThickness)
])
|> patternCircular3d(
%,
instances = 4,
@ -97,11 +97,13 @@ pipe()
yaw = 0,
)
|> translate(
%,
x = 0,
y = 0,
z = flangeBaseThickness + flangeFrontHeight - 0.5,
global = true,
%,
translate = [
0,
0,
flangeBaseThickness + flangeFrontHeight - 0.5
],
global = true,
)
pipe()
@ -112,9 +114,11 @@ pipe()
yaw = 0,
)
|> translate(
%,
x = 0,
y = 0,
z = -(flangeBackHeight * 2 + gasketThickness + flangeBaseThickness + flangeFrontHeight - 0.5),
global = true,
%,
translate = [
0,
0,
-(flangeBackHeight * 2 + gasketThickness + flangeBaseThickness + flangeFrontHeight - 0.5)
],
global = true,
)

View File

@ -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)

View File

@ -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)

View File

@ -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]
},
)

View File

@ -14,7 +14,7 @@ radius = 10
depth = 30
distanceToInsideEdge = slateWidthHalf + templateThickness + templateGap
sketch001 = startSketchOn(XZ)
|> startProfileAt([0, 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 = 0, tag = $seg04)
|> xLine(endAbsolute = turns::ZERO, tag = $seg04)
|> xLine(length = -segLen(seg04))
|> yLine(length = -segLen(seg10))
|> xLine(length = -segLen(seg05))

View File

@ -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 = 0, tag = $seg02)
|> xLine(endAbsolute = turns::ZERO, tag = $seg02)
// |> line(end = [7.78, 11.16])
|> xLine(length = -segLen(seg02))
|> yLine(length = segLen(seg03))

View File

@ -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

View File

@ -20,37 +20,37 @@ body()
// import the antenna
antenna()
|> translate(x = -width / 2 + .45, y = -0.10, z = height / 2)
|> translate(translate = [-width / 2 + .45, -0.10, height / 2])
// import the case
case()
|> translate(x = 0, y = -1, z = 0)
|> translate(translate = [0, -1, 0])
// import the talk button
talkButton()
|> translate(x = width / 2, y = -thickness / 2, z = .5)
|> translate(translate = [width / 2, -thickness / 2, .5])
// import the frequency knob
knob()
|> translate(
x = width / 2 - 0.70,
y = -thickness / 2,
z = height / 2
)
|> translate(translate = [
width / 2 - 0.70,
-thickness / 2,
height / 2
])
// import the buttons
button()
|> translate(
x = -(screenWidth / 2 + tolerance),
y = -1,
z = screenYPosition
)
|> translate(translate = [
-(screenWidth / 2 + tolerance),
-1,
screenYPosition
])
button()
|> translate(
x = -(screenWidth / 2 + tolerance),
y = -1,
z = screenYPosition - buttonHeight - (tolerance * 2)
)
|> translate(translate = [
-(screenWidth / 2 + tolerance),
-1,
screenYPosition - buttonHeight - (tolerance * 2)
])
button()
|> rotate(
%,
@ -59,21 +59,25 @@ button()
yaw = 0,
)
|> translate(
x = screenWidth / 2 + tolerance,
y = -1,
z = screenYPosition - buttonHeight,
global = true,
)
button()
|> rotate(
%,
roll = 0,
pitch = 180,
yaw = 0,
)
|> translate(
x = screenWidth / 2 + tolerance,
y = -1,
z = screenYPosition - (buttonHeight * 2) - (tolerance * 2),
translate = [
screenWidth / 2 + tolerance,
-1,
screenYPosition - buttonHeight
],
global = true,
)
button()
|> rotate(
%,
roll = 0,
pitch = 180,
yaw = 0,
)
|> translate(
translate = [
screenWidth / 2 + tolerance,
-1,
screenYPosition - (buttonHeight * 2) - (tolerance * 2)
],
global = true,
)

24
rust/Cargo.lock generated
View File

@ -1780,7 +1780,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.56"
version = "0.1.55"
dependencies = [
"anyhow",
"clap",
@ -1791,7 +1791,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.56"
version = "0.1.55"
dependencies = [
"Inflector",
"anyhow",
@ -1810,7 +1810,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.56"
version = "0.1.55"
dependencies = [
"proc-macro2",
"quote",
@ -1819,7 +1819,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.56"
version = "0.2.55"
dependencies = [
"anyhow",
"clap",
@ -1840,7 +1840,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.56"
version = "0.1.55"
dependencies = [
"anyhow",
"clap",
@ -1860,7 +1860,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.56"
version = "0.2.55"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1928,7 +1928,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.56"
version = "0.3.55"
dependencies = [
"anyhow",
"kcl-lib",
@ -1943,7 +1943,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.56"
version = "0.1.55"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -1956,7 +1956,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.56"
version = "0.1.55"
dependencies = [
"anyhow",
"async-trait",
@ -1970,7 +1970,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.56"
version = "0.1.55"
dependencies = [
"bson",
"console_error_panic_hook",
@ -1996,9 +1996,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.3.36"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a345fd2a4cb16205f32bd1aa41715045830c59d78c59927fca6580e2a651ac9"
checksum = "4f6f65645cc07a8f43c34584e4979bf4da16c047cce50c4715fa9381227574d5"
dependencies = [
"anyhow",
"async-trait",

View File

@ -35,7 +35,7 @@ clap = { version = "4.5.31", features = ["derive"] }
dashmap = { version = "6.1.0" }
http = "1"
indexmap = "2.7.0"
kittycad = { version = "0.3.36", default-features = false, features = ["js", "requests"] }
kittycad = { version = "0.3.33", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.107", features = ["ts-rs", "websocket"] }
lazy_static = "1.5.0"
miette = "7.5.0"

View File

@ -43,10 +43,6 @@ overwrite-sim-test test_name:
EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} -p kcl-lib --no-quiet -- tests::{{test_name}}::kcl_test_execute
EXPECTORATE=overwrite {{cita}} -p kcl-lib --no-quiet -- simulation_tests::{{test_name}}::test_after_engine
# Regenerate all the simulation test output.
redo-sim-tests:
EXPECTORATE=overwrite TWENTY_TWENTY=overwrite {{cita}} -p kcl-lib --no-quiet -- simulation_tests
test:
export RUST_BRACKTRACE="full" && cargo nextest run --workspace --no-fail-fast

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-bumper"
version = "0.1.56"
version = "0.1.55"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-api"
rust-version = "1.76"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.56"
version = "0.1.55"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-directory-test-macro"
description = "A tool for generating tests from a directory of kcl files"
version = "0.1.56"
version = "0.1.55"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-language-server-release"
version = "0.1.56"
version = "0.1.55"
edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false

View File

@ -2,7 +2,7 @@
name = "kcl-language-server"
description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.56"
version = "0.2.55"
edition = "2021"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -2194,9 +2194,9 @@ supports-color@^8.1.1:
has-flag "^4.0.0"
tar-fs@^2.0.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.2.tgz#425f154f3404cb16cb8ff6e671d45ab2ed9596c5"
integrity sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==
version "2.1.1"
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
dependencies:
chownr "^1.1.1"
mkdirp-classic "^0.5.2"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.56"
version = "0.2.55"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -104,7 +104,7 @@ tower-lsp = { workspace = true, features = ["proposed", "default"] }
[features]
default = ["engine"]
cli = ["dep:clap", "kittycad/clap"]
cli = ["dep:clap"]
dhat-heap = ["dep:dhat"]
# For the lsp server, when run with stdout for rpc we want to disable println.
# This is used for editor extensions that use the lsp server.

View File

@ -1,8 +1,6 @@
//! Cache testing framework.
use kcl_lib::{bust_cache, ExecError, ExecOutcome};
use kcmc::{each_cmd as mcmd, ModelingCmd};
use kittycad_modeling_cmds as kcmc;
#[derive(Debug)]
struct Variation<'a> {
@ -255,71 +253,3 @@ extrude(sketch001, length = 4)
second.artifact_graph.len()
);
}
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_cache_empty_file_pop_cache_empty_file_planes_work() {
// Get the current working directory.
let code = "";
let ctx = kcl_lib::ExecutorContext::new_with_default_client(Default::default())
.await
.unwrap();
let program = kcl_lib::Program::parse_no_errs(code).unwrap();
let outcome = ctx.run_with_caching(program).await.unwrap();
// Ensure nothing is left in the batch
assert!(ctx.engine.batch().read().await.is_empty());
assert!(ctx.engine.batch_end().read().await.is_empty());
// Ensure the planes work, and we can show or hide them.
// Hide/show the grid.
let default_planes = ctx.engine.get_default_planes().read().await.clone().unwrap();
// Assure the outcome is the same.
assert_eq!(outcome.default_planes, Some(default_planes.clone()));
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
Default::default(),
&ModelingCmd::from(mcmd::ObjectVisible {
hidden: false,
object_id: default_planes.xy,
}),
)
.await
.unwrap();
// Now simulate an engine pause/network disconnect.
// Raw dog clear the scene entirely.
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
Default::default(),
&ModelingCmd::from(mcmd::SceneClearAll {}),
)
.await
.unwrap();
// Bust the cache and reset the scene.
let outcome = ctx.bust_cache_and_reset_scene().await.unwrap();
// Get the default planes.
let default_planes = ctx.engine.get_default_planes().read().await.clone().unwrap();
assert_eq!(outcome.default_planes, Some(default_planes.clone()));
// Ensure we can show a plane.
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
Default::default(),
&ModelingCmd::from(mcmd::ObjectVisible {
hidden: false,
object_id: default_planes.xz,
}),
)
.await
.unwrap();
ctx.close().await;
}

View File

@ -1,77 +0,0 @@
@settings(defaultLengthUnit = mm)
sketch001 = startSketchOn(XZ)
profile001 = circle(sketch001, center = [154.36, 113.92], radius = 41.09)
extrude001 = extrude(profile001, length = 200)
sketch002 = startSketchOn(XY)
profile002 = startProfileAt([72.24, -52.05], sketch002)
|> angledLine([0, 181.26], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
21.54
], %)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $mySeg)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude002 = extrude(profile002, length = 150)
|> chamfer(
%,
length = 15,
tags = [mySeg],
tag = $seg02,
)
sketch003 = startSketchOn(extrude002, mySeg)
profile003 = startProfileAt([207.36, 126.19], sketch003)
|> angledLine([0, 33.57], %, $rectangleSegmentA002)
|> angledLine([
segAng(rectangleSegmentA002) - 90,
99.11
], %)
|> angledLine([
segAng(rectangleSegmentA002),
-segLen(rectangleSegmentA002)
], %, $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude003 = extrude(profile003, length = -20)
sketch004 = startSketchOn(extrude003, seg01)
profile004 = startProfileAt([-235.38, 66.16], sketch004)
|> angledLine([0, 24.21], %, $rectangleSegmentA003)
|> angledLine([
segAng(rectangleSegmentA003) - 90,
3.72
], %)
|> angledLine([
segAng(rectangleSegmentA003),
-segLen(rectangleSegmentA003)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude004 = extrude(profile004, length = 30)
sketch005 = startSketchOn(extrude002, seg02)
profile005 = startProfileAt([-129.93, -59.19], sketch005)
|> xLine(length = 48.79)
|> line(end = [1.33, 11.03])
|> xLine(length = -60.56, tag = $seg03)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude005 = extrude(profile005, length = -10)
sketch006 = startSketchOn(extrude005, seg03)
profile006 = startProfileAt([-95.86, 38.73], sketch006)
|> angledLine([0, 3.48], %, $rectangleSegmentA004)
|> angledLine([
segAng(rectangleSegmentA004) - 90,
3.36
], %)
|> angledLine([
segAng(rectangleSegmentA004),
-segLen(rectangleSegmentA004)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude006 = extrude(profile006, length = 13)

View File

@ -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], %)

View File

@ -53,7 +53,7 @@ pub trait CoreDump: Clone {
.meta()
.create_debug_uploads(vec![kittycad::types::multipart::Attachment {
name: "".to_string(),
filepath: Some(format!(r#"modeling-app/coredump-{coredump_id}-screenshot.png"#).into()),
filename: Some(format!(r#"modeling-app/coredump-{coredump_id}-screenshot.png"#)),
content_type: Some("image/png".to_string()),
data,
}])
@ -101,7 +101,7 @@ pub trait CoreDump: Clone {
.meta()
.create_debug_uploads(vec![kittycad::types::multipart::Attachment {
name: "".to_string(),
filepath: Some(format!(r#"modeling-app/coredump-{}.json"#, coredump_id).into()),
filename: Some(format!(r#"modeling-app/coredump-{}.json"#, coredump_id)),
content_type: Some("application/json".to_string()),
data,
}])

View File

@ -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>> {

View File

@ -508,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.
@ -528,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 {
@ -535,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}}]"#,
@ -563,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,
}
}

View File

@ -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})${}"#
);
}
@ -1141,7 +1147,7 @@ mod tests {
let snippet = scale_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"scale(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})${}"#
r#"scale(${0:%}, scale = [${1:3.14}, ${2:3.14}, ${3:3.14}])${}"#
);
}
@ -1152,7 +1158,7 @@ mod tests {
let snippet = translate_fn.to_autocomplete_snippet().unwrap();
assert_eq!(
snippet,
r#"translate(${0:%}, x = ${1:3.14}, y = ${2:3.14}, z = ${3:3.14})${}"#
r#"translate(${0:%}, translate = [${1:3.14}, ${2:3.14}, ${3:3.14}])${}"#
);
}

View File

@ -121,13 +121,6 @@ impl EngineConnection {
}
})?;
if value.is_null() || value.is_undefined() {
return Err(KclError::Engine(KclErrorDetails {
message: "Received null or undefined response from engine".into(),
source_ranges: vec![source_range],
}));
}
// Convert JsValue to a Uint8Array
let data = js_sys::Uint8Array::from(value);

View File

@ -6,7 +6,7 @@ use itertools::{EitherOrBoth, Itertools};
use tokio::sync::RwLock;
use crate::{
execution::{annotations, memory::Stack, state::ModuleInfoMap, EnvironmentRef, ExecState, ExecutorSettings},
execution::{annotations, memory::Stack, EnvironmentRef, ExecState, ExecutorSettings},
parsing::ast::types::{Annotation, Node, Program},
walk::Node as WalkNode,
};
@ -15,7 +15,7 @@ lazy_static::lazy_static! {
/// A static mutable lock for updating the last successful execution state for the cache.
static ref OLD_AST: Arc<RwLock<Option<OldAstState>>> = Default::default();
// The last successful run's memory. Not cleared after an unssuccessful run.
static ref PREV_MEMORY: Arc<RwLock<Option<(Stack, ModuleInfoMap)>>> = Default::default();
static ref PREV_MEMORY: Arc<RwLock<Option<Stack>>> = Default::default();
}
/// Read the old ast memory from the lock.
@ -29,12 +29,12 @@ pub(super) async fn write_old_ast(old_state: OldAstState) {
*old_ast = Some(old_state);
}
pub(crate) async fn read_old_memory() -> Option<(Stack, ModuleInfoMap)> {
pub(crate) async fn read_old_memory() -> Option<Stack> {
let old_mem = PREV_MEMORY.read().await;
old_mem.clone()
}
pub(super) async fn write_old_memory(mem: (Stack, ModuleInfoMap)) {
pub(super) async fn write_old_memory(mem: Stack) {
let mut old_mem = PREV_MEMORY.write().await;
*old_mem = Some(mem);
}

View File

@ -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 => {}
}
}

View File

@ -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
}?;

View File

@ -529,18 +529,6 @@ impl ExecutorContext {
.await
}
pub async fn bust_cache_and_reset_scene(&self) -> Result<ExecOutcome, KclErrorWithOutputs> {
cache::bust_cache().await;
// Execute an empty program to clear and reset the scene.
// We specifically want to be returned the objects after the scene is reset.
// Like the default planes so it is easier to just execute an empty program
// after the cache is busted.
let outcome = self.run_with_caching(crate::Program::empty()).await?;
Ok(outcome)
}
async fn prepare_mem(&self, exec_state: &mut ExecState) -> Result<(), KclErrorWithOutputs> {
self.eval_prelude(exec_state, SourceRange::synthetic())
.await
@ -559,10 +547,7 @@ impl ExecutorContext {
let mut exec_state = ExecState::new(self);
if use_prev_memory {
match cache::read_old_memory().await {
Some(mem) => {
*exec_state.mut_stack() = mem.0;
exec_state.global.module_infos = mem.1;
}
Some(mem) => *exec_state.mut_stack() = mem,
None => self.prepare_mem(&mut exec_state).await?,
}
} else {
@ -580,11 +565,10 @@ impl ExecutorContext {
// memory, not to the exec_state which is not cached for mock execution.
let mut mem = exec_state.stack().clone();
let module_infos = exec_state.global.module_infos.clone();
let outcome = exec_state.to_mock_wasm_outcome(result.0).await;
mem.squash_env(result.0);
cache::write_old_memory((mem, module_infos)).await;
cache::write_old_memory(mem).await;
Ok(outcome)
}
@ -774,7 +758,7 @@ impl ExecutorContext {
if !self.is_mock() {
let mut mem = exec_state.stack().deep_clone();
mem.restore_env(env_ref);
cache::write_old_memory((mem, exec_state.global.module_infos.clone())).await;
cache::write_old_memory(mem).await;
}
let session_data = self.engine.get_session_data().await;
Ok((env_ref, session_data))
@ -2060,8 +2044,6 @@ let w = f() + f()
// Ensure the settings are as expected.
assert_eq!(settings_state, ctx.settings);
ctx.close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -2075,9 +2057,6 @@ let w = f() + f()
let program2 = crate::Program::parse_no_errs("z = x + 1").unwrap();
let result = ctx2.run_mock(program2, true).await.unwrap();
assert_eq!(result.variables.get("z").unwrap().as_f64().unwrap(), 3.0);
ctx.close().await;
ctx2.close().await;
}
#[tokio::test(flavor = "multi_thread")]

View File

@ -30,8 +30,6 @@ pub struct ExecState {
pub(super) exec_context: Option<super::ExecutorContext>,
}
pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
#[derive(Debug, Clone)]
pub(super) struct GlobalState {
/// Map from source file absolute path to module ID.
@ -39,7 +37,7 @@ pub(super) struct GlobalState {
/// Map from module ID to source file.
pub id_to_source: IndexMap<ModuleId, ModuleSource>,
/// Map from module ID to module info.
pub module_infos: ModuleInfoMap,
pub module_infos: IndexMap<ModuleId, ModuleInfo>,
/// Output map of UUIDs to artifacts.
pub artifacts: IndexMap<ArtifactId, Artifact>,
/// Output commands to allow building the artifact graph by the caller.

View File

@ -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());
}
}

View File

@ -97,7 +97,7 @@ pub use parsing::ast::{modify::modify_ast_for_sketch, types::FormatOptions};
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
pub use source_range::SourceRange;
#[cfg(not(target_arch = "wasm32"))]
pub use unparser::{recast_dir, walk_dir};
pub use unparser::recast_dir;
// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
// Ideally we wouldn't export these things at all, they should only be used for testing.
@ -211,14 +211,6 @@ impl Program {
pub fn recast_with_options(&self, options: &FormatOptions) -> String {
self.ast.recast(options, 0)
}
/// Create an empty program.
pub fn empty() -> Self {
Self {
ast: parsing::ast::types::Node::no_src(parsing::ast::types::Program::default()),
original_file_contents: String::new(),
}
}
}
#[inline]

View File

@ -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 }, %)
"
);

View File

@ -786,7 +786,7 @@ impl Backend {
vec![kittycad::types::multipart::Attachment {
// Clean the URI part.
name: "attachment".to_string(),
filepath: Some("attachment.zip".into()),
filename: Some("attachment.zip".to_string()),
content_type: Some("application/x-zip".to_string()),
data: self.create_zip().await?,
}],
@ -1635,7 +1635,7 @@ fn position_to_char_index(position: Position, code: &str) -> usize {
async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
let mem = cache::read_old_memory().await?;
let value = mem.0.get(name, SourceRange::default()).ok()?;
let value = mem.get(name, SourceRange::default()).ok()?;
Some(f(value))
}

View File

@ -1016,8 +1016,6 @@ startSketchOn(XY)
}
_ => unreachable!(),
}
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -1634,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)
@ -1649,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],
@ -1673,7 +1671,7 @@ outsideRevolve = startSketchOn(XZ)
|> line(end = [0, thickness])
|> line(end = [overHangLength - thickness, 0])
|> close()
|> revolve({ axis: 'y' }, %)"#
|> revolve({ axis = Y }, %)"#
.to_string(),
},
})
@ -1707,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
}
}
);
@ -1734,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)
@ -1749,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],
@ -1773,7 +1771,7 @@ outsideRevolve = startSketchOn(XZ)
|> line(end = [0, thickness])
|> line(end = [overHangLength - thickness, 0])
|> close()
|> revolve({ axis = 'y' }, %)"#
|> revolve({ axis = Y }, %)"#
);
}
@ -2320,8 +2318,6 @@ async fn kcl_test_kcl_lsp_on_change_update_memory() {
}],
})
.await;
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 10)]
@ -2394,8 +2390,6 @@ part001 = cube([0,0], 20)
let units = server.executor_ctx().await.clone().unwrap().settings.units;
assert_eq!(units, crate::settings::types::UnitLength::M);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -2512,8 +2506,6 @@ async fn kcl_test_kcl_lsp_diagnostics_on_execution_error() {
// Get the diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -2564,8 +2556,6 @@ async fn kcl_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
// Get the ast.
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
assert_eq!(ast.ast, default_hashed);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -2652,8 +2642,6 @@ async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
// Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -2729,8 +2717,6 @@ async fn kcl_test_kcl_lsp_code_and_ast_unchanged_but_has_diagnostics_reexecute()
// Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -2809,8 +2795,6 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_diagnostics_reexe
// Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -2868,8 +2852,6 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_memory_reexecute_
// Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -2993,8 +2975,6 @@ async fn kcl_test_kcl_lsp_cant_execute_set() {
// Assure we have no diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 0);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -3263,8 +3243,6 @@ part001 = startSketchOn(XY)
// Check the diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 2);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -3332,8 +3310,6 @@ NEW_LINT = 1"#
// Check the diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 2);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -3401,8 +3377,6 @@ NEW_LINT = 1"#
// Check the diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -3486,8 +3460,6 @@ NEW_LINT = 1"#
// Check the diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -3579,8 +3551,6 @@ part001 = startSketchOn(XY)
// Check the diagnostics.
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 2);
server.executor_ctx().await.clone().unwrap().close().await;
}
#[tokio::test(flavor = "multi_thread")]
@ -3679,6 +3649,4 @@ async fn kcl_test_kcl_lsp_multi_file_error() {
} else {
panic!("Expected diagnostics");
}
server.executor_ctx().await.clone().unwrap().close().await;
}

View File

@ -133,13 +133,6 @@ impl<T> Node<T> {
})
}
fn reset_source(&mut self) {
self.start = 0;
self.end = 0;
self.module_id = ModuleId::default();
self.comment_start = 0;
}
pub fn as_source_range(&self) -> SourceRange {
SourceRange::new(self.start, self.end, self.module_id)
}
@ -352,10 +345,7 @@ impl Node<Program> {
let mut found = false;
for node in &mut new_program.inner_attrs {
if node.name() == Some(annotations::SETTINGS) {
node.inner = Annotation::new_from_meta_settings(&settings);
// Previous source range no longer makes sense, but we want to
// preserve other things like comments.
node.reset_source();
*node = Node::no_src(Annotation::new_from_meta_settings(&settings));
found = true;
break;
}
@ -4152,50 +4142,6 @@ startSketchOn(XY)
r#"@settings(defaultLengthUnit = mm)
startSketchOn(XY)
"#
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_change_meta_settings_preserves_comments() {
let code = r#"// Title
// Set Units
@settings(defaultLengthUnit = in)
// Between
// Above Code
5
"#;
let program = crate::parsing::top_level_parse(code).unwrap();
let new_program = program
.change_meta_settings(crate::execution::MetaSettings {
default_length_units: crate::execution::types::UnitLen::Cm,
..Default::default()
})
.unwrap();
let result = new_program.meta_settings().unwrap();
assert!(result.is_some());
let meta_settings = result.unwrap();
assert_eq!(meta_settings.default_length_units, crate::execution::types::UnitLen::Cm);
let formatted = new_program.recast(&Default::default(), 0);
assert_eq!(
formatted,
r#"// Title
// Set Units
@settings(defaultLengthUnit = cm)
// Between
// Above Code
5
"#
);
}

View File

@ -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)?;
@ -2333,6 +2338,21 @@ fn nameable_identifier(i: &mut TokenSlice) -> PResult<Node<Identifier>> {
));
}
if let Some(suggestion) = super::deprecation(&result.name, DeprecationKind::Const) {
ParseContext::warn(
CompilationError::err(
result.as_source_range(),
format!("Using `{}` is deprecated, prefer using `{}`.", result.name, suggestion),
)
.with_suggestion(
format!("Replace `{}` with `{}`", result.name, suggestion),
suggestion,
None,
Tag::Deprecated,
),
);
}
Ok(result)
}
@ -2351,7 +2371,8 @@ fn name(i: &mut TokenSlice) -> PResult<Node<Name>> {
let name = idents.pop().unwrap();
let end = name.end;
let module_id = name.module_id;
let result = Node::new(
Ok(Node::new(
Name {
name,
path: idents,
@ -2361,24 +2382,7 @@ fn name(i: &mut TokenSlice) -> PResult<Node<Name>> {
start,
end,
module_id,
);
if let Some(suggestion) = super::deprecation(&result.to_string(), DeprecationKind::Const) {
ParseContext::warn(
CompilationError::err(
result.as_source_range(),
format!("Using `{result}` is deprecated, prefer using `{suggestion}`."),
)
.with_suggestion(
format!("Replace `{result}` with `{suggestion}`"),
suggestion,
None,
Tag::Deprecated,
),
);
}
Ok(result)
))
}
impl TryFrom<Token> for Node<TagDeclarator> {
@ -2903,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),
@ -2934,6 +2942,7 @@ fn parameter(i: &mut TokenSlice) -> PResult<ParamDescription> {
return Err(ErrMode::Backtrack(ContextError::from(e)));
}
},
attr,
comments,
})
}
@ -2955,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)?;
@ -2962,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,

View File

@ -114,8 +114,6 @@ async fn unparse_test(test: &Test) {
let kcl_files = kcl_files.into_iter().filter(|f| f != &entry_point);
let futures = kcl_files
.into_iter()
.filter(|file| file.extension().is_some_and(|ext| ext == "kcl")) // We only care about kcl
// files here.
.map(|file| {
let snap_path = Path::new("..").join(&test.output_dir);
tokio::spawn(async move {
@ -2381,66 +2379,3 @@ mod rotate_after_fillet {
super::execute(TEST_NAME, true).await
}
}
mod union_cubes {
const TEST_NAME: &str = "union_cubes";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}
mod subtract_cylinder_from_cube {
const TEST_NAME: &str = "subtract_cylinder_from_cube";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}
mod intersect_cubes {
const TEST_NAME: &str = "intersect_cubes";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}

View File

@ -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,

View File

@ -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))
}
}

View File

@ -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]
})
);
}
}

View File

@ -45,7 +45,7 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// ```
#[stdlib {
name = "union",
feature_tree_operation = true,
feature_tree_operation = false,
keywords = true,
unlabeled_first = true,
deprecated = true,
@ -107,7 +107,7 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
/// ```
#[stdlib {
name = "intersect",
feature_tree_operation = true,
feature_tree_operation = false,
keywords = true,
unlabeled_first = true,
deprecated = true,
@ -164,7 +164,7 @@ pub async fn subtract(exec_state: &mut ExecState, args: Args) -> Result<KclValue
/// ```
#[stdlib {
name = "subtract",
feature_tree_operation = true,
feature_tree_operation = false,
keywords = true,
unlabeled_first = true,
deprecated = true,

View File

@ -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?;

View File

@ -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?;

View File

@ -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!(),
}
}

View File

@ -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,
}),

View File

@ -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], %)

View File

@ -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')

View File

@ -28,22 +28,15 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
]),
exec_state,
)?;
let scale_x = args.get_kw_arg("x")?;
let scale_y = args.get_kw_arg("y")?;
let scale_z = args.get_kw_arg("z")?;
let scale = args.get_kw_arg("scale")?;
let global = args.get_kw_arg_opt("global")?;
let objects = inner_scale(objects, scale_x, scale_y, scale_z, global, exec_state, args).await?;
let objects = inner_scale(objects, scale, global, exec_state, args).await?;
Ok(objects.into())
}
/// Scale a solid or a sketch.
///
/// This is really useful for resizing parts. You can create a part and then scale it to the
/// correct size.
///
/// For sketches, you can use this to scale a sketch and then loft it with another sketch.
///
/// By default the transform is applied in local sketch axis, therefore the origin will not move.
///
/// If you want to apply the transform in global space, set `global` to `true`. The origin of the
@ -85,9 +78,7 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// |> hole(pipeHole, %)
/// |> sweep(path = sweepPath)
/// |> scale(
/// x = 1.0,
/// y = 1.0,
/// z = 2.5,
/// scale = [1.0, 1.0, 2.5],
/// )
/// ```
///
@ -98,9 +89,7 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
///
/// cube
/// |> scale(
/// x = 1.0,
/// y = 1.0,
/// z = 2.5,
/// scale = [1.0, 1.0, 2.5],
/// )
/// ```
///
@ -135,7 +124,7 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// parts = sweep([rectangleSketch, circleSketch], path = sweepPath)
///
/// // Scale the sweep.
/// scale(parts, x = 1.0, y = 1.0, z = 0.5)
/// scale(parts, scale = [1.0, 1.0, 0.5])
/// ```
#[stdlib {
name = "scale",
@ -144,17 +133,13 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
unlabeled_first = true,
args = {
objects = {docs = "The solid, sketch, or set of solids or sketches to scale."},
x = {docs = "The scale factor for the x axis."},
y = {docs = "The scale factor for the y axis."},
z = {docs = "The scale factor for the z axis."},
scale = {docs = "The scale factor for the x, y, and z axes."},
global = {docs = "If true, the transform is applied in global space. The origin of the model will move. By default, the transform is applied in local sketch axis, therefore the origin will not move."}
}
}]
async fn inner_scale(
objects: SolidOrSketchOrImportedGeometry,
x: f64,
y: f64,
z: f64,
scale: [f64; 3],
global: Option<bool>,
exec_state: &mut ExecState,
args: Args,
@ -174,7 +159,11 @@ async fn inner_scale(
object_id,
transforms: vec![shared::ComponentTransform {
scale: Some(shared::TransformBy::<Point3d<f64>> {
property: Point3d { x, y, z },
property: Point3d {
x: scale[0],
y: scale[1],
z: scale[2],
},
set: false,
is_local: !global.unwrap_or(false),
}),
@ -201,23 +190,15 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
]),
exec_state,
)?;
let translate_x = args.get_kw_arg("x")?;
let translate_y = args.get_kw_arg("y")?;
let translate_z = args.get_kw_arg("z")?;
let translate = args.get_kw_arg("translate")?;
let global = args.get_kw_arg_opt("global")?;
let objects = inner_translate(objects, translate_x, translate_y, translate_z, global, exec_state, args).await?;
let objects = inner_translate(objects, translate, global, exec_state, args).await?;
Ok(objects.into())
}
/// Move a solid or a sketch.
///
/// This is really useful for assembling parts together. You can create a part
/// and then move it to the correct location.
///
/// Translate is really useful for sketches if you want to move a sketch
/// and then rotate it using the `rotate` function to create a loft.
///
/// ```no_run
/// // Move a pipe.
///
@ -251,9 +232,7 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
/// |> hole(pipeHole, %)
/// |> sweep(path = sweepPath)
/// |> translate(
/// x = 1.0,
/// y = 1.0,
/// z = 2.5,
/// translate = [1.0, 1.0, 2.5],
/// )
/// ```
///
@ -264,9 +243,7 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
///
/// cube
/// |> translate(
/// x = 1.0,
/// y = 1.0,
/// z = 2.5,
/// translate = [1.0, 1.0, 2.5],
/// )
/// ```
///
@ -301,7 +278,7 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
/// parts = sweep([rectangleSketch, circleSketch], path = sweepPath)
///
/// // Move the sweeps.
/// translate(parts, x = 1.0, y = 1.0, z = 2.5)
/// translate(parts, translate = [1.0, 1.0, 2.5])
/// ```
///
/// ```no_run
@ -324,9 +301,7 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
///
/// square(10)
/// |> translate(
/// x = 5,
/// y = 5,
/// z = 0,
/// translate = [5, 5, 0],
/// )
/// |> extrude(
/// length = 10,
@ -349,7 +324,7 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
/// profile001 = square()
///
/// profile002 = square()
/// |> translate(x = 0, y = 0, z = 20)
/// |> translate(translate = [0, 0, 20])
/// |> rotate(axis = [0, 0, 1.0], angle = 45)
///
/// loft([profile001, profile002])
@ -361,17 +336,13 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
unlabeled_first = true,
args = {
objects = {docs = "The solid, sketch, or set of solids or sketches to move."},
x = {docs = "The amount to move the solid or sketch along the x axis."},
y = {docs = "The amount to move the solid or sketch along the y axis."},
z = {docs = "The amount to move the solid or sketch along the z axis."},
translate = {docs = "The amount to move the solid or sketch in all three axes."},
global = {docs = "If true, the transform is applied in global space. The origin of the model will move. By default, the transform is applied in local sketch axis, therefore the origin will not move."}
}
}]
async fn inner_translate(
objects: SolidOrSketchOrImportedGeometry,
x: f64,
y: f64,
z: f64,
translate: [f64; 3],
global: Option<bool>,
exec_state: &mut ExecState,
args: Args,
@ -392,9 +363,9 @@ async fn inner_translate(
transforms: vec![shared::ComponentTransform {
translate: Some(shared::TransformBy::<Point3d<LengthUnit>> {
property: shared::Point3d {
x: LengthUnit(x),
y: LengthUnit(y),
z: LengthUnit(z),
x: LengthUnit(translate[0]),
y: LengthUnit(translate[1]),
z: LengthUnit(translate[2]),
},
set: false,
is_local: !global.unwrap_or(false),
@ -535,11 +506,6 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// Rotate a solid or a sketch.
///
/// This is really useful for assembling parts together. You can create a part
/// and then rotate it to the correct orientation.
///
/// For sketches, you can use this to rotate a sketch and then loft it with another sketch.
///
/// ### Using Roll, Pitch, and Yaw
///
/// When rotating a part in 3D space, "roll," "pitch," and "yaw" refer to the
@ -701,7 +667,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// profile001 = square()
///
/// profile002 = square()
/// |> translate(x = 0, y = 0, z = 20)
/// |> translate(translate = [0, 0, 20])
/// |> rotate(axis = [0, 0, 1.0], angle = 45)
///
/// loft([profile001, profile002])

View File

@ -1 +0,0 @@

View File

@ -1,8 +1,5 @@
use std::fmt::Write;
#[cfg(feature = "cli")]
use clap::ValueEnum;
use crate::parsing::{
ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
@ -867,33 +864,10 @@ impl Parameter {
}
}
lazy_static::lazy_static! {
pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
#[cfg(feature = "cli")]
let named_extensions = kittycad::types::FileImportFormat::value_variants()
.iter()
.map(|x| format!("{}", x))
.collect::<Vec<String>>();
#[cfg(not(feature = "cli"))]
let named_extensions = vec![]; // We don't really need this outside of the CLI.
// Add all the default import formats.
import_file_extensions.extend_from_slice(&named_extensions);
import_file_extensions
};
pub static ref RELEVANT_EXTENSIONS: Vec<String> = {
let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
relevant_extensions.push("kcl".to_string());
relevant_extensions
};
}
/// Collect all the kcl (and other relevant) files in a directory, recursively.
/// Collect all the kcl files in a directory, recursively.
#[cfg(not(target_arch = "wasm32"))]
#[async_recursion::async_recursion]
pub async fn walk_dir(dir: &std::path::PathBuf) -> Result<Vec<std::path::PathBuf>, anyhow::Error> {
pub(crate) async fn walk_dir(dir: &std::path::PathBuf) -> Result<Vec<std::path::PathBuf>, anyhow::Error> {
// Make sure we actually have a directory.
if !dir.is_dir() {
anyhow::bail!("`{}` is not a directory", dir.display());
@ -907,10 +881,7 @@ pub async fn walk_dir(dir: &std::path::PathBuf) -> Result<Vec<std::path::PathBuf
if path.is_dir() {
files.extend(walk_dir(&path).await?);
} else if path
.extension()
.is_some_and(|ext| RELEVANT_EXTENSIONS.contains(&ext.to_string_lossy().to_string()))
{
} else if path.extension().is_some_and(|ext| ext == "kcl") {
files.push(path);
}
}
@ -930,8 +901,6 @@ pub async fn recast_dir(dir: &std::path::Path, options: &crate::FormatOptions) -
let futures = files
.into_iter()
.filter(|file| file.extension().is_some_and(|ext| ext == "kcl")) // We only care about kcl
// files here.
.map(|file| {
let options = options.clone();
tokio::spawn(async move {
@ -1266,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)
@ -1281,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],
@ -1305,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);
@ -1332,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)
@ -1347,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],
@ -1371,7 +1340,7 @@ outsideRevolve = startSketchOn(XZ)
|> line([0, thickness], %)
|> line([overHangLength - thickness, 0], %)
|> close()
|> revolve({ axis = 'y' }, %)
|> revolve({ axis = Y }, %)
"#
);
}
@ -1644,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], %)
@ -1676,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], %)
@ -2559,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 }
}
})
"#;

View File

@ -234,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
@ -266,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 {}

View File

@ -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 {}

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