From a4db302174bdf1321e1d0eab858261c726ca3650 Mon Sep 17 00:00:00 2001 From: Jess Frazelle Date: Sat, 1 Mar 2025 16:38:25 -0800 Subject: [PATCH] move kcl.py to this repo (#5587) * ci for kcl-python-bindings Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * bettter concurrency Signed-off-by: Jess Frazelle * cleanup files Signed-off-by: Jess Frazelle * updates Signed-off-by: Jess Frazelle * updaets Signed-off-by: Jess Frazelle * updaets Signed-off-by: Jess Frazelle * updaets Signed-off-by: Jess Frazelle * updaets Signed-off-by: Jess Frazelle * format Signed-off-by: Jess Frazelle * format Signed-off-by: Jess Frazelle --------- Signed-off-by: Jess Frazelle --- .github/dependabot.yml | 10 +- .github/workflows/kcl-python-bindings.yml | 179 ++++++ .github/workflows/ruff.yml | 24 + .gitignore | 7 +- ...ld-be-stable--XZ-1-Google-Chrome-linux.png | Bin 56117 -> 57302 bytes rust/Cargo.lock | 20 +- rust/Cargo.toml | 7 +- rust/justfile | 4 +- rust/kcl-derive-docs/Cargo.toml | 2 +- rust/kcl-lib/Cargo.toml | 8 +- rust/kcl-lib/README.md | 2 +- rust/kcl-python-bindings/Cargo.toml | 26 + rust/kcl-python-bindings/README.md | 39 ++ .../files/box_with_linter_errors.kcl | 14 + .../files/parse_file_error/main.kcl | 1 + .../files/parse_file_error/thing.kcl | 1 + .../files/walkie-talkie/antenna.kcl | 50 ++ .../files/walkie-talkie/body.kcl | 80 +++ .../files/walkie-talkie/button.kcl | 38 ++ .../files/walkie-talkie/case.kcl | 85 +++ .../files/walkie-talkie/globals.kcl | 42 ++ .../files/walkie-talkie/knob.kcl | 38 ++ .../files/walkie-talkie/main.kcl | 50 ++ .../files/walkie-talkie/talk-button.kcl | 46 ++ .../files/walkie-talkie/zoo-logo.kcl | 83 +++ rust/kcl-python-bindings/justfile | 8 + rust/kcl-python-bindings/pyproject.toml | 22 + rust/kcl-python-bindings/src/lib.rs | 525 ++++++++++++++++++ rust/kcl-python-bindings/tests/tests.py | 140 +++++ rust/kcl-to-core/Cargo.toml | 4 +- 30 files changed, 1541 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/kcl-python-bindings.yml create mode 100644 .github/workflows/ruff.yml create mode 100644 rust/kcl-python-bindings/Cargo.toml create mode 100644 rust/kcl-python-bindings/README.md create mode 100644 rust/kcl-python-bindings/files/box_with_linter_errors.kcl create mode 100644 rust/kcl-python-bindings/files/parse_file_error/main.kcl create mode 100644 rust/kcl-python-bindings/files/parse_file_error/thing.kcl create mode 100644 rust/kcl-python-bindings/files/walkie-talkie/antenna.kcl create mode 100644 rust/kcl-python-bindings/files/walkie-talkie/body.kcl create mode 100644 rust/kcl-python-bindings/files/walkie-talkie/button.kcl create mode 100644 rust/kcl-python-bindings/files/walkie-talkie/case.kcl create mode 100644 rust/kcl-python-bindings/files/walkie-talkie/globals.kcl create mode 100644 rust/kcl-python-bindings/files/walkie-talkie/knob.kcl create mode 100644 rust/kcl-python-bindings/files/walkie-talkie/main.kcl create mode 100644 rust/kcl-python-bindings/files/walkie-talkie/talk-button.kcl create mode 100644 rust/kcl-python-bindings/files/walkie-talkie/zoo-logo.kcl create mode 100644 rust/kcl-python-bindings/justfile create mode 100644 rust/kcl-python-bindings/pyproject.toml create mode 100644 rust/kcl-python-bindings/src/lib.rs create mode 100755 rust/kcl-python-bindings/tests/tests.py diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1af3d4078..299d2a1d5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -25,7 +25,7 @@ updates: - adamchalmers - jessfraz - package-ecosystem: 'cargo' # See documentation for possible values - directory: '/src/wasm-lib/' # Location of package manifests + directory: '/rust/' # Location of package manifests schedule: interval: weekly day: monday @@ -39,3 +39,11 @@ updates: wasm-bindgen-deps: patterns: - "wasm-bindgen*" + - package-ecosystem: "pip" # See documentation for possible values + directory: "/rust/kcl-python-bindings/" # Location of package manifests + schedule: + interval: weekly + day: monday + reviewers: + - adamchalmers + - jessfraz diff --git a/.github/workflows/kcl-python-bindings.yml b/.github/workflows/kcl-python-bindings.yml new file mode 100644 index 000000000..e972a090d --- /dev/null +++ b/.github/workflows/kcl-python-bindings.yml @@ -0,0 +1,179 @@ +# This file is autogenerated by maturin v1.6.0 and then modified +# To update, run +# +# maturin generate-ci github +# +name: kcl-python-bindings + +on: + push: + branches: + - main + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + - '**/rust-toolchain.toml' + - 'rust/kcl-python-bindings/**' + - '**.rs' + - .github/workflows/kcl-python-bindings.yml + tags: + - 'kcl-*' + pull_request: + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + - '**/rust-toolchain.toml' + - 'rust/kcl-python-bindings/**' + - '**.rs' + - .github/workflows/kcl-python-bindings.yml + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + linux-x86_64: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: rust/kcl-python-bindings + target: x86_64 + args: --release --out dist --find-interpreter + sccache: 'true' + manylinux: auto + before-script-linux: | + yum install openssl-devel -y + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-x86_64 + path: rust/kcl-python-bindings/dist + + windows: + runs-on: windows-16-cores + strategy: + matrix: + target: + - x64 + fail-fast: false + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + architecture: ${{ matrix.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: rust/kcl-python-bindings + target: ${{ matrix.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-windows-${{ matrix.target }} + path: rust/kcl-python-bindings/dist + + macos: + runs-on: macos-latest + strategy: + matrix: + target: + - x86_64 + - aarch64 + fail-fast: false + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + working-directory: rust/kcl-python-bindings + target: ${{ matrix.target }} + args: --release --out dist --find-interpreter + sccache: 'true' + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.target }} + path: rust/kcl-python-bindings/dist + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install uv + uses: astral-sh/setup-uv@v5 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + - uses: taiki-e/install-action@just + - name: Run tests + run: | + cd rust/kcl-python-bindings + just setup-uv + just test + env: + KITTYCAD_API_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN }} + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v5 + - name: Install codespell + run: | + uv venv .venv + echo "VIRTUAL_ENV=.venv" >> $GITHUB_ENV + echo "$PWD/.venv/bin" >> $GITHUB_PATH + uv pip install pip --upgrade + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + working-directory: rust/kcl-python-bindings + command: sdist + args: --out dist + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: wheels-sdist + path: rust/kcl-python-bindings/dist + + release: + name: Release + runs-on: ubuntu-latest + permissions: + contents: write + if: startsWith(github.ref, 'refs/tags/') + needs: [linux-x86_64, windows, macos, sdist] + steps: + - uses: actions/download-artifact@v4 + - name: Install the latest version of uv + uses: astral-sh/setup-uv@v5 + - name: Install codespell + run: | + cd rust/kcl-python-bindings + uv venv .venv + echo "VIRTUAL_ENV=.venv" >> $GITHUB_ENV + echo "$PWD/.venv/bin" >> $GITHUB_PATH + uv pip install pip --upgrade + - name: Publish to PyPI + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + working-directory: rust/kcl-python-bindings + command: upload + args: --non-interactive --skip-existing wheels-*/* diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml new file mode 100644 index 000000000..be1d46285 --- /dev/null +++ b/.github/workflows/ruff.yml @@ -0,0 +1,24 @@ +name: ruff +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true +on: + push: + branches: main + paths: + - '**.py' + - .github/workflows/ruff.yml + pull_request: + paths: + - '**.py' + - .github/workflows/ruff.yml +permissions: + contents: read + pull-requests: write +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/ruff-action@v3 + diff --git a/.gitignore b/.gitignore index b08188e19..14ef0ac3d 100644 --- a/.gitignore +++ b/.gitignore @@ -63,8 +63,13 @@ Mac_App_Distribution.provisionprofile *.tsbuildinfo .eslintcache -venv .vite/ # electron out/ + +# python +__pycache__/ +uv.lock +rust/kcl-python-bindings/dist +venv diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XZ-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable--XZ-1-Google-Chrome-linux.png index 9e3d0691bad2509b14bb258950295f88127d089b..6f6d302758bd57a1d9170d78eefd63b840d735f3 100644 GIT binary patch literal 57302 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 literal 56117 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 E2OeeQLI3~& diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 4e4da8656..82424bdbe 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1744,6 +1744,21 @@ dependencies = [ "zip", ] +[[package]] +name = "kcl-python-bindings" +version = "0.3.45" +dependencies = [ + "anyhow", + "kcl-lib", + "kittycad-modeling-cmds", + "miette", + "pyo3", + "serde", + "serde_json", + "tokio", + "uuid", +] + [[package]] name = "kcl-test-server" version = "0.1.45" @@ -1833,9 +1848,9 @@ dependencies = [ [[package]] name = "kittycad-modeling-cmds" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "828a0c74476533e6258ea7dd70cfc7d63a5df4b37753d30ef198e0689eaac4eb" +checksum = "fb5a824cb9bb4c602962ecbaca5ce71225938aa1abc24103bf46c222f468dd26" dependencies = [ "anyhow", "chrono", @@ -2530,6 +2545,7 @@ dependencies = [ "pyo3-build-config", "pyo3-ffi", "pyo3-macros", + "serde", "unindent", ] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index df4f3454a..cd9ff4035 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "kcl-derive-docs", "kcl-lib", + "kcl-python-bindings", "kcl-test-server", "kcl-to-core", "kcl-wasm-lib" @@ -23,10 +24,14 @@ similar = { opt-level = 3 } debug = "line-tables-only" [workspace.dependencies] +async-trait = "0.1.85" +anyhow = { version = "1" } http = "1" indexmap = "2.7.0" kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] } -kittycad-modeling-cmds = { version = "0.2.99", features = ["ts-rs", "websocket"] } +kittycad-modeling-cmds = { version = "0.2.100", features = ["ts-rs", "websocket"] } +miette = "7.5.0" +pyo3 = { version = "0.22.6" } serde = { version = "1", features = ["derive"] } serde_json = { version = "1" } tokio = { version = "1" } diff --git a/rust/justfile b/rust/justfile index 24fa377e7..25920beb6 100644 --- a/rust/justfile +++ b/rust/justfile @@ -46,7 +46,9 @@ test: publish-kcl version: git tag kcl-{{version}} - git push origin kcl-{{version}} cargo publish -p kcl-derive-docs cargo publish -p kcl-lib cargo publish -p kcl-test-server + # We push the tag at the end of publish since pushing the tag + # will trigger CI to release the kcl python bindings. + git push origin kcl-{{version}} diff --git a/rust/kcl-derive-docs/Cargo.toml b/rust/kcl-derive-docs/Cargo.toml index 61a95bb67..522a7d895 100644 --- a/rust/kcl-derive-docs/Cargo.toml +++ b/rust/kcl-derive-docs/Cargo.toml @@ -23,7 +23,7 @@ serde_tokenstream = "0.2" syn = { version = "2.0.96", features = ["full"] } [dev-dependencies] -anyhow = "1.0.95" +anyhow = { workspace = true } expectorate = "1.1.0" pretty_assertions = "1.4.1" rustfmt-wrapper = "0.2.1" diff --git a/rust/kcl-lib/Cargo.toml b/rust/kcl-lib/Cargo.toml index cdf62ecbc..c80edc418 100644 --- a/rust/kcl-lib/Cargo.toml +++ b/rust/kcl-lib/Cargo.toml @@ -11,9 +11,9 @@ keywords = ["kcl", "KittyCAD", "CAD"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -anyhow = { version = "1.0.95", features = ["backtrace"] } +anyhow = { workspace = true, features = ["backtrace"] } async-recursion = "1.1.1" -async-trait = "0.1.85" +async-trait = {workspace = true} base64 = "0.22.1" chrono = "0.4.38" clap = { version = "4.5.27", default-features = false, optional = true, features = [ @@ -37,10 +37,10 @@ kittycad = { workspace = true } kittycad-modeling-cmds = { workspace = true } lazy_static = "1.5.0" measurements = "0.11.0" -miette = "7.5.0" +miette = { workspace = true } mime_guess = "2.0.5" parse-display = "0.9.1" -pyo3 = { version = "0.22.6", optional = true } +pyo3 = { workspace = true, optional = true } regex = "1.11.1" reqwest = { version = "0.12", default-features = false, features = [ "stream", diff --git a/rust/kcl-lib/README.md b/rust/kcl-lib/README.md index 0d4e6577b..b5e8cb6eb 100644 --- a/rust/kcl-lib/README.md +++ b/rust/kcl-lib/README.md @@ -1,4 +1,4 @@ -# KCL +# kcl-lib Our language for defining geometry and working with our Geometry Engine efficiently. Short for KittyCAD Language, named after our Design API. diff --git a/rust/kcl-python-bindings/Cargo.toml b/rust/kcl-python-bindings/Cargo.toml new file mode 100644 index 000000000..6c2762b9d --- /dev/null +++ b/rust/kcl-python-bindings/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "kcl-python-bindings" +version = "0.3.45" +edition = "2021" +repository = "https://github.com/kittycad/modeling-app" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "kcl" +crate-type = ["cdylib"] + +[dependencies] +anyhow = { workspace = true } +kcl-lib = { path = "../kcl-lib", features = [ + "pyo3", + "engine", + "disable-println", +] } +#kcl-lib = { path = "../modeling-app/src/wasm-lib/kcl", default-features = false, features = ["pyo3", "engine", "disable-println"] } +kittycad-modeling-cmds = { workspace = true } +miette = { workspace = true, features = ["fancy"] } +pyo3 = { workspace = true, features = ["serde", "experimental-async"] } +serde = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true } +uuid = { workspace = true, features = ["v4"] } diff --git a/rust/kcl-python-bindings/README.md b/rust/kcl-python-bindings/README.md new file mode 100644 index 000000000..cfafaae93 --- /dev/null +++ b/rust/kcl-python-bindings/README.md @@ -0,0 +1,39 @@ +# kcl-python-bindings + +Python bindings to the rust kcl-lib crate. + +## Usage + +The [tests.py](tests/tests.py) file contains examples of how to use the library. + +## Development + +We use [maturin](https://github.com/PyO3/maturin) for this project. + +You can either download binaries from the [latest release](https://github.com/PyO3/maturin/releases/latest) or install it with [pipx](https://pypa.github.io/pipx/): + +```shell +pipx install maturin +``` + +> [!NOTE] +> +> `pip install maturin` should also work if you don't want to use pipx. + +There are four main commands: + +- `maturin publish` builds the crate into python packages and publishes them to pypi. +- `maturin build` builds the wheels and stores them in a folder (`target/wheels` by default), but doesn't upload them. It's possible to upload those with [twine](https://github.com/pypa/twine) or `maturin upload`. +- `maturin develop` builds the crate and installs it as a python module directly in the current virtualenv. Note that while `maturin develop` is faster, it doesn't support all the feature that running `pip install` after `maturin build` supports. + +`pyo3` bindings are automatically detected. +`maturin` doesn't need extra configuration files and doesn't clash with an existing setuptools-rust or milksnake configuration. + +### Releasing a new version + +1. Make sure the `Cargo.toml` has the new version you want to release. +2. Run `make tag` this is just an easy command for making a tag formatted + correctly with the version. +3. Push the tag (the result of `make tag` gives instructions for this) +4. Everything else is triggered from the tag push. Just make sure all the tests + pass on the `main` branch before making and pushing a new tag. diff --git a/rust/kcl-python-bindings/files/box_with_linter_errors.kcl b/rust/kcl-python-bindings/files/box_with_linter_errors.kcl new file mode 100644 index 000000000..c9080939b --- /dev/null +++ b/rust/kcl-python-bindings/files/box_with_linter_errors.kcl @@ -0,0 +1,14 @@ +// A 25x25x50 box + +const box_width = 25 +const box_depth = 25 +const box_height = 50 + +const box_sketch = startSketchOn('XY') + |> startProfileAt([0, 0], %) + |> xLine(box_width, %, $line1) + |> yLine(box_depth, %, $line2) + |> xLineTo(profileStartX(%), %, $line3) + |> close(%, $line4) + +const box3D = extrude(box_sketch, length = box_height) \ No newline at end of file diff --git a/rust/kcl-python-bindings/files/parse_file_error/main.kcl b/rust/kcl-python-bindings/files/parse_file_error/main.kcl new file mode 100644 index 000000000..b13fa34ba --- /dev/null +++ b/rust/kcl-python-bindings/files/parse_file_error/main.kcl @@ -0,0 +1 @@ +import thing from 'thing.kcl' diff --git a/rust/kcl-python-bindings/files/parse_file_error/thing.kcl b/rust/kcl-python-bindings/files/parse_file_error/thing.kcl new file mode 100644 index 000000000..47b209720 --- /dev/null +++ b/rust/kcl-python-bindings/files/parse_file_error/thing.kcl @@ -0,0 +1 @@ +lksjndflsskjfnak;jfna## diff --git a/rust/kcl-python-bindings/files/walkie-talkie/antenna.kcl b/rust/kcl-python-bindings/files/walkie-talkie/antenna.kcl new file mode 100644 index 000000000..291c77098 --- /dev/null +++ b/rust/kcl-python-bindings/files/walkie-talkie/antenna.kcl @@ -0,0 +1,50 @@ +// Antenna + + +// Set units +@settings(defaultLengthUnit = in) + + +// import constants +import height, width, antennaBaseWidth, antennaBaseHeight, antennaTopWidth, antennaTopHeight from "globals.kcl" + +// Calculate the origin +origin = [-width / 2 + .45, -0.10] + +// Create the antenna +antennaX = origin[0] +antennaY = origin[1] + +antennaPlane = { + plane = { + origin = { x = 0, y = 0, z = height / 2 }, + xAxis = { x = 1, y = 0, z = 0 }, + yAxis = { x = 0, y = 1, z = 0 }, + zAxis = { x = 0, y = 0, z = 1 } + } +} + +// Create the antenna base sketch +sketch001 = startSketchOn(antennaPlane) + |> startProfileAt([origin[0], origin[1]], %) + |> line(end = [antennaBaseWidth, 0]) + |> line(end = [0, -antennaBaseHeight]) + |> line(end = [-antennaBaseWidth, 0]) + |> close() + +// Create the antenna top sketch +loftPlane = offsetPlane('XY', offset = height / 2 + 3) + +sketch002 = startSketchOn(loftPlane) + |> startProfileAt([ + origin[0] + (antennaBaseWidth - antennaTopWidth) / 2, + origin[1] - ((antennaBaseHeight - antennaTopHeight) / 2) + ], %) + |> xLine(antennaTopWidth, %) + |> yLine(-antennaTopHeight, %) + |> xLine(-antennaTopWidth, %) + |> close() + +// Create the antenna using a loft +loft([sketch001, sketch002]) + |> appearance(color = "#000000") diff --git a/rust/kcl-python-bindings/files/walkie-talkie/body.kcl b/rust/kcl-python-bindings/files/walkie-talkie/body.kcl new file mode 100644 index 000000000..605e0eb12 --- /dev/null +++ b/rust/kcl-python-bindings/files/walkie-talkie/body.kcl @@ -0,0 +1,80 @@ +// Walkie talkie body + + +// Set units +@settings(defaultLengthUnit = in) + + +// Import constants +import height, width, thickness, chamferLength, offset, screenWidth, screenHeight, screenYPosition, screenDepth, speakerBoxWidth, speakerBoxHeight from "globals.kcl" + +bodySketch = startSketchOn('XZ') + |> startProfileAt([-width / 2, height / 2], %) + |> xLine(width, %, $chamfer1) + |> yLine(-height, %, $chamfer2) + |> xLine(-width, %, $chamfer3) + |> close(tag = $chamfer4) +bodyExtrude = extrude(bodySketch, length = thickness) + |> chamfer( + length = chamferLength, + tags = [ + getNextAdjacentEdge(chamfer1), + getNextAdjacentEdge(chamfer2), + getNextAdjacentEdge(chamfer3), + getNextAdjacentEdge(chamfer4) + ] + ) + +// Define the offset for the indentation +sketch002 = startSketchOn(bodyExtrude, 'END') + |> startProfileAt([ + -width / 2 + offset, + height / 2 - (chamferLength + offset / 2 * cos(toRadians(45))) + ], %) + |> angledLineToY({ angle = 45, to = height / 2 - offset }, %) + |> line(endAbsolute = [ + width / 2 - (chamferLength + offset / 2 * cos(toRadians(45))), + height / 2 - offset + ]) + |> angledLineToX({ angle = -45, to = width / 2 - offset }, %) + |> line(endAbsolute = [ + width / 2 - offset, + -(height / 2 - (chamferLength + offset / 2 * cos(toRadians(45)))) + ]) + |> angledLineToY({ + angle = -135, + to = -height / 2 + offset + }, %) + |> line(endAbsolute = [ + -(width / 2 - (chamferLength + offset / 2 * cos(toRadians(45)))), + -height / 2 + offset + ]) + |> angledLineToX({ + angle = -225, + to = -width / 2 + offset + }, %) + |> close() +extrude002 = extrude(sketch002, length = -0.0625) + +// Create the pocket for the screen +sketch003 = startSketchOn(extrude002, 'start') + |> startProfileAt([-screenWidth / 2, screenYPosition], %) + |> xLine(screenWidth, %, $seg01) + |> yLine(-screenHeight, %) + |> xLine(-segLen(seg01), %) + |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) + |> close() +extrude003 = extrude(sketch003, length = screenDepth) + +// Create the speaker box +sketch004 = startSketchOn(extrude002, 'start') + |> startProfileAt([-1.25 / 2, -.125], %) + |> xLine(speakerBoxWidth, %) + |> yLine(-speakerBoxHeight, %) + |> xLine(-speakerBoxWidth, %) + |> close() +extrude(sketch004, length = -.5) + |> appearance( + color = "#277bb0", + ) + diff --git a/rust/kcl-python-bindings/files/walkie-talkie/button.kcl b/rust/kcl-python-bindings/files/walkie-talkie/button.kcl new file mode 100644 index 000000000..7011a77f1 --- /dev/null +++ b/rust/kcl-python-bindings/files/walkie-talkie/button.kcl @@ -0,0 +1,38 @@ +// Walkie Talkie button + +// Set units +@settings(defaultLengthUnit = in) + +// Import constants +import screenHeight, buttonWidth, tolerance, buttonHeight, buttonThickness from 'globals.kcl' + + +// Create a function for the button +export fn button(origin, rotation, plane) { + buttonSketch = startSketchOn(plane) + |> startProfileAt([origin[0], origin[1]], %) + |> angledLine({ + angle = 180 + rotation, + length = buttonWidth + }, %, $tag1) + |> angledLine({ + angle = 270 + rotation, + length = buttonHeight + }, %, $tag2) + |> angledLine({ + angle = 0 + rotation, + length = buttonWidth + }, %) + |> close() + buttonExtrude = extrude(buttonSketch, length = buttonThickness) + |> chamfer( + length = .050, + tags = [ + getNextAdjacentEdge(tag1), + getNextAdjacentEdge(tag2) + ] + ) + |> appearance(color = "#ff0000") + + return buttonExtrude +} diff --git a/rust/kcl-python-bindings/files/walkie-talkie/case.kcl b/rust/kcl-python-bindings/files/walkie-talkie/case.kcl new file mode 100644 index 000000000..69a806cc0 --- /dev/null +++ b/rust/kcl-python-bindings/files/walkie-talkie/case.kcl @@ -0,0 +1,85 @@ +// Walkie talkie case + + +// Set units +@settings(defaultLengthUnit = in) + + +// Import constants and Zoo logo +import width, height, chamferLength, offset, screenWidth, screenHeight, screenYPosition, screenDepth, speakerBoxWidth, speakerBoxHeight, squareHoleSideLength, caseTolerance from "globals.kcl" +import zLogo, oLogo, oLogo2 from "zoo-logo.kcl" + +plane = offsetPlane("XZ", offset = 1) + +fn screenHole(sketchStart) { + sketch006 = startSketchOn(sketchStart) + |> startProfileAt([-screenWidth / 2, screenYPosition], %) + |> xLine(screenWidth, %) + |> yLine(-screenHeight, %) + |> xLine(-screenWidth, %) + |> line(endAbsolute = [profileStartX(%), profileStartY(%)]) + |> close() + return sketch006 +} + +fn squareHolePattern(plane, x, y) { + fn transformX(i) { + return { translate = [.125 * i, 0] } + } + fn transformY(i) { + return { translate = [0, -.125 * i] } + } + squareHolePatternSketch = startSketchOn(plane) + |> startProfileAt([-x, -y], %) + |> line(end = [squareHoleSideLength / 2, 0]) + |> line(end = [0, -squareHoleSideLength / 2]) + |> line(end = [-squareHoleSideLength / 2, 0]) + |> close() + |> patternTransform2d(instances = 13, transform = transformX) + |> patternTransform2d(instances = 11, transform = transformY) + return squareHolePatternSketch +} +sketch005 = startSketchOn(offsetPlane("XZ", offset = 1)) + |> startProfileAt([ + -width / 2 + offset + caseTolerance, + height / 2 - (chamferLength + (offset + caseTolerance) / 2 * cos(toRadians(45))) + ], %) + |> angledLineToY({ + angle = 45, + to = height / 2 - (offset + caseTolerance) + }, %) + |> line(endAbsolute = [ + width / 2 - (chamferLength + (offset + caseTolerance) / 2 * cos(toRadians(45))), + height / 2 - (offset + caseTolerance) + ]) + |> angledLineToX({ + angle = -45, + to = width / 2 - (offset + caseTolerance) + }, %) + |> line(endAbsolute = [ + width / 2 - (offset + caseTolerance), + -(height / 2 - (chamferLength + (offset + caseTolerance) / 2 * cos(toRadians(45)))) + ]) + |> angledLineToY({ + angle = -135, + to = -height / 2 + offset + caseTolerance + }, %) + |> line(endAbsolute = [ + -(width / 2 - (chamferLength + (offset + caseTolerance) / 2 * cos(toRadians(45)))), + -height / 2 + offset + caseTolerance + ]) + |> angledLineToX({ + angle = -225, + to = -width / 2 + offset + caseTolerance + }, %) + |> close() + |> hole(screenHole(plane), %) + |> hole(squareHolePattern(plane, .75, .125), %) + |> hole(zLogo(plane, [-.30, -1.825], .20), %) + |> hole(oLogo(plane, [-.075, -1.825], .20), %) + |> hole(oLogo2(plane, [-.075, -1.825], .20), %) + |> hole(oLogo(plane, [.175, -1.825], .20), %) + |> hole(oLogo2(plane, [.175, -1.825], .20), %) + +extrude(sketch005, length = -0.0625) + |> appearance(color = '#D0FF01', metalness = 0, roughness = 50) diff --git a/rust/kcl-python-bindings/files/walkie-talkie/globals.kcl b/rust/kcl-python-bindings/files/walkie-talkie/globals.kcl new file mode 100644 index 000000000..3fef987c7 --- /dev/null +++ b/rust/kcl-python-bindings/files/walkie-talkie/globals.kcl @@ -0,0 +1,42 @@ +// Global constants for the walkie talkie + +// Set units +@settings(defaultLengthUnit = in) + +// body +export height = 4 +export width = 2.5 +export thickness = 1 +export chamferLength = .325 +export offset = .125 +export screenWidth = 1.75 +export screenHeight = 1 +export screenYPosition = height / 2 - 0.75 +export screenDepth = -.0625 +export speakerBoxWidth = 1.25 +export speakerBoxHeight = 1.25 + +// antenna +export antennaBaseWidth = .5 +export antennaBaseHeight = .25 +export antennaTopWidth = .30 +export antennaTopHeight = .05 + +// button +export buttonWidth = .15 +export tolerance = 0.020 +export buttonHeight = screenHeight / 2 - tolerance +export buttonThickness = .040 + +// case +export squareHoleSideLength = 0.0625 +export caseTolerance = 0.010 + +// knob +export knobDiameter = .5 +export knobHeight = .25 +export knobRadius = 0.050 + +// talk-button +export talkButtonSideLength = 0.5 +export talkButtonHeight = 0.050 \ No newline at end of file diff --git a/rust/kcl-python-bindings/files/walkie-talkie/knob.kcl b/rust/kcl-python-bindings/files/walkie-talkie/knob.kcl new file mode 100644 index 000000000..6ce0ac390 --- /dev/null +++ b/rust/kcl-python-bindings/files/walkie-talkie/knob.kcl @@ -0,0 +1,38 @@ +// Walkie talkie knob + + +// Set units +@settings(defaultLengthUnit = in) + + +// Import constants +import width, thickness, height, knobDiameter, knobHeight, knobRadius from "globals.kcl" + +// Define the plane for the knob +knobPlane = { + plane = { + origin = { + x = width / 2 - 0.70, + y = -thickness / 2, + z = height / 2 + }, + xAxis = { x = 1, y = 0, z = 0 }, + yAxis = { x = 0, y = 0, z = 1 }, + zAxis = { x = 0, y = 1, z = 0 } + } +} + +// Create the knob sketch and revolve +startSketchOn(knobPlane) + |> startProfileAt([0.0001, 0], %) + |> xLine(knobDiameter / 2, %) + |> yLine(knobHeight - 0.05, %) + |> arc({ + angleStart = 0, + angleEnd = 90, + radius = .05 + }, %) + |> xLineTo(0.0001, %) + |> close() + |> revolve({ axis = "Y" }, %) + |> appearance(color = '#D0FF01', metalness = 90, roughness = 50) diff --git a/rust/kcl-python-bindings/files/walkie-talkie/main.kcl b/rust/kcl-python-bindings/files/walkie-talkie/main.kcl new file mode 100644 index 000000000..60aaad8f1 --- /dev/null +++ b/rust/kcl-python-bindings/files/walkie-talkie/main.kcl @@ -0,0 +1,50 @@ +// Walkie Talkie +// A portable, handheld two-way radio device that allows users to communicate wirelessly over short to medium distances. It operates on specific radio frequencies and features a push-to-talk button for transmitting messages, making it ideal for quick and reliable communication in outdoor, work, or emergency settings. + +// Set units +@settings(defaultLengthUnit = in) + +// Import parts and constants +import 'body.kcl' +import 'antenna.kcl' +import 'case.kcl' +import 'talk-button.kcl' as talkButton +import 'knob.kcl' +import button from "button.kcl" +import width, height, thickness, screenWidth, screenHeight, screenYPosition, tolerance from "globals.kcl" + +// Import the body +body + +// Import the case +case + +// Import the antenna +antenna + +// Import the buttons +button([ + -(screenWidth / 2 + tolerance), + screenYPosition +], 0, offsetPlane("XZ", offset = thickness)) +button([ + -(screenWidth / 2 + tolerance), + screenYPosition - (screenHeight / 2) +], 0, offsetPlane("XZ", offset = thickness)) +button([ + screenWidth / 2 + tolerance, + screenYPosition - screenHeight +], 180, offsetPlane("XZ", offset = thickness)) +button([ + screenWidth / 2 + tolerance, + screenYPosition - (screenHeight / 2) +], 180, offsetPlane("XZ", offset = thickness)) + +// Import the talk button +talkButton + +// Import the frequency knob +knob + + + diff --git a/rust/kcl-python-bindings/files/walkie-talkie/talk-button.kcl b/rust/kcl-python-bindings/files/walkie-talkie/talk-button.kcl new file mode 100644 index 000000000..1a8e4cde6 --- /dev/null +++ b/rust/kcl-python-bindings/files/walkie-talkie/talk-button.kcl @@ -0,0 +1,46 @@ +// Walkie talkie talk button + + +// Set units +@settings(defaultLengthUnit = in) + + +// Import constants +import width, thickness, talkButtonSideLength, talkButtonHeight from "globals.kcl" + +talkButtonPlane = { + plane = { + origin = { + x = width / 2, + y = -thickness / 2, + z = .5 + }, + xAxis = { x = 0, y = 1, z = 0 }, + yAxis = { x = 0, y = 0, z = 1 }, + zAxis = { x = 1, y = 0, z = 0 } + } +} + +// Create the talk button sketch +talkButtonSketch = startSketchOn(talkButtonPlane) + |> startProfileAt([ + -talkButtonSideLength / 2, + talkButtonSideLength / 2 + ], %) + |> xLine(talkButtonSideLength, %, $tag1) + |> yLine(-talkButtonSideLength, %, $tag2) + |> xLine(-talkButtonSideLength, %, $tag3) + |> close(tag = $tag4) + +// Create the talk button and apply fillets +extrude(talkButtonSketch, length = talkButtonHeight) + |> fillet( + radius = 0.050, + tags = [ + getNextAdjacentEdge(tag1), + getNextAdjacentEdge(tag2), + getNextAdjacentEdge(tag3), + getNextAdjacentEdge(tag4) + ] + ) + |> appearance(color = '#D0FF01', metalness = 90, roughness = 90) diff --git a/rust/kcl-python-bindings/files/walkie-talkie/zoo-logo.kcl b/rust/kcl-python-bindings/files/walkie-talkie/zoo-logo.kcl new file mode 100644 index 000000000..03e71643b --- /dev/null +++ b/rust/kcl-python-bindings/files/walkie-talkie/zoo-logo.kcl @@ -0,0 +1,83 @@ +// Zoo logo + +// Define a function to draw the ZOO "Z" +export fn zLogo(surface, origin, scale) { + zSketch = surface + |> startProfileAt([ + 0 + origin[0], + 0.15 * scale + origin[1] + ], %) + |> yLine(-0.15 * scale, %) + |> xLine(0.15 * scale, %) + |> angledLineToX({ + angle = 47.15, + to = 0.3 * scale + origin[0] + }, %, $seg1) + |> yLineTo(0 + origin[1], %, $seg3) + |> xLine(0.63 * scale, %) + |> yLine(0.225 * scale, %) + |> xLine(-0.57 * scale, %) + |> angledLineToX({ + angle = 47.15, + to = 0.93 * scale + origin[0] + }, %) + |> yLine(0.15 * scale, %) + |> xLine(-0.15 * scale, %) + |> angledLine({ + angle = 47.15, + length = -segLen(seg1) + }, %, $seg2) + |> yLine(segLen(seg3), %) + |> xLineTo(0 + origin[0], %) + |> yLine(-0.225 * scale, %) + |> angledLineThatIntersects({ + angle = 0, + intersectTag = seg2, + offset = 0 + }, %) + |> close() + return zSketch +} + +// Define a function to draw the ZOO "O" +export fn oLogo(surface, origin, scale) { + oSketch001 = surface + |> startProfileAt([ + .788 * scale + origin[0], + .921 * scale + origin[1] + ], %) + |> arc({ + angleStart = 47.15 + 6, + angleEnd = 47.15 - 6 + 180, + radius = .525 * scale + }, %) + |> angledLine({ angle = 47.15, length = .24 * scale }, %) + |> arc({ + angleStart = 47.15 - 11 + 180, + angleEnd = 47.15 + 11, + radius = .288 * scale + }, %) + |> close() + return oSketch001 +} + +export fn oLogo2(surface, origin, scale) { + oSketch002 = surface + |> startProfileAt([ + .16 * scale + origin[0], + .079 * scale + origin[1] + ], %) + |> arc({ + angleStart = 47.15 + 6 - 180, + angleEnd = 47.15 - 6, + radius = .525 * scale + }, %) + |> angledLine({ angle = 47.15, length = -.24 * scale }, %) + |> arc({ + angleStart = 47.15 - 11, + angleEnd = 47.15 + 11 - 180, + radius = .288 * scale + }, %) + |> close() + return oSketch002 +} \ No newline at end of file diff --git a/rust/kcl-python-bindings/justfile b/rust/kcl-python-bindings/justfile new file mode 100644 index 000000000..dcc9daf10 --- /dev/null +++ b/rust/kcl-python-bindings/justfile @@ -0,0 +1,8 @@ +test: + uv pip install .[test] + uv run pytest tests/tests.py +setup-uv: + uv python install + uv venv .venv + echo "VIRTUAL_ENV=.venv" >> $GITHUB_ENV + echo "$PWD/.venv/bin" >> $GITHUB_PATH diff --git a/rust/kcl-python-bindings/pyproject.toml b/rust/kcl-python-bindings/pyproject.toml new file mode 100644 index 000000000..a206d035f --- /dev/null +++ b/rust/kcl-python-bindings/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["maturin>=1.6,<2.0"] +build-backend = "maturin" + +[project] +name = "zoo-kcl" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] + +[project.optional-dependencies] +test = [ + "pytest", + "pytest-asyncio", +] + +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/rust/kcl-python-bindings/src/lib.rs b/rust/kcl-python-bindings/src/lib.rs new file mode 100644 index 000000000..4de7b5eb8 --- /dev/null +++ b/rust/kcl-python-bindings/src/lib.rs @@ -0,0 +1,525 @@ +#![allow(clippy::useless_conversion)] +use anyhow::Result; +use kcl_lib::{ + lint::{checks, Discovered}, + ExecutorContext, UnitLength, +}; +use pyo3::{ + prelude::PyModuleMethods, pyclass, pyfunction, pymethods, pymodule, types::PyModule, wrap_pyfunction, Bound, PyErr, + PyResult, +}; +use serde::{Deserialize, Serialize}; + +fn tokio() -> &'static tokio::runtime::Runtime { + use std::sync::OnceLock; + static RT: OnceLock = OnceLock::new(); + RT.get_or_init(|| tokio::runtime::Runtime::new().unwrap()) +} + +fn into_miette(error: kcl_lib::KclErrorWithOutputs) -> PyErr { + let report = error.clone().into_miette_report_with_outputs().unwrap(); + let report = miette::Report::new(report); + pyo3::exceptions::PyException::new_err(format!("{:?}", report)) +} + +fn into_miette_for_parse(filename: &str, input: &str, error: kcl_lib::KclError) -> PyErr { + let report = kcl_lib::Report { + kcl_source: input.to_string(), + error: error.clone(), + filename: filename.to_string(), + }; + let report = miette::Report::new(report); + pyo3::exceptions::PyException::new_err(format!("{:?}", report)) +} + +/// The variety of image formats snapshots may be exported to. +#[derive(Serialize, Deserialize, PartialEq, Hash, Debug, Clone, Copy)] +#[pyclass(eq, eq_int)] +#[serde(rename_all = "lowercase")] +pub enum ImageFormat { + /// .png format + Png, + /// .jpeg format + Jpeg, +} + +impl From for kittycad_modeling_cmds::ImageFormat { + fn from(format: ImageFormat) -> Self { + match format { + ImageFormat::Png => kittycad_modeling_cmds::ImageFormat::Png, + ImageFormat::Jpeg => kittycad_modeling_cmds::ImageFormat::Jpeg, + } + } +} + +/// A file that was exported from the engine. +#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)] +#[pyclass] +pub struct ExportFile { + /// Binary contents of the file. + pub contents: Vec, + /// Name of the file. + pub name: String, +} + +impl From for ExportFile { + fn from(file: kittycad_modeling_cmds::shared::ExportFile) -> Self { + ExportFile { + contents: file.contents.0, + name: file.name, + } + } +} + +impl From for ExportFile { + fn from(file: kittycad_modeling_cmds::websocket::RawFile) -> Self { + ExportFile { + contents: file.contents, + name: file.name, + } + } +} + +#[pymethods] +impl ExportFile { + #[getter] + fn contents(&self) -> Vec { + self.contents.clone() + } + + #[getter] + fn name(&self) -> String { + self.name.clone() + } +} + +/// The valid types of output file formats. +#[derive(Serialize, Deserialize, PartialEq, Hash, Debug, Clone)] +#[pyclass(eq, eq_int)] +#[serde(rename_all = "lowercase")] +pub enum FileExportFormat { + /// Autodesk Filmbox (FBX) format. + Fbx, + /// Binary glTF 2.0. + /// + /// This is a single binary with .glb extension. + /// + /// This is better if you want a compressed format as opposed to the human readable glTF that lacks + /// compression. + Glb, + /// glTF 2.0. Embedded glTF 2.0 (pretty printed). + /// + /// Single JSON file with .gltf extension binary data encoded as base64 data URIs. + /// + /// The JSON contents are pretty printed. + /// + /// It is human readable, single file, and you can view the diff easily in a + /// git commit. + Gltf, + /// The OBJ file format. It may or + /// may not have an an attached material (mtl // mtllib) within the file, but we + /// interact with it as if it does not. + Obj, + /// The PLY file format. + Ply, + /// The STEP file format. + Step, + /// The STL file format. + Stl, +} + +fn get_output_format( + format: &FileExportFormat, + src_unit: kittycad_modeling_cmds::units::UnitLength, +) -> kittycad_modeling_cmds::format::OutputFormat3d { + // Zoo co-ordinate system. + // + // * Forward: -Y + // * Up: +Z + // * Handedness: Right + let coords = kittycad_modeling_cmds::coord::System { + forward: kittycad_modeling_cmds::coord::AxisDirectionPair { + axis: kittycad_modeling_cmds::coord::Axis::Y, + direction: kittycad_modeling_cmds::coord::Direction::Negative, + }, + up: kittycad_modeling_cmds::coord::AxisDirectionPair { + axis: kittycad_modeling_cmds::coord::Axis::Z, + direction: kittycad_modeling_cmds::coord::Direction::Positive, + }, + }; + + match format { + FileExportFormat::Fbx => { + kittycad_modeling_cmds::format::OutputFormat3d::Fbx(kittycad_modeling_cmds::format::fbx::export::Options { + storage: kittycad_modeling_cmds::format::fbx::export::Storage::Binary, + created: None, + }) + } + FileExportFormat::Glb => kittycad_modeling_cmds::format::OutputFormat3d::Gltf( + kittycad_modeling_cmds::format::gltf::export::Options { + storage: kittycad_modeling_cmds::format::gltf::export::Storage::Binary, + presentation: kittycad_modeling_cmds::format::gltf::export::Presentation::Compact, + }, + ), + FileExportFormat::Gltf => kittycad_modeling_cmds::format::OutputFormat3d::Gltf( + kittycad_modeling_cmds::format::gltf::export::Options { + storage: kittycad_modeling_cmds::format::gltf::export::Storage::Embedded, + presentation: kittycad_modeling_cmds::format::gltf::export::Presentation::Pretty, + }, + ), + FileExportFormat::Obj => { + kittycad_modeling_cmds::format::OutputFormat3d::Obj(kittycad_modeling_cmds::format::obj::export::Options { + coords, + units: src_unit, + }) + } + FileExportFormat::Ply => { + kittycad_modeling_cmds::format::OutputFormat3d::Ply(kittycad_modeling_cmds::format::ply::export::Options { + storage: kittycad_modeling_cmds::format::ply::export::Storage::Ascii, + coords, + selection: kittycad_modeling_cmds::format::Selection::DefaultScene, + units: src_unit, + }) + } + FileExportFormat::Step => kittycad_modeling_cmds::format::OutputFormat3d::Step( + kittycad_modeling_cmds::format::step::export::Options { coords, created: None }, + ), + FileExportFormat::Stl => { + kittycad_modeling_cmds::format::OutputFormat3d::Stl(kittycad_modeling_cmds::format::stl::export::Options { + storage: kittycad_modeling_cmds::format::stl::export::Storage::Ascii, + coords, + units: src_unit, + selection: kittycad_modeling_cmds::format::Selection::DefaultScene, + }) + } + } +} + +/// Get the path to the current file from the path given, and read the code. +async fn get_code_and_file_path(path: &str) -> Result<(String, std::path::PathBuf)> { + let mut path = std::path::PathBuf::from(path); + // Check if the path is a directory, if so we want to look for a main.kcl inside. + if path.is_dir() { + path = path.join("main.kcl"); + if !path.exists() { + return Err(anyhow::anyhow!("Directory must contain a main.kcl file")); + } + } else { + // Otherwise be sure we have a kcl file. + if let Some(ext) = path.extension() { + if ext != "kcl" { + return Err(anyhow::anyhow!("File must have a .kcl extension")); + } + } + } + + let code = tokio::fs::read_to_string(&path).await?; + Ok((code, path)) +} + +async fn new_context_state(current_file: Option) -> Result<(ExecutorContext, kcl_lib::ExecState)> { + let mut settings: kcl_lib::ExecutorSettings = Default::default(); + if let Some(current_file) = current_file { + settings.with_current_file(current_file); + } + let state = kcl_lib::ExecState::new(&settings); + let ctx = ExecutorContext::new_with_client(settings, None, None).await?; + Ok((ctx, state)) +} + +/// Execute the kcl code from a file path. +#[pyfunction] +async fn execute(path: String) -> PyResult<()> { + tokio() + .spawn(async move { + let (code, path) = get_code_and_file_path(&path) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + let program = kcl_lib::Program::parse_no_errs(&code) + .map_err(|err| into_miette_for_parse(&path.display().to_string(), &code, err))?; + + let (ctx, mut state) = new_context_state(Some(path)) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + // Execute the program. + ctx.run_with_ui_outputs(&program, &mut state) + .await + .map_err(into_miette)?; + + Ok(()) + }) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))? +} + +/// Execute the kcl code. +#[pyfunction] +async fn execute_code(code: String) -> PyResult<()> { + tokio() + .spawn(async move { + let program = + kcl_lib::Program::parse_no_errs(&code).map_err(|err| into_miette_for_parse("", &code, err))?; + + let (ctx, mut state) = new_context_state(None) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + // Execute the program. + ctx.run_with_ui_outputs(&program, &mut state) + .await + .map_err(into_miette)?; + + Ok(()) + }) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))? +} + +/// Execute a kcl file and snapshot it in a specific format. +#[pyfunction] +async fn execute_and_snapshot(path: String, image_format: ImageFormat) -> PyResult> { + tokio() + .spawn(async move { + let (code, path) = get_code_and_file_path(&path) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + let program = kcl_lib::Program::parse_no_errs(&code) + .map_err(|err| into_miette_for_parse(&path.display().to_string(), &code, err))?; + + let (ctx, mut state) = new_context_state(Some(path)) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + // Execute the program. + ctx.run_with_ui_outputs(&program, &mut state) + .await + .map_err(into_miette)?; + + // Zoom to fit. + ctx.engine + .send_modeling_cmd( + uuid::Uuid::new_v4(), + kcl_lib::SourceRange::default(), + &kittycad_modeling_cmds::ModelingCmd::ZoomToFit(kittycad_modeling_cmds::ZoomToFit { + object_ids: Default::default(), + padding: 0.1, + animated: false, + }), + ) + .await?; + + // Send a snapshot request to the engine. + let resp = ctx + .engine + .send_modeling_cmd( + uuid::Uuid::new_v4(), + kcl_lib::SourceRange::default(), + &kittycad_modeling_cmds::ModelingCmd::TakeSnapshot(kittycad_modeling_cmds::TakeSnapshot { + format: image_format.into(), + }), + ) + .await?; + + let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Modeling { + modeling_response: kittycad_modeling_cmds::ok_response::OkModelingCmdResponse::TakeSnapshot(data), + } = resp + else { + return Err(pyo3::exceptions::PyException::new_err(format!( + "Unexpected response from engine: {:?}", + resp + ))); + }; + + Ok(data.contents.0) + }) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))? +} + +/// Execute the kcl code and snapshot it in a specific format. +#[pyfunction] +async fn execute_code_and_snapshot(code: String, image_format: ImageFormat) -> PyResult> { + tokio() + .spawn(async move { + let program = + kcl_lib::Program::parse_no_errs(&code).map_err(|err| into_miette_for_parse("", &code, err))?; + + let (ctx, mut state) = new_context_state(None) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + // Execute the program. + ctx.run_with_ui_outputs(&program, &mut state) + .await + .map_err(into_miette)?; + + // Zoom to fit. + ctx.engine + .send_modeling_cmd( + uuid::Uuid::new_v4(), + kcl_lib::SourceRange::default(), + &kittycad_modeling_cmds::ModelingCmd::ZoomToFit(kittycad_modeling_cmds::ZoomToFit { + object_ids: Default::default(), + padding: 0.1, + animated: false, + }), + ) + .await?; + + // Send a snapshot request to the engine. + let resp = ctx + .engine + .send_modeling_cmd( + uuid::Uuid::new_v4(), + kcl_lib::SourceRange::default(), + &kittycad_modeling_cmds::ModelingCmd::TakeSnapshot(kittycad_modeling_cmds::TakeSnapshot { + format: image_format.into(), + }), + ) + .await?; + + let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Modeling { + modeling_response: kittycad_modeling_cmds::ok_response::OkModelingCmdResponse::TakeSnapshot(data), + } = resp + else { + return Err(pyo3::exceptions::PyException::new_err(format!( + "Unexpected response from engine: {:?}", + resp + ))); + }; + + Ok(data.contents.0) + }) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))? +} + +/// Execute a kcl file and export it to a specific file format. +#[pyfunction] +async fn execute_and_export(path: String, export_format: FileExportFormat) -> PyResult> { + tokio() + .spawn(async move { + let (code, path) = get_code_and_file_path(&path) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + let program = kcl_lib::Program::parse_no_errs(&code) + .map_err(|err| into_miette_for_parse(&path.display().to_string(), &code, err))?; + let settings = program.meta_settings()?.unwrap_or_default(); + let units: UnitLength = settings.default_length_units.into(); + + let (ctx, mut state) = new_context_state(Some(path)) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + // Execute the program. + ctx.run_with_ui_outputs(&program, &mut state) + .await + .map_err(into_miette)?; + + // This will not return until there are files. + let resp = ctx + .engine + .send_modeling_cmd( + uuid::Uuid::new_v4(), + kcl_lib::SourceRange::default(), + &kittycad_modeling_cmds::ModelingCmd::Export(kittycad_modeling_cmds::Export { + entity_ids: vec![], + format: get_output_format(&export_format, units.into()), + }), + ) + .await?; + + let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { files } = resp else { + return Err(pyo3::exceptions::PyException::new_err(format!( + "Unexpected response from engine: {:?}", + resp + ))); + }; + + Ok(files.into_iter().map(ExportFile::from).collect()) + }) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))? +} + +/// Execute the kcl code and export it to a specific file format. +#[pyfunction] +async fn execute_code_and_export(code: String, export_format: FileExportFormat) -> PyResult> { + tokio() + .spawn(async move { + let program = + kcl_lib::Program::parse_no_errs(&code).map_err(|err| into_miette_for_parse("", &code, err))?; + let settings = program.meta_settings()?.unwrap_or_default(); + let units: UnitLength = settings.default_length_units.into(); + + let (ctx, mut state) = new_context_state(None) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + // Execute the program. + ctx.run_with_ui_outputs(&program, &mut state) + .await + .map_err(into_miette)?; + + // This will not return until there are files. + let resp = ctx + .engine + .send_modeling_cmd( + uuid::Uuid::new_v4(), + kcl_lib::SourceRange::default(), + &kittycad_modeling_cmds::ModelingCmd::Export(kittycad_modeling_cmds::Export { + entity_ids: vec![], + format: get_output_format(&export_format, units.into()), + }), + ) + .await?; + + let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { files } = resp else { + return Err(pyo3::exceptions::PyException::new_err(format!( + "Unexpected response from engine: {:?}", + resp + ))); + }; + + Ok(files.into_iter().map(ExportFile::from).collect()) + }) + .await + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))? +} + +/// Format the kcl code. +#[pyfunction] +fn format(code: String) -> PyResult { + let program = kcl_lib::Program::parse_no_errs(&code).map_err(|err| into_miette_for_parse("", &code, err))?; + let recasted = program.recast(); + + Ok(recasted) +} + +/// Lint the kcl code. +#[pyfunction] +fn lint(code: String) -> PyResult> { + let program = kcl_lib::Program::parse_no_errs(&code).map_err(|err| into_miette_for_parse("", &code, err))?; + let lints = program + .lint(checks::lint_variables) + .map_err(|err| pyo3::exceptions::PyException::new_err(err.to_string()))?; + + Ok(lints) +} + +/// The kcl python module. +#[pymodule] +fn kcl(m: &Bound<'_, PyModule>) -> PyResult<()> { + // Add our types to the module. + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + // Add our functions to the module. + m.add_function(wrap_pyfunction!(execute, m)?)?; + m.add_function(wrap_pyfunction!(execute_code, m)?)?; + m.add_function(wrap_pyfunction!(execute_and_snapshot, m)?)?; + m.add_function(wrap_pyfunction!(execute_code_and_snapshot, m)?)?; + m.add_function(wrap_pyfunction!(execute_and_export, m)?)?; + m.add_function(wrap_pyfunction!(execute_code_and_export, m)?)?; + m.add_function(wrap_pyfunction!(format, m)?)?; + m.add_function(wrap_pyfunction!(lint, m)?)?; + Ok(()) +} diff --git a/rust/kcl-python-bindings/tests/tests.py b/rust/kcl-python-bindings/tests/tests.py new file mode 100755 index 000000000..13b7ce890 --- /dev/null +++ b/rust/kcl-python-bindings/tests/tests.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 +import os + +import kcl +import pytest + +# Get the path to this script's parent directory. +files_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "files") +kcl_dir = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "..", "..", "kcl-lib" +) +lego_file = os.path.join(kcl_dir, "e2e", "executor", "inputs", "lego.kcl") +walkie_talkie_dir = os.path.join(files_dir, "walkie-talkie") + + +@pytest.mark.asyncio +async def test_kcl_execute_with_exception(): + # Read from a file. + try: + await kcl.execute(os.path.join(files_dir, "parse_file_error")) + except Exception as e: + assert e is not None + assert len(str(e)) > 0 + assert "lksjndflsskjfnak;jfna##" in str(e) + + +@pytest.mark.asyncio +async def test_kcl_execute(): + # Read from a file. + await kcl.execute(lego_file) + + +@pytest.mark.asyncio +async def test_kcl_execute_code(): + # Read from a file. + with open(lego_file, "r") as f: + code = str(f.read()) + assert code is not None + assert len(code) > 0 + await kcl.execute_code(code) + + +@pytest.mark.asyncio +async def test_kcl_execute_code_and_snapshot(): + # Read from a file. + with open(lego_file, "r") as f: + code = str(f.read()) + assert code is not None + assert len(code) > 0 + image_bytes = await kcl.execute_code_and_snapshot(code, kcl.ImageFormat.Jpeg) + assert image_bytes is not None + assert len(image_bytes) > 0 + + +@pytest.mark.asyncio +async def test_kcl_execute_code_and_export(): + # Read from a file. + with open(lego_file, "r") as f: + code = str(f.read()) + assert code is not None + assert len(code) > 0 + files = await kcl.execute_code_and_export(code, kcl.FileExportFormat.Step) + assert files is not None + assert len(files) > 0 + assert files[0] is not None + name = files[0].name + contents = files[0].contents + assert name is not None + assert len(name) > 0 + assert contents is not None + assert len(contents) > 0 + + +@pytest.mark.asyncio +async def test_kcl_execute_dir_assembly(): + # Read from a file. + await kcl.execute(walkie_talkie_dir) + + +@pytest.mark.asyncio +async def test_kcl_execute_and_snapshot(): + # Read from a file. + image_bytes = await kcl.execute_and_snapshot(lego_file, kcl.ImageFormat.Jpeg) + assert image_bytes is not None + assert len(image_bytes) > 0 + + +@pytest.mark.asyncio +async def test_kcl_execute_and_snapshot_dir(): + # Read from a file. + image_bytes = await kcl.execute_and_snapshot( + walkie_talkie_dir, kcl.ImageFormat.Jpeg + ) + assert image_bytes is not None + assert len(image_bytes) > 0 + + +@pytest.mark.asyncio +async def test_kcl_execute_and_export(): + # Read from a file. + files = await kcl.execute_and_export(lego_file, kcl.FileExportFormat.Step) + assert files is not None + assert len(files) > 0 + assert files[0] is not None + name = files[0].name + contents = files[0].contents + assert name is not None + assert len(name) > 0 + assert contents is not None + assert len(contents) > 0 + + +def test_kcl_format(): + # Read from a file. + with open(lego_file, "r") as f: + code = str(f.read()) + assert code is not None + assert len(code) > 0 + formatted_code = kcl.format(code) + assert formatted_code is not None + assert len(formatted_code) > 0 + + +def test_kcl_lint(): + # Read from a file. + with open(os.path.join(files_dir, "box_with_linter_errors.kcl"), "r") as f: + code = str(f.read()) + assert code is not None + assert len(code) > 0 + lints = kcl.lint(code) + assert lints is not None + assert len(lints) > 0 + description = lints[0].description + assert description is not None + assert len(description) > 0 + finding = lints[0].finding + assert finding is not None + finding_title = finding.title + assert finding_title is not None + assert len(finding_title) > 0 diff --git a/rust/kcl-to-core/Cargo.toml b/rust/kcl-to-core/Cargo.toml index 5a3bd047d..3576e0c5f 100644 --- a/rust/kcl-to-core/Cargo.toml +++ b/rust/kcl-to-core/Cargo.toml @@ -13,8 +13,8 @@ name = "kcl-to-core" path = "src/tool.rs" [dependencies] -anyhow = "1" -async-trait = "0.1.85" +anyhow = { workspace = true } +async-trait = { workspace = true } indexmap = { workspace = true } kcl-lib = { path = "../kcl-lib" } kittycad = { workspace = true, features = ["clap"] }