carla-canvas 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. carla_canvas-0.1.1/.github/actions/wheel/action.yml +50 -0
  2. carla_canvas-0.1.1/.github/workflows/ci.yml +51 -0
  3. carla_canvas-0.1.1/.github/workflows/release-label.yml +45 -0
  4. carla_canvas-0.1.1/.github/workflows/release.yml +280 -0
  5. carla_canvas-0.1.1/.gitignore +7 -0
  6. carla_canvas-0.1.1/CLAUDE.md +54 -0
  7. carla_canvas-0.1.1/CMakeLists.txt +155 -0
  8. carla_canvas-0.1.1/LICENSE +21 -0
  9. carla_canvas-0.1.1/PKG-INFO +202 -0
  10. carla_canvas-0.1.1/README.md +177 -0
  11. carla_canvas-0.1.1/cmake/FetchDeps.cmake +104 -0
  12. carla_canvas-0.1.1/docs/configuration.md +119 -0
  13. carla_canvas-0.1.1/docs/design.md +189 -0
  14. carla_canvas-0.1.1/docs/renderers.md +112 -0
  15. carla_canvas-0.1.1/docs/ros2.md +43 -0
  16. carla_canvas-0.1.1/examples/camera_lidar.py +50 -0
  17. carla_canvas-0.1.1/examples/gaussian_splatting.yaml +28 -0
  18. carla_canvas-0.1.1/examples/gaussian_splatting_demo.yaml +17 -0
  19. carla_canvas-0.1.1/examples/my_renderer.py +44 -0
  20. carla_canvas-0.1.1/examples/primitives.yaml +13 -0
  21. carla_canvas-0.1.1/examples/splatsim.yaml +22 -0
  22. carla_canvas-0.1.1/native/src/config.cpp +134 -0
  23. carla_canvas-0.1.1/native/src/config.hpp +41 -0
  24. carla_canvas-0.1.1/native/src/exports.map +7 -0
  25. carla_canvas-0.1.1/native/src/plugin.cpp +743 -0
  26. carla_canvas-0.1.1/native/src/tsc_api.hpp +95 -0
  27. carla_canvas-0.1.1/native/tests/test_config.cpp +78 -0
  28. carla_canvas-0.1.1/proto/carla_canvas/v1/renderer.proto +206 -0
  29. carla_canvas-0.1.1/pyproject.toml +68 -0
  30. carla_canvas-0.1.1/python/carla_canvas/__init__.py +37 -0
  31. carla_canvas-0.1.1/python/carla_canvas/cli.py +140 -0
  32. carla_canvas-0.1.1/python/carla_canvas/config.py +137 -0
  33. carla_canvas-0.1.1/python/carla_canvas/encodings.py +57 -0
  34. carla_canvas-0.1.1/python/carla_canvas/geometry.py +154 -0
  35. carla_canvas-0.1.1/python/carla_canvas/lidar.py +220 -0
  36. carla_canvas-0.1.1/python/carla_canvas/plugin.py +53 -0
  37. carla_canvas-0.1.1/python/carla_canvas/renderer.py +166 -0
  38. carla_canvas-0.1.1/python/carla_canvas/renderers/__init__.py +1 -0
  39. carla_canvas-0.1.1/python/carla_canvas/renderers/demo_assets.py +62 -0
  40. carla_canvas-0.1.1/python/carla_canvas/renderers/gaussian_splatting.py +390 -0
  41. carla_canvas-0.1.1/python/carla_canvas/renderers/gs_ply.py +192 -0
  42. carla_canvas-0.1.1/python/carla_canvas/renderers/primitives.py +141 -0
  43. carla_canvas-0.1.1/python/carla_canvas/ros/__init__.py +17 -0
  44. carla_canvas-0.1.1/python/carla_canvas/ros/bridge.py +214 -0
  45. carla_canvas-0.1.1/python/carla_canvas/ros/dds_types.py +118 -0
  46. carla_canvas-0.1.1/python/carla_canvas/ros/messages.py +142 -0
  47. carla_canvas-0.1.1/python/carla_canvas/ros/transports.py +214 -0
  48. carla_canvas-0.1.1/python/carla_canvas/scene.py +185 -0
  49. carla_canvas-0.1.1/python/carla_canvas/sensors.py +88 -0
  50. carla_canvas-0.1.1/python/carla_canvas/server/__init__.py +113 -0
  51. carla_canvas-0.1.1/python/carla_canvas/server/service.py +235 -0
  52. carla_canvas-0.1.1/python/carla_canvas/v1/__init__.py +1 -0
  53. carla_canvas-0.1.1/python/carla_canvas/v1/renderer_pb2.py +89 -0
  54. carla_canvas-0.1.1/python/carla_canvas/v1/renderer_pb2.pyi +244 -0
  55. carla_canvas-0.1.1/python/carla_canvas/v1/renderer_pb2_grpc.py +328 -0
  56. carla_canvas-0.1.1/tests/conftest.py +63 -0
  57. carla_canvas-0.1.1/tests/programs/batch_destroy.py +29 -0
  58. carla_canvas-0.1.1/tests/programs/callbacks_ros.py +60 -0
  59. carla_canvas-0.1.1/tests/test_end_to_end.py +143 -0
  60. carla_canvas-0.1.1/tests/test_geometry_and_config.py +88 -0
  61. carla_canvas-0.1.1/tests/test_packaging.py +61 -0
  62. carla_canvas-0.1.1/tests/test_renderers.py +175 -0
  63. carla_canvas-0.1.1/tests/test_service.py +323 -0
  64. carla_canvas-0.1.1/tools/bump_version.py +63 -0
  65. carla_canvas-0.1.1/tools/check_wheel.py +49 -0
  66. carla_canvas-0.1.1/tools/gen_proto.py +52 -0
@@ -0,0 +1,50 @@
1
+ # One carla-canvas wheel for an architecture, on a runner of that architecture:
2
+ # the sensor plugin with gRPC, protobuf and yaml-cpp built from source and linked
3
+ # in statically (CARLA_CANVAS_FETCH_DEPS), checked as installed. Shared by CI and
4
+ # the release workflow; the dependencies' build is cached between runs.
5
+ name: carla-canvas wheel
6
+ description: Builds and checks one carla-canvas manylinux wheel for an architecture
7
+ inputs:
8
+ arch:
9
+ description: x86_64 or aarch64 (the runner's)
10
+ required: true
11
+ runs:
12
+ using: composite
13
+ steps:
14
+ - name: Restore the dependencies' build
15
+ uses: actions/cache@v4
16
+ with:
17
+ path: ${{ runner.temp }}/wheel-build
18
+ # Keep the cibuildwheel version in step with `uses:` below.
19
+ # One entry (~1 GB) per dependency set: cmake/FetchDeps.cmake pins them.
20
+ key: wheel-manylinux_2_28-${{ inputs.arch }}-cibuildwheel-v3.1.4-${{ hashFiles('cmake/**') }}
21
+ - uses: pypa/cibuildwheel@v3.1.4
22
+ env:
23
+ CIBW_BUILD: cp312-manylinux_${{ inputs.arch }}
24
+ CIBW_ARCHS: ${{ inputs.arch }}
25
+ CIBW_MANYLINUX_X86_64_IMAGE: manylinux_2_28
26
+ CIBW_MANYLINUX_AARCH64_IMAGE: manylinux_2_28
27
+ # FetchDeps needs CMake 3.28+: scikit-build-core fetches it if the image's is older.
28
+ CIBW_ENVIRONMENT: SKBUILD_CMAKE_VERSION=">=3.28"
29
+ CIBW_CONTAINER_ENGINE: "docker; create_args: --volume=${{ runner.temp }}/wheel-build:/wheel-build"
30
+ CIBW_CONFIG_SETTINGS: >-
31
+ build-dir=/wheel-build/build
32
+ cmake.define.CARLA_CANVAS_FETCH_DEPS=ON
33
+ cmake.define.CARLA_CANVAS_REQUIRE_PLUGIN=ON
34
+ # Checked in the next step, as installed outside the source tree.
35
+ CIBW_TEST_SKIP: "*"
36
+ with:
37
+ output-dir: dist
38
+ # The container wrote the build directory as root; actions/cache saves it
39
+ # as the runner user.
40
+ - name: Hand the build back to the runner user
41
+ shell: bash
42
+ run: sudo chown -R "$(id -u):$(id -g)" "$RUNNER_TEMP/wheel-build"
43
+ - uses: actions/setup-python@v5
44
+ with: { python-version: "3.12" }
45
+ - name: Check the wheel (plugin, exports, no system gRPC/protobuf/yaml-cpp)
46
+ shell: bash
47
+ run: |
48
+ python -m venv /tmp/check
49
+ /tmp/check/bin/pip install dist/*.whl
50
+ cd /tmp && /tmp/check/bin/python "$GITHUB_WORKSPACE/tools/check_wheel.py"
@@ -0,0 +1,51 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ env:
8
+ # typesafe_carla with the sensor source C ABI (5.3); mock backend: no CARLA server.
9
+ TYPESAFE_CARLA_REPO: hakuturu583/typesafe_carla
10
+ TYPESAFE_CARLA_REF: main
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ubuntu-24.04
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ repository: ${{ env.TYPESAFE_CARLA_REPO }}
20
+ ref: ${{ env.TYPESAFE_CARLA_REF }}
21
+ path: typesafe_carla
22
+ - name: System packages
23
+ run: |
24
+ sudo apt-get update
25
+ sudo apt-get install -y g++ cmake zlib1g-dev libgrpc++-dev protobuf-compiler-grpc \
26
+ libprotobuf-dev protobuf-compiler libyaml-cpp-dev
27
+ - uses: astral-sh/setup-uv@v5
28
+ - name: Environment
29
+ run: |
30
+ uv venv
31
+ uv pip install scikit-build-core grpcio grpcio-tools protobuf numpy pyyaml pytest cyclonedds
32
+ TSC_BACKEND=mock uv pip install ./typesafe_carla
33
+ uv pip install --no-build-isolation --no-deps -e .
34
+ - name: Native tests
35
+ run: |
36
+ cmake -S . -B build -DCARLA_CANVAS_REQUIRE_PLUGIN=ON \
37
+ -DTYPESAFE_CARLA_INCLUDE_DIR=$PWD/typesafe_carla/native/include
38
+ cmake --build build -j
39
+ ctest --test-dir build --output-on-failure
40
+ - name: Python tests
41
+ run: PATH=$PWD/.venv/bin:$PATH .venv/bin/python -m pytest -v
42
+
43
+ wheel:
44
+ # The release wheel (x86_64): the plugin with gRPC, protobuf and yaml-cpp
45
+ # built from source, no system packages (.github/actions/wheel).
46
+ runs-on: ubuntu-24.04
47
+ steps:
48
+ - uses: actions/checkout@v4
49
+ - uses: ./.github/actions/wheel
50
+ with:
51
+ arch: x86_64
@@ -0,0 +1,45 @@
1
+ # The release labels of the auto-release (release.yml): a PR merged to main with
2
+ # release:major, release:minor or release:patch is released; with none, it is
3
+ # not. This creates the labels if they are missing and fails a PR that carries
4
+ # more than one.
5
+ name: Release label
6
+
7
+ on:
8
+ pull_request:
9
+ types: [opened, reopened, labeled, unlabeled, synchronize]
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ jobs:
15
+ label:
16
+ runs-on: ubuntu-24.04
17
+ permissions:
18
+ issues: write # creating the labels
19
+ steps:
20
+ - name: Create the release labels
21
+ # A fork's PR gets a read-only token: the labels are created by the next
22
+ # PR from this repository instead.
23
+ if: github.event.pull_request.head.repo.full_name == github.repository
24
+ env:
25
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
26
+ REPO: ${{ github.repository }}
27
+ run: |
28
+ gh label create release:major --repo "$REPO" --color b60205 --force \
29
+ --description "Merging releases a new major version (X.0.0)"
30
+ gh label create release:minor --repo "$REPO" --color fbca04 --force \
31
+ --description "Merging releases a new minor version (x.Y.0)"
32
+ gh label create release:patch --repo "$REPO" --color 0e8a16 --force \
33
+ --description "Merging releases a new patch version (x.y.Z)"
34
+ - name: At most one release label
35
+ env:
36
+ LABELS: ${{ toJSON(github.event.pull_request.labels.*.name) }}
37
+ run: |
38
+ count=$(echo "$LABELS" | jq '[.[] | select(test("^release:(major|minor|patch)$"))] | length')
39
+ if [ "$count" -gt 1 ]; then
40
+ echo "::error::More than one release label: pick one of release:major, release:minor, release:patch"
41
+ exit 1
42
+ fi
43
+ if [ "$count" -eq 0 ]; then
44
+ echo "No release label: merging does not release"
45
+ fi
@@ -0,0 +1,280 @@
1
+ # Cuts releases of carla-canvas and publishes its wheels and sdist to PyPI.
2
+ #
3
+ # The wheels carry the sensor plugin with gRPC, protobuf and yaml-cpp built
4
+ # from source and linked in statically (CARLA_CANVAS_FETCH_DEPS), so
5
+ # `pip install 'carla-canvas[client]'` needs no system packages: one
6
+ # py3-none-manylinux_2_28 wheel per architecture.
7
+ #
8
+ # There are three ways in, and the first is the everyday one:
9
+ #
10
+ # push to main the auto-release path. When the merged PR carries exactly one
11
+ # `release:major|minor|patch` label, the `release` job bumps the
12
+ # version (tools/bump_version.py), commits it to main, tags
13
+ # v<version> and drafts a GitHub release; the tag is built and
14
+ # published to PyPI in this same run, and the GitHub release is
15
+ # published last. No label, no release.
16
+ # push a tag v<version> a hand-cut release: built and published directly
17
+ # (the tag must match python/carla_canvas/__init__.py).
18
+ # workflow_dispatch a dry run: builds and checks; with publish=true it
19
+ # uploads to TestPyPI.
20
+ #
21
+ # It is one workflow rather than a separate `on: push: tags` one on purpose: the
22
+ # auto-release tag is pushed with GITHUB_TOKEN, and a tag pushed with
23
+ # GITHUB_TOKEN starts no new workflow runs, so the follow-up jobs of the run
24
+ # that cut it build and publish it.
25
+ #
26
+ # Publishing uses PyPI Trusted Publishing (OIDC) from the "pypi" / "testpypi"
27
+ # environments: register this workflow as the project's trusted publisher on
28
+ # both indexes. No API tokens are stored.
29
+ name: Release
30
+
31
+ on:
32
+ push:
33
+ branches: [main]
34
+ tags: ["v*"]
35
+ workflow_dispatch:
36
+ inputs:
37
+ publish:
38
+ description: "Upload to TestPyPI"
39
+ type: boolean
40
+ default: false
41
+
42
+ permissions:
43
+ contents: read
44
+
45
+ jobs:
46
+ # Reads the release label off the merged PR and, if there is one, bumps the
47
+ # version and pushes the tag. Only on a push to main.
48
+ release:
49
+ name: cut the release tag
50
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
51
+ runs-on: ubuntu-24.04
52
+ permissions:
53
+ contents: write
54
+ pull-requests: read
55
+ # Never two release cuts at once; a queued one runs after (and fails on the
56
+ # non-fast-forward push if main moved).
57
+ concurrency:
58
+ group: release-main
59
+ cancel-in-progress: false
60
+ outputs:
61
+ bump: ${{ steps.pr.outputs.bump }}
62
+ tag: ${{ steps.bump.outputs.tag }}
63
+ steps:
64
+ - name: Read the release label off the merged PR
65
+ id: pr
66
+ env:
67
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
68
+ run: |
69
+ set -euo pipefail
70
+ # An API error fails the job; only an empty list means "no PR".
71
+ pr_json=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \
72
+ --jq '.[0] // empty')
73
+ if [ -z "$pr_json" ]; then
74
+ echo "No PR associated with this push: nothing to release"
75
+ echo "bump=none" >> "$GITHUB_OUTPUT"
76
+ exit 0
77
+ fi
78
+ labels=$(echo "$pr_json" | jq -r '[.labels[].name] | join(",")')
79
+ bump=none
80
+ count=0
81
+ for lvl in major minor patch; do
82
+ if echo ",$labels," | grep -q ",release:${lvl},"; then
83
+ bump=$lvl
84
+ count=$((count + 1))
85
+ fi
86
+ done
87
+ if [ "$count" -gt 1 ]; then
88
+ echo "::error::More than one release label on the PR: pick exactly one"
89
+ exit 1
90
+ fi
91
+ echo "bump=${bump}" >> "$GITHUB_OUTPUT"
92
+ echo "Resolved bump level: ${bump}"
93
+
94
+ # The pushed commit, not whatever main is now: the bump is pushed to main
95
+ # without force, so if main has moved since, the push fails loudly.
96
+ - uses: actions/checkout@v4
97
+ if: steps.pr.outputs.bump != 'none'
98
+ with:
99
+ ref: ${{ github.sha }}
100
+ fetch-depth: 0
101
+
102
+ - name: Bump the version
103
+ if: steps.pr.outputs.bump != 'none'
104
+ id: bump
105
+ env:
106
+ BUMP: ${{ steps.pr.outputs.bump }}
107
+ run: |
108
+ set -euo pipefail
109
+ next=$(python3 tools/bump_version.py "$BUMP")
110
+ echo "version=${next}" >> "$GITHUB_OUTPUT"
111
+ echo "tag=v${next}" >> "$GITHUB_OUTPUT"
112
+ echo "Bumped to ${next} (${BUMP})"
113
+
114
+ - name: Fail if the version is already tagged or on PyPI
115
+ if: steps.pr.outputs.bump != 'none'
116
+ env:
117
+ VERSION: ${{ steps.bump.outputs.version }}
118
+ TAG: ${{ steps.bump.outputs.tag }}
119
+ run: |
120
+ set -euo pipefail
121
+ if git rev-parse "refs/tags/${TAG}" >/dev/null 2>&1; then
122
+ echo "::error::Tag ${TAG} already exists"
123
+ exit 1
124
+ fi
125
+ code=$(curl -s -o /dev/null -w '%{http_code}' "https://pypi.org/pypi/carla-canvas/${VERSION}/json")
126
+ if [ "$code" = 200 ]; then
127
+ echo "::error::carla-canvas ${VERSION} is already on PyPI"
128
+ exit 1
129
+ fi
130
+
131
+ - name: Commit, tag and push
132
+ if: steps.pr.outputs.bump != 'none'
133
+ env:
134
+ TAG: ${{ steps.bump.outputs.tag }}
135
+ run: |
136
+ set -euo pipefail
137
+ git config user.name "github-actions[bot]"
138
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
139
+ git add python/carla_canvas/__init__.py
140
+ git commit -m "chore(release): ${TAG}"
141
+ git tag -a "${TAG}" -m "Release ${TAG}"
142
+ # Both or neither: a main bump without its tag could not be re-run.
143
+ git push --atomic origin "HEAD:main" "refs/tags/${TAG}"
144
+
145
+ # A draft until `github-release` publishes it, after PyPI.
146
+ - name: Draft the GitHub release with generated notes
147
+ if: steps.pr.outputs.bump != 'none'
148
+ env:
149
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
150
+ TAG: ${{ steps.bump.outputs.tag }}
151
+ run: gh release create "${TAG}" --draft --verify-tag --title "${TAG}" --generate-notes
152
+
153
+ # For all three entry points: whether to build, which commit, and where to
154
+ # publish. Runs even when `release` is skipped (tag push, dispatch).
155
+ resolve:
156
+ name: resolve what to build
157
+ needs: [release]
158
+ if: always() && needs.release.result != 'failure' && needs.release.result != 'cancelled'
159
+ runs-on: ubuntu-24.04
160
+ outputs:
161
+ proceed: ${{ steps.r.outputs.proceed }}
162
+ ref: ${{ steps.r.outputs.ref }}
163
+ tag: ${{ steps.r.outputs.tag }}
164
+ index: ${{ steps.r.outputs.index }}
165
+ steps:
166
+ - id: r
167
+ env:
168
+ EVENT: ${{ github.event_name }}
169
+ REF_TYPE: ${{ github.ref_type }}
170
+ REF_NAME: ${{ github.ref_name }}
171
+ SHA: ${{ github.sha }}
172
+ BUMP: ${{ needs.release.outputs.bump }}
173
+ CUT_TAG: ${{ needs.release.outputs.tag }}
174
+ INPUT_PUBLISH: ${{ inputs.publish }}
175
+ run: |
176
+ set -euo pipefail
177
+ proceed=true; ref="$SHA"; tag=""; index=none
178
+ if [ "$EVENT" = push ] && [ "$REF_TYPE" = branch ]; then
179
+ if [ "${BUMP:-none}" = none ]; then
180
+ proceed=false
181
+ else
182
+ ref="refs/tags/${CUT_TAG}"; tag="$CUT_TAG"; index=pypi
183
+ fi
184
+ elif [ "$EVENT" = push ]; then
185
+ ref="refs/tags/${REF_NAME}"; tag="$REF_NAME"; index=pypi
186
+ elif [ "$INPUT_PUBLISH" = true ]; then
187
+ index=testpypi
188
+ fi
189
+ {
190
+ echo "proceed=$proceed"; echo "ref=$ref"; echo "tag=$tag"; echo "index=$index"
191
+ } >> "$GITHUB_OUTPUT"
192
+ echo "proceed=$proceed ref=$ref tag=$tag index=$index"
193
+
194
+ version:
195
+ needs: resolve
196
+ if: always() && needs.resolve.outputs.proceed == 'true'
197
+ runs-on: ubuntu-24.04
198
+ steps:
199
+ - uses: actions/checkout@v4
200
+ with: { ref: "${{ needs.resolve.outputs.ref }}" }
201
+ - name: The tag matches the package version
202
+ env:
203
+ TAG: ${{ needs.resolve.outputs.tag }}
204
+ run: |
205
+ version=$(python3 tools/bump_version.py --current)
206
+ echo "carla-canvas ${version}"
207
+ if [ -n "$TAG" ] && [ "$TAG" != "v$version" ]; then
208
+ echo "::error::tag $TAG does not match the package version $version"
209
+ exit 1
210
+ fi
211
+
212
+ wheel:
213
+ name: wheel (${{ matrix.arch.name }})
214
+ needs: [resolve, version]
215
+ if: always() && needs.version.result == 'success'
216
+ strategy:
217
+ fail-fast: false
218
+ matrix:
219
+ arch:
220
+ - { name: x86_64, runner: ubuntu-24.04 }
221
+ - { name: aarch64, runner: ubuntu-24.04-arm }
222
+ runs-on: ${{ matrix.arch.runner }}
223
+ steps:
224
+ - uses: actions/checkout@v4
225
+ with: { ref: "${{ needs.resolve.outputs.ref }}" }
226
+ - uses: ./.github/actions/wheel
227
+ with:
228
+ arch: ${{ matrix.arch.name }}
229
+ - uses: actions/upload-artifact@v4
230
+ with: { name: "dist-wheel-${{ matrix.arch.name }}", path: dist/*.whl }
231
+
232
+ sdist:
233
+ needs: [resolve, version]
234
+ if: always() && needs.version.result == 'success'
235
+ runs-on: ubuntu-24.04
236
+ steps:
237
+ - uses: actions/checkout@v4
238
+ with: { ref: "${{ needs.resolve.outputs.ref }}" }
239
+ - uses: actions/setup-python@v5
240
+ with: { python-version: "3.12" }
241
+ - run: pipx run build --sdist
242
+ - uses: actions/upload-artifact@v4
243
+ with: { name: dist-sdist, path: dist/*.tar.gz }
244
+
245
+ publish:
246
+ needs: [resolve, wheel, sdist]
247
+ if: >-
248
+ always() && needs.wheel.result == 'success' && needs.sdist.result == 'success'
249
+ && needs.resolve.outputs.index != 'none'
250
+ runs-on: ubuntu-24.04
251
+ environment: ${{ needs.resolve.outputs.index }}
252
+ permissions:
253
+ id-token: write # Trusted Publishing
254
+ steps:
255
+ - uses: actions/download-artifact@v4
256
+ with: { pattern: "dist-*", path: dist, merge-multiple: true }
257
+ - uses: pypa/gh-action-pypi-publish@release/v1
258
+ with:
259
+ repository-url: ${{ needs.resolve.outputs.index == 'pypi' && 'https://upload.pypi.org/legacy/' || 'https://test.pypi.org/legacy/' }}
260
+
261
+ # Publishes the GitHub release once PyPI has the version: the auto-release
262
+ # path drafted it in `release`; a hand-cut tag gets one here.
263
+ github-release:
264
+ needs: [resolve, publish]
265
+ if: always() && needs.publish.result == 'success' && needs.resolve.outputs.tag != ''
266
+ runs-on: ubuntu-24.04
267
+ permissions:
268
+ contents: write
269
+ steps:
270
+ - env:
271
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
272
+ TAG: ${{ needs.resolve.outputs.tag }}
273
+ REPO: ${{ github.repository }}
274
+ run: |
275
+ set -euo pipefail
276
+ if gh release view "$TAG" --repo "$REPO" >/dev/null 2>&1; then
277
+ gh release edit "$TAG" --repo "$REPO" --draft=false
278
+ else
279
+ gh release create "$TAG" --repo "$REPO" --verify-tag --title "$TAG" --generate-notes
280
+ fi
@@ -0,0 +1,7 @@
1
+ build/
2
+ dist/
3
+ .venv/
4
+ __pycache__/
5
+ *.egg-info/
6
+ *.so
7
+ demo_assets/
@@ -0,0 +1,54 @@
1
+ # carla_canvas
2
+
3
+ Swaps CARLA's camera/LiDAR rendering (3D Gaussian splatting or any renderer)
4
+ behind the CARLA API for typesafe_carla programs: a typesafe_carla sensor
5
+ plugin (C++, `native/`) ↔ gRPC (`proto/carla_canvas/v1/renderer.proto`) ↔
6
+ renderer service (Python, `python/carla_canvas/`). Design: `docs/design.md`.
7
+
8
+ ## Commands
9
+
10
+ ```sh
11
+ uv venv && TSC_BACKEND=mock uv pip install -e ../typesafe_carla # typesafe_carla with ABI >= 5.3
12
+ uv pip install scikit-build-core grpcio grpcio-tools protobuf numpy pyyaml pytest cyclonedds
13
+ uv pip install --no-build-isolation -e . # builds libcarla_canvas_plugin.so
14
+ cmake -S . -B build && cmake --build build -j && ctest --test-dir build
15
+ PATH=$PWD/.venv/bin:$PATH .venv/bin/python -m pytest # e2e tests run Codon programs (mock CARLA)
16
+ .venv/bin/python tools/gen_proto.py # after editing proto/ (commit the output)
17
+ CARLA_CANVAS_CONFIG=examples/primitives.yaml typesafe-codon run examples/camera_lidar.py
18
+ ```
19
+
20
+ The plugin uses the system's gRPC C++, protobuf and yaml-cpp (`libgrpc++-dev
21
+ protobuf-compiler-grpc libprotobuf-dev protobuf-compiler libyaml-cpp-dev`), or
22
+ builds them from source with `-DCARLA_CANVAS_FETCH_DEPS=ON` (`cmake/FetchDeps.cmake`,
23
+ pinned versions; what the release wheels do: `.github/actions/wheel`,
24
+ `tools/check_wheel.py`).
25
+
26
+ Releases: a PR merged to main with one `release:major|minor|patch` label is
27
+ released by `.github/workflows/release.yml` (bumps `__version__` with
28
+ `tools/bump_version.py`, tags `v<version>`, publishes to PyPI). Do not bump the
29
+ version or tag by hand.
30
+
31
+ ## Rules
32
+
33
+ - The user's program never changes: everything is chosen by the file in
34
+ `CARLA_CANVAS_CONFIG`. Do not add carla_canvas imports to examples or tests
35
+ of programs (`examples/camera_lidar.py` is typesafe_carla's example).
36
+ - The protocol mirrors the CARLA API (names, units, Unreal's left-handed
37
+ frame, world transforms, CARLA's raw measurement layouts). A protocol
38
+ change bumps `kProtocolVersion` (plugin.cpp) and `PROTOCOL_VERSION`
39
+ (server/service.py) together, and regenerates `python/carla_canvas/v1/`.
40
+ - CARLA semantics live in the service, not in renderers: encodings
41
+ (`encodings.py`), the LiDAR model (`lidar.py`, mirrors RayCastLidar), sensor
42
+ attributes and defaults (`sensors.py`), ROS 2 (`ros/`). A renderer only
43
+ implements `load_world` / `render_camera` (and optionally `cast_rays`).
44
+ - ROS 2 output mirrors CARLA's publishers (`LibCarla/source/carla/ros2`):
45
+ topic names, types, frame ids, y negation, TransformFromCarlaRotation.
46
+ Renderers never implement DDS.
47
+ - The plugin talks to typesafe_carla only through `get_proc` (`tsc_api.hpp`);
48
+ it must not link against the library. It needs typesafe_carla's C ABI 5.3+.
49
+ - 3D Gaussian splatting for real use is splatsim's backend (its repository
50
+ depends on carla_canvas). `renderers/gaussian_splatting.py` and
51
+ `primitives.py` are reference and test renderers: keep them numpy-only and
52
+ covered by the CPU tests.
53
+ - `ros/dds_types.py` must not use `from __future__ import annotations`
54
+ (cyclonedds resolves field types from the module's globals).
@@ -0,0 +1,155 @@
1
+ cmake_minimum_required(VERSION 3.20)
2
+ project(carla_canvas LANGUAGES C CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_POSITION_INDEPENDENT_CODE ON)
7
+ if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
8
+ set(CMAKE_BUILD_TYPE Release)
9
+ endif()
10
+
11
+ # The sensor plugin needs gRPC C++, protobuf, yaml-cpp and typesafe_carla's
12
+ # header. Without them (a renderer-only install: the Python SDK is all a
13
+ # renderer host needs) it is skipped, unless CARLA_CANVAS_REQUIRE_PLUGIN is ON.
14
+ # With CARLA_CANVAS_FETCH_DEPS, the missing ones are built from source (the
15
+ # published wheels; a source build without system packages).
16
+ option(CARLA_CANVAS_REQUIRE_PLUGIN "Fail when the sensor plugin cannot be built" OFF)
17
+ option(CARLA_CANVAS_FETCH_DEPS
18
+ "Build missing gRPC, protobuf and yaml-cpp from source, and download typesafe_carla's header"
19
+ OFF)
20
+ find_package(Threads REQUIRED)
21
+ find_package(Protobuf QUIET)
22
+ find_package(gRPC CONFIG QUIET)
23
+ find_package(yaml-cpp QUIET)
24
+ find_program(GRPC_CPP_PLUGIN grpc_cpp_plugin)
25
+ if(CARLA_CANVAS_FETCH_DEPS)
26
+ include(cmake/FetchDeps.cmake)
27
+ endif()
28
+ # gRPC and protobuf: the system's, or both from gRPC's source tree.
29
+ if(Protobuf_FOUND AND gRPC_FOUND AND GRPC_CPP_PLUGIN)
30
+ set(_protoc protobuf::protoc)
31
+ set(_grpc_cpp_plugin "${GRPC_CPP_PLUGIN}")
32
+ set(_protobuf_lib protobuf::libprotobuf)
33
+ set(_grpc_lib gRPC::grpc++)
34
+ elseif(CARLA_CANVAS_FETCH_DEPS)
35
+ carla_canvas_fetch_grpc()
36
+ set(_protoc protoc)
37
+ set(_grpc_cpp_plugin "$<TARGET_FILE:grpc_cpp_plugin>")
38
+ set(_protobuf_lib libprotobuf)
39
+ set(_grpc_lib grpc++)
40
+ # protobuf's well-known types (google/protobuf/struct.proto)
41
+ set(_proto_include --proto_path "${grpc_SOURCE_DIR}/third_party/protobuf/src")
42
+ endif()
43
+ if(NOT yaml-cpp_FOUND AND CARLA_CANVAS_FETCH_DEPS)
44
+ carla_canvas_fetch_yaml_cpp()
45
+ endif()
46
+ # ---------------------------------------------------------------------------
47
+ # typesafe_carla's C ABI header (typesafe_carla/ffi.h, ABI 5.3 or later).
48
+ # The plugin does not link against the library (it gets the functions through
49
+ # get_proc at load time), it only needs the declarations. Found, in order:
50
+ # TYPESAFE_CARLA_INCLUDE_DIR, the installed typesafe_carla Python package
51
+ # (typesafe_carla.paths.include_dir()), a sibling checkout ../typesafe_carla.
52
+ # ---------------------------------------------------------------------------
53
+ set(TYPESAFE_CARLA_INCLUDE_DIR "$ENV{TYPESAFE_CARLA_INCLUDE_DIR}" CACHE PATH
54
+ "Directory containing typesafe_carla/ffi.h")
55
+ if(NOT TYPESAFE_CARLA_INCLUDE_DIR)
56
+ find_package(Python COMPONENTS Interpreter QUIET)
57
+ if(Python_EXECUTABLE)
58
+ execute_process(
59
+ COMMAND "${Python_EXECUTABLE}" -c
60
+ "from typesafe_carla import paths; print(paths.include_dir())"
61
+ OUTPUT_VARIABLE _tsc_include OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET
62
+ RESULT_VARIABLE _tsc_result)
63
+ if(_tsc_result EQUAL 0 AND EXISTS "${_tsc_include}/typesafe_carla/ffi.h")
64
+ set(TYPESAFE_CARLA_INCLUDE_DIR "${_tsc_include}")
65
+ endif()
66
+ endif()
67
+ endif()
68
+ if(NOT TYPESAFE_CARLA_INCLUDE_DIR AND
69
+ EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/../typesafe_carla/native/include/typesafe_carla/ffi.h")
70
+ set(TYPESAFE_CARLA_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../typesafe_carla/native/include")
71
+ endif()
72
+ if(NOT TYPESAFE_CARLA_INCLUDE_DIR AND CARLA_CANVAS_FETCH_DEPS)
73
+ carla_canvas_fetch_tsc_header(TYPESAFE_CARLA_INCLUDE_DIR)
74
+ endif()
75
+ set(_missing "")
76
+ if(NOT EXISTS "${TYPESAFE_CARLA_INCLUDE_DIR}/typesafe_carla/ffi.h")
77
+ list(APPEND _missing "typesafe_carla/ffi.h (install typesafe-carla or set TYPESAFE_CARLA_INCLUDE_DIR)")
78
+ endif()
79
+ if(NOT _grpc_lib)
80
+ list(APPEND _missing "gRPC C++ and protobuf")
81
+ endif()
82
+ if(NOT TARGET yaml-cpp::yaml-cpp)
83
+ list(APPEND _missing "yaml-cpp")
84
+ endif()
85
+ if(_missing AND NOT CARLA_CANVAS_FETCH_DEPS)
86
+ list(APPEND _missing "(or -DCARLA_CANVAS_FETCH_DEPS=ON to build them from source)")
87
+ endif()
88
+ if(_missing)
89
+ if(CARLA_CANVAS_REQUIRE_PLUGIN)
90
+ message(FATAL_ERROR "carla_canvas: cannot build the sensor plugin, missing: ${_missing}")
91
+ endif()
92
+ message(WARNING "carla_canvas: not building the sensor plugin (renderer-only install), "
93
+ "missing: ${_missing}")
94
+ return()
95
+ endif()
96
+ message(STATUS "carla_canvas: typesafe_carla header from ${TYPESAFE_CARLA_INCLUDE_DIR}")
97
+
98
+ # ---------------------------------------------------------------------------
99
+ # gRPC / protobuf / yaml-cpp (Ubuntu: libgrpc++-dev protobuf-compiler-grpc
100
+ # libprotobuf-dev protobuf-compiler libyaml-cpp-dev)
101
+ # ---------------------------------------------------------------------------
102
+
103
+ set(_proto_dir "${CMAKE_CURRENT_SOURCE_DIR}/proto")
104
+ set(_proto "${_proto_dir}/carla_canvas/v1/renderer.proto")
105
+ set(_gen "${CMAKE_CURRENT_BINARY_DIR}/generated")
106
+ set(_gen_srcs
107
+ "${_gen}/carla_canvas/v1/renderer.pb.cc"
108
+ "${_gen}/carla_canvas/v1/renderer.grpc.pb.cc")
109
+ add_custom_command(
110
+ OUTPUT ${_gen_srcs}
111
+ "${_gen}/carla_canvas/v1/renderer.pb.h"
112
+ "${_gen}/carla_canvas/v1/renderer.grpc.pb.h"
113
+ COMMAND "${CMAKE_COMMAND}" -E make_directory "${_gen}"
114
+ COMMAND ${_protoc}
115
+ --proto_path "${_proto_dir}" ${_proto_include}
116
+ --cpp_out "${_gen}"
117
+ --grpc_out "${_gen}"
118
+ --plugin=protoc-gen-grpc=${_grpc_cpp_plugin}
119
+ "${_proto}"
120
+ DEPENDS "${_proto}" ${_protoc} $<$<TARGET_EXISTS:grpc_cpp_plugin>:grpc_cpp_plugin>
121
+ COMMENT "Generating the renderer protocol (C++)")
122
+
123
+ # ---------------------------------------------------------------------------
124
+ # The sensor plugin (loaded by typesafe_carla: TYPESAFE_CARLA_SENSOR_PLUGIN)
125
+ # ---------------------------------------------------------------------------
126
+ add_library(carla_canvas_plugin MODULE
127
+ native/src/plugin.cpp
128
+ native/src/config.cpp
129
+ ${_gen_srcs})
130
+ target_include_directories(carla_canvas_plugin PRIVATE
131
+ "${TYPESAFE_CARLA_INCLUDE_DIR}" "${_gen}" native/src)
132
+ target_link_libraries(carla_canvas_plugin PRIVATE
133
+ ${_grpc_lib} ${_protobuf_lib} yaml-cpp::yaml-cpp Threads::Threads)
134
+ set_target_properties(carla_canvas_plugin PROPERTIES
135
+ OUTPUT_NAME carla_canvas_plugin
136
+ CXX_VISIBILITY_PRESET hidden
137
+ VISIBILITY_INLINES_HIDDEN ON)
138
+ if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
139
+ target_compile_options(carla_canvas_plugin PRIVATE -Wall -Wextra)
140
+ # Export only tsc_sensor_plugin_init.
141
+ target_link_options(carla_canvas_plugin PRIVATE
142
+ "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/native/src/exports.map")
143
+ endif()
144
+
145
+ install(TARGETS carla_canvas_plugin LIBRARY DESTINATION carla_canvas/_native
146
+ COMPONENT carla_canvas)
147
+
148
+ option(CARLA_CANVAS_BUILD_TESTS "Build native tests" ON)
149
+ if(CARLA_CANVAS_BUILD_TESTS)
150
+ enable_testing()
151
+ add_executable(test_config native/tests/test_config.cpp native/src/config.cpp)
152
+ target_include_directories(test_config PRIVATE native/src)
153
+ target_link_libraries(test_config PRIVATE ${_protobuf_lib} yaml-cpp::yaml-cpp)
154
+ add_test(NAME test_config COMMAND test_config "${CMAKE_CURRENT_SOURCE_DIR}/native/tests")
155
+ endif()
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 carla_canvas contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.