Skip to content

Commit dd126ed

Browse files
feat(pj_base): add VoxelGrid canonical builtin object (SDK 0.10.0) (#130)
* feat(pj_base): add VoxelGrid canonical builtin object + codec Add sdk::VoxelGrid, the dense 3D voxel-grid builtin — the volumetric sibling of OccupancyGrid, reusing PointCloud's PointField channel model. The struct mirrors foxglove.VoxelGrid's byte layout (Z-Y-X order, three strides) so a lazily-parsing producer exposes `data` as a zero-copy Span<const uint8_t> + BufferAnchor rather than transcoding cells. The per-voxel value is generic (occupancy/cost/ESDF/semantic via `fields`); which voxels are drawn stays a viewer-side decision. - voxel_grid.hpp / voxel_grid_codec.{hpp,cpp} + proto/pj/VoxelGrid.proto - kVoxelGrid = 18 appended to BuiltinObjectType and PJ_builtin_object_type_t (append-only; type 2 stays reserved) - round-trip + empty-buffer tests; builtin_object_test enumerations extended - docs: builtin_type.md (type row, section, conversion), proto README, builtin-count bumps + 3-source version mechanics in CLAUDE.md Build Debug+ASAN green; voxel_grid_codec_test 4/4, builtin_object_test 6/6. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(release): bump version to 0.10.0 VoxelGrid is a backward-compatible addition (tail-appended enum value + two new exported symbols), so this is a MINOR bump and abi/baseline.abi is unchanged. Aligns all three version sources — conanfile.py, CMakeLists.txt (PJ_PACKAGE_VERSION), and recipe.yaml (context.version) — so a v0.10.0 tag passes the conda-release guard. recipe.yaml had drifted to 0.8.1 at the v0.9.0 tag, which is why the conda/pixi 0.9.0 publish failed while the Conan/GitHub release succeeded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent bf1e37c commit dd126ed

16 files changed

Lines changed: 535 additions & 14 deletions

CLAUDE.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ two modules below have no own CLAUDE.md.
1515
### Modules
1616

1717
- **pj_base** — vocabulary types (`Timestamp`, `DatasetId`, `Expected<T>`, `Span<T>`, type trees),
18-
the canonical builtin object vocabulary (`pj_base/builtin/`: 16 struct headers — Image, DepthImage,
18+
the canonical builtin object vocabulary (`pj_base/builtin/`: 17 struct headers — Image, DepthImage,
1919
PointCloud, CompressedPointCloud, OccupancyGrid(+Update), Mesh3D, VideoFrame, AssetVideo,
20-
SceneEntities, RobotDescription, CameraInfo, Log, ImageAnnotations, FrameTransforms, PosesInFrame) and their 15
20+
SceneEntities, RobotDescription, CameraInfo, Log, ImageAnnotations, FrameTransforms, PosesInFrame, VoxelGrid) and their 16
2121
wire codecs (RobotDescription carries source text as-is — no codec), the C-ABI protocol headers for
2222
DataSource/MessageParser/Toolbox + the C++ SDK base classes / host-view helpers built on them.
2323
- **pj_plugins** — host-side loaders + RAII handles + plugin discovery/catalog for four plugin
@@ -102,11 +102,14 @@ changes within `0.x`** — the next ABI/API break ships as `1.0.0`. So a plugin
102102
`[>=0.Y.Z <1.0.0]`. (Deliberately stricter than the usual "0.x may break" convention, because
103103
plugins pin against this SDK.)
104104

105-
**Mechanics.** The version lives in two places that must stay in sync — `version` in `conanfile.py`
106-
and `PJ_PACKAGE_VERSION` in the root `CMakeLists.txt` (also update the example tag in the
107-
`conanfile.py` docstring). A non-MAJOR PR must not alter `abi/baseline.abi` beyond additions (verify
108-
with `abidiff`). Tagging and pushing a release is a separate, explicitly-authorized step — never tag
109-
or push a release without the user's go-ahead.
105+
**Mechanics.** The version lives in **three** places that must stay in sync — `version` in
106+
`conanfile.py`, `PJ_PACKAGE_VERSION` in the root `CMakeLists.txt`, and `context.version` in
107+
`recipe.yaml` (the conda/pixi package version `rattler-build` embeds; `pixi.toml` itself carries
108+
**no** version) — plus the example tag in the `conanfile.py` docstring. The `conda-release.yml`
109+
release job hard-fails ("Verify tag matches all version sources") if the `v*` tag and any of the
110+
three disagree, so a bump that misses `recipe.yaml` cannot be released. A non-MAJOR PR must not
111+
alter `abi/baseline.abi` beyond additions (verify with `abidiff`). Tagging and pushing a release is
112+
a separate, explicitly-authorized step — never tag or push a release without the user's go-ahead.
110113

111114
## Coding Conventions
112115

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ endif()
111111
if(PJ_INSTALL_SDK)
112112
include(CMakePackageConfigHelpers)
113113

114-
set(PJ_PACKAGE_VERSION "0.9.0")
114+
set(PJ_PACKAGE_VERSION "0.10.0")
115115
set(PJ_PACKAGE_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/plotjuggler_sdk)
116116

117117
install(EXPORT plotjuggler_sdkTargets

conanfile.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
plugin_sdk — umbrella for plugin authors (base + dialog SDK + parser SDK)
77
plugin_host — umbrella for host loaders (data_source/parser/toolbox/dialog)
88
9-
A consuming Conan recipe declares e.g. `plotjuggler_sdk/0.9.0` and then:
9+
A consuming Conan recipe declares e.g. `plotjuggler_sdk/0.10.0` and then:
1010
1111
find_package(plotjuggler_sdk REQUIRED COMPONENTS plugin_sdk)
1212
target_link_libraries(my_plugin PRIVATE plotjuggler_sdk::plugin_sdk)
@@ -30,7 +30,7 @@
3030

3131
class PlotjugglerSdkConan(ConanFile):
3232
name = "plotjuggler_sdk"
33-
version = "0.9.0"
33+
version = "0.10.0"
3434
# Apache-2.0 covers the whole SDK (pj_base + pj_plugins). See LICENSE.
3535
license = "Apache-2.0"
3636
url = "https://github.com/PlotJuggler/plotjuggler_sdk"

docs/builtin_type.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Builtin objects fall into two serialization families:
8585

8686
| Family | Current types | Storage model | Codec policy |
8787
|--------|---------------|---------------|--------------|
88-
| Byte-backed views | `Image`, `DepthImage`, `PointCloud`, `CompressedPointCloud`, `OccupancyGrid`, `OccupancyGridUpdate`, `Mesh3D`, `VideoFrame` | Header fields live in the SDK struct; payload bytes live behind `Span<const uint8_t>` plus `BufferAnchor`. | No mandatory canonical codec; preserve zero-copy views over ROS, MCAP, compressed image, point-cloud, or plugin-owned payloads. If conversion is unavoidable, allocate a new payload and anchor it. |
88+
| Byte-backed views | `Image`, `DepthImage`, `PointCloud`, `CompressedPointCloud`, `OccupancyGrid`, `OccupancyGridUpdate`, `VoxelGrid`, `Mesh3D`, `VideoFrame` | Header fields live in the SDK struct; payload bytes live behind `Span<const uint8_t>` plus `BufferAnchor`. | No mandatory canonical codec; preserve zero-copy views over ROS, MCAP, compressed image, point-cloud, or plugin-owned payloads. If conversion is unavoidable, allocate a new payload and anchor it. |
8989
| Owned values | `ImageAnnotations`, `FrameTransforms`, `SceneEntities`, `AssetVideo`, `RobotDescription`, `CameraInfo`, `PosesInFrame`; future marker types | SDK structs own their vectors/strings/scalars directly. | Add explicit codecs when canonical bytes are needed. Codecs serialize the owned value to the protobuf-wire payload described by the `.proto` contract, using shared private wire primitives. `RobotDescription` carries source-format text as-is (no canonical codec) — the format hint distinguishes URDF / SDF / MJCF. |
9090

9191
Canonical `.proto` files live under `pj_base/proto/pj` and act as the wire
@@ -126,6 +126,7 @@ annotations, frame transforms, or no builtin object.
126126
| `kOccupancyGridUpdate` | `PJ::sdk::OccupancyGridUpdate` | Incremental sub-rectangle patch for a previously-published `OccupancyGrid`. |
127127
| `kLog` | `PJ::sdk::Log` | Textual log message (severity level + text + originating name). |
128128
| `kPosesInFrame` | `PJ::sdk::PosesInFrame` | Array of poses in one frame (PoseArray / particle clouds); styling is viewer-side. |
129+
| `kVoxelGrid` | `PJ::sdk::VoxelGrid` | Dense 3D voxel grid (occupancy/cost/ESDF/semantic); the volumetric sibling of `OccupancyGrid`. |
129130

130131
`BuiltinObject` is `std::any`. Producers store a concrete builtin value in it;
131132
consumers recover the concrete type with `std::any_cast<T>(&object)` or ask
@@ -593,6 +594,40 @@ so no `BufferAnchor` is needed.
593594
`pj_base/builtin/poses_in_frame_codec.hpp` serializes and deserializes this
594595
type using the canonical `PJ.PosesInFrame` protobuf wire format.
595596

597+
## VoxelGrid
598+
599+
`VoxelGrid` is a dense 3D voxel grid placed in world coordinates — the
600+
volumetric sibling of `OccupancyGrid` — for 3D occupancy maps, costmaps,
601+
ESDFs, and semantic grids (e.g. `foxglove.VoxelGrid`, `costmap_2d/VoxelGrid`).
602+
603+
It is a byte-backed view: the lattice of `column_count * row_count *
604+
slice_count` voxels lives in `data` (a `Span<const uint8_t>` + `BufferAnchor`)
605+
in depth-major, row-major **Z-Y-X** order (x fastest), and the per-voxel layout
606+
is described by `fields` — the same `PointField` channel model used by
607+
`PointCloud`. The byte layout mirrors `foxglove.VoxelGrid` (three strides +
608+
Z-Y-X order) so a parser can hand out `data` zero-copy rather than transcoding
609+
millions of cells; expanding occupied voxels into renderable primitives is a
610+
viewer-side concern.
611+
612+
Unlike the 2D `OccupancyGrid` (which fixes `-1`/`0..100` cell semantics), the
613+
per-voxel **value is generic**: occupancy byte, RGBA, a float cost/intensity, a
614+
class id, etc. The draw predicate (which voxels are visible) and any colormap are
615+
viewer-side, so one type serves occupancy/cost/ESDF/semantic grids.
616+
617+
| Field | Type | Notes |
618+
|-------|------|-------|
619+
| `timestamp_ns` | `Timestamp` | Time of the grid. `0` when the source had none. |
620+
| `frame_id` | `std::string` | Source coordinate frame; 3D consumers TF-transform from it. |
621+
| `origin` | `Pose` | Lower-front-left `(0,0,0)` corner of the grid in `frame_id`. |
622+
| `cell_size` | `Vector3` | Metric voxel size along x/y/z (meters); need not be cubic. |
623+
| `column_count` / `row_count` / `slice_count` | `uint32_t` | Voxels along x / y / z. |
624+
| `cell_stride` / `row_stride` / `slice_stride` | `uint32_t` | Byte spacing of a voxel / row / z-slice (padding allowed). |
625+
| `fields` | `std::vector<PointField>` | Per-voxel channel layout. |
626+
| `data` | `Span<const uint8_t>` + `BufferAnchor` | Packed voxel bytes in Z-Y-X order. |
627+
628+
`pj_base/builtin/voxel_grid_codec.hpp` serializes and deserializes this type
629+
using the canonical `PJ.VoxelGrid` protobuf wire format.
630+
596631
## Conversion Examples
597632

598633
| Source type | Canonical builtin type | Conversion intent |
@@ -612,6 +647,7 @@ type using the canonical `PJ.PosesInFrame` protobuf wire format.
612647
| ROS `std_msgs/String` on `/robot_description` (or matching name) carrying URDF XML | `RobotDescription` | Validate root element matches `format`, then carry the raw text + format hint. No mesh resolution at parse time. |
613648
| ROS `sensor_msgs/CameraInfo` | `CameraInfo` | Map K / D / R / P plus dimensions; correlate to the image topic by name. Sub-window (binning / ROI) is dropped. |
614649
| ROS `map_msgs/OccupancyGridUpdate` | `OccupancyGridUpdate` | Forward the cell-space patch (`x`/`y`/`width`/`height` + bytes); the consumer pairs it with the base grid and supplies origin/resolution. |
650+
| `foxglove.VoxelGrid` / `costmap_2d/VoxelGrid` | `VoxelGrid` | Map counts/strides/`cell_size`/`origin` into the struct; keep voxel bytes zero-copy in Z-Y-X order. The draw predicate is viewer-side. |
615651

616652
The builtin type is the boundary object. After conversion, consumers should not
617653
need to know which third-party schema produced it.

pj_base/CLAUDE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# pj_base — SDK vocabulary, builtin object schemas, and the C plugin ABI
22

3-
pj_base is the **Level 0** foundation and the **SDK boundary** for plugin authors. It owns: the zero-dependency vocabulary types (`Timestamp`, `DatasetId`, `Range`, `Expected<T>`, `Span`, `TypeTree`); the canonical *builtin object* schemas (`sdk::Image`, `PointCloud`, `DepthImage`, `OccupancyGrid`, `FrameTransforms`, … — 16 types) **and 15 of their wire codecs** (RobotDescription has none); and the **C ABI** primitives every plugin family speaks (`plugin_data_api.h` + the service registry) plus the C-ABI protocol headers for **three** families — `data_source_protocol.h`, `message_parser_protocol.h`, `toolbox_protocol.h`. The **Dialog** protocol header is the exception: it lives in `pj_plugins/dialog_protocol/`, not here. It also ships the C++ SDK base classes for DataSource and Toolbox; the MessageParser and Dialog base classes live in `pj_plugins`. Builds as a STATIC lib with **zero public deps** — `fast_float` is a `BUILD_INTERFACE` private impl detail of `parseNumber`. Must NOT depend on `pj_datastore`, `pj_plugins`, Qt, or any Conan runtime lib. This is a read-only submodule subtree: change it only when explicitly working in `plotjuggler_sdk`.
3+
pj_base is the **Level 0** foundation and the **SDK boundary** for plugin authors. It owns: the zero-dependency vocabulary types (`Timestamp`, `DatasetId`, `Range`, `Expected<T>`, `Span`, `TypeTree`); the canonical *builtin object* schemas (`sdk::Image`, `PointCloud`, `DepthImage`, `OccupancyGrid`, `VoxelGrid`, `FrameTransforms`, … — 17 types) **and 16 of their wire codecs** (RobotDescription has none); and the **C ABI** primitives every plugin family speaks (`plugin_data_api.h` + the service registry) plus the C-ABI protocol headers for **three** families — `data_source_protocol.h`, `message_parser_protocol.h`, `toolbox_protocol.h`. The **Dialog** protocol header is the exception: it lives in `pj_plugins/dialog_protocol/`, not here. It also ships the C++ SDK base classes for DataSource and Toolbox; the MessageParser and Dialog base classes live in `pj_plugins`. Builds as a STATIC lib with **zero public deps** — `fast_float` is a `BUILD_INTERFACE` private impl detail of `parseNumber`. Must NOT depend on `pj_datastore`, `pj_plugins`, Qt, or any Conan runtime lib. This is a read-only submodule subtree: change it only when explicitly working in `plotjuggler_sdk`.
44

55
## Layout
66
- `include/pj_base/` — vocabulary primitives: `types.hpp`, `time.hpp` (absolute time spine: `Timepoint`/`Duration` + `fromRaw`/`toRaw`), `type_tree.hpp`, `dataset.hpp`, `expected.hpp`, `span.hpp`, `number_parse.hpp`, `assert.hpp`, `diagnostic_sink.hpp`, `buffer_anchor.hpp`.
7-
- `include/pj_base/builtin/` — the 16 builtin object struct headers (`*.hpp`; 17 enum values in `BuiltinObjectType`, value 2 reserved) + their 15 wire codecs (`*_codec.hpp`; RobotDescription has none) + the `BuiltinObject` (`std::any`) type-erased holder.
7+
- `include/pj_base/builtin/` — the 17 builtin object struct headers (`*.hpp`; 18 enum values in `BuiltinObjectType`, value 2 reserved) + their 16 wire codecs (`*_codec.hpp`; RobotDescription has none) + the `BuiltinObject` (`std::any`) type-erased holder.
88
- `include/pj_base/sdk/` — C++ SDK over the ABI: DataSource + Toolbox `*_plugin_base.hpp`, `service_registry.hpp`/`service_traits.hpp`, host views, Arrow RAII holders, `testing/`.
99
- `include/pj_base/*_protocol.h`, `plugin_data_api.h`, `builtin_object_abi.h`, `plugin_abi_export.hpp` — the stable C-ABI surface for DataSource/MessageParser/Toolbox (the Dialog protocol header lives in `pj_plugins/dialog_protocol/`).
1010
- `proto/pj/` — canonical `.proto` wire contracts for the builtin types (see its README).

pj_base/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ add_library(pj_base STATIC
1818
src/builtin/poses_in_frame_codec.cpp
1919
src/builtin/scene_entities_codec.cpp
2020
src/builtin/video_frame_codec.cpp
21+
src/builtin/voxel_grid_codec.cpp
2122
src/number_parse.cpp
2223
src/type_tree.cpp
2324
)
@@ -105,6 +106,7 @@ if(PJ_BUILD_TESTS)
105106
tests/asset_video_codec_test.cpp
106107
tests/time_spine_test.cpp
107108
tests/poses_in_frame_codec_test.cpp
109+
tests/voxel_grid_codec_test.cpp
108110
)
109111

110112
foreach(test_src ${PJ_BASE_TESTS})

pj_base/include/pj_base/builtin/builtin_object.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include "pj_base/builtin/robot_description.hpp"
4343
#include "pj_base/builtin/scene_entities.hpp"
4444
#include "pj_base/builtin/video_frame.hpp"
45+
#include "pj_base/builtin/voxel_grid.hpp"
4546

4647
namespace PJ {
4748
namespace sdk {
@@ -64,6 +65,7 @@ enum class BuiltinObjectType : uint16_t {
6465
kOccupancyGridUpdate = 15, ///< sdk::OccupancyGridUpdate — incremental sub-rectangle patch for an OccupancyGrid.
6566
kLog = 16, ///< sdk::Log — textual log message (level + text + name).
6667
kPosesInFrame = 17, ///< sdk::PosesInFrame — array of poses in one reference frame.
68+
kVoxelGrid = 18, ///< sdk::VoxelGrid — dense 3D voxel grid (occupancy/cost/ESDF/semantic).
6769
};
6870

6971
/// A-priori classification of a schema. Currently carries only the type;
@@ -110,6 +112,8 @@ struct SchemaClassification {
110112
return "kLog";
111113
case BuiltinObjectType::kPosesInFrame:
112114
return "kPosesInFrame";
115+
case BuiltinObjectType::kVoxelGrid:
116+
return "kVoxelGrid";
113117
}
114118
return "kNone";
115119
}
@@ -168,6 +172,9 @@ struct SchemaClassification {
168172
if (s == "kPosesInFrame") {
169173
return BuiltinObjectType::kPosesInFrame;
170174
}
175+
if (s == "kVoxelGrid") {
176+
return BuiltinObjectType::kVoxelGrid;
177+
}
171178
return std::nullopt;
172179
}
173180

@@ -229,6 +236,9 @@ using BuiltinObject = std::any;
229236
if (t == typeid(PosesInFrame)) {
230237
return BuiltinObjectType::kPosesInFrame;
231238
}
239+
if (t == typeid(VoxelGrid)) {
240+
return BuiltinObjectType::kVoxelGrid;
241+
}
232242
return BuiltinObjectType::kNone;
233243
}
234244

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* @file voxel_grid.hpp
3+
* @brief Dense 3D voxel grid placed in world coordinates.
4+
*/
5+
// Copyright 2026 Davide Faconti
6+
// SPDX-License-Identifier: Apache-2.0
7+
8+
#pragma once
9+
10+
#include <cstdint>
11+
#include <string>
12+
#include <vector>
13+
14+
#include "pj_base/buffer_anchor.hpp"
15+
#include "pj_base/builtin/frame_transforms.hpp" // for Pose, Vector3
16+
#include "pj_base/builtin/point_cloud.hpp" // for PointField (shared channel descriptor)
17+
#include "pj_base/span.hpp"
18+
#include "pj_base/types.hpp"
19+
20+
namespace PJ {
21+
namespace sdk {
22+
23+
/// Dense 3D voxel grid placed in world coordinates — the volumetric sibling of
24+
/// OccupancyGrid, reusing PointCloud's per-channel field model (`PointField`).
25+
///
26+
/// The grid is a regular lattice of `column_count * row_count * slice_count`
27+
/// voxels. Each voxel occupies `cell_stride` bytes laid out per `fields`; cells
28+
/// are stored densely in depth-major, row-major **Z-Y-X** order (x varies
29+
/// fastest), matching the foxglove.VoxelGrid wire layout so a parser can expose
30+
/// `data` as a zero-copy view rather than transcoding millions of cells.
31+
///
32+
/// Strides (in bytes) describe the packing and allow trailing padding:
33+
/// cell_stride : one voxel (>= sum of field element sizes)
34+
/// row_stride : one row of x (>= column_count * cell_stride)
35+
/// slice_stride : one z-slice (>= row_count * row_stride)
36+
/// so the byte offset of voxel (cx, ry, sz) is
37+
/// sz*slice_stride + ry*row_stride + cx*cell_stride
38+
/// and `data.size()` must be at least `slice_count * slice_stride`.
39+
///
40+
/// Voxel (column cx, row ry, slice sz) has its **center** at, in `frame_id`:
41+
/// origin ∘ ((cx + .5)*cell_size.x, (ry + .5)*cell_size.y, (sz + .5)*cell_size.z)
42+
/// where `origin` is the grid's lower-front-left corner (the (0,0,0) corner).
43+
///
44+
/// Unlike the 2D OccupancyGrid (which fixes -1/0..100 occupancy semantics), the
45+
/// per-voxel value is **generic**: `fields` declares the layout (occupancy byte,
46+
/// RGBA, a float cost/intensity, class id, …) and the renderer decides which
47+
/// voxels are drawn (the draw predicate / colormap is viewer-side), so one type
48+
/// serves occupancy maps, costmaps, ESDFs, and semantic grids.
49+
///
50+
/// `anchor` keeps the underlying buffer alive — the producer may have made
51+
/// `data` a view into the source payload (zero-copy) or into a freshly
52+
/// allocated vector; consumers don't need to know which.
53+
struct VoxelGrid {
54+
Timestamp timestamp_ns = 0;
55+
std::string frame_id; ///< Source coordinate frame; required for 3D TF resolution.
56+
Pose origin; ///< Lower-front-left (0,0,0) corner of the grid in `frame_id`.
57+
Vector3 cell_size; ///< Metric voxel size along x, y, z (meters); need not be cubic.
58+
uint32_t column_count = 0; ///< Voxels along x (fastest-varying).
59+
uint32_t row_count = 0; ///< Voxels along y.
60+
uint32_t slice_count = 0; ///< Voxels along z (depth).
61+
uint32_t cell_stride = 0; ///< Bytes per voxel.
62+
uint32_t row_stride = 0; ///< Bytes per row of x.
63+
uint32_t slice_stride = 0; ///< Bytes per z-slice.
64+
std::vector<PointField> fields;
65+
Span<const uint8_t> data;
66+
BufferAnchor anchor;
67+
};
68+
69+
} // namespace sdk
70+
} // namespace PJ
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#pragma once
2+
// Copyright 2026 Davide Faconti
3+
// SPDX-License-Identifier: Apache-2.0
4+
5+
#include <cstddef>
6+
#include <cstdint>
7+
#include <string_view>
8+
#include <vector>
9+
10+
#include "pj_base/builtin/voxel_grid.hpp"
11+
#include "pj_base/expected.hpp"
12+
13+
namespace PJ {
14+
15+
inline constexpr std::string_view kSchemaVoxelGrid = "PJ.VoxelGrid";
16+
17+
/// Serializes sdk::VoxelGrid to canonical PJ.VoxelGrid wire bytes
18+
/// (see pj_base/proto/pj/VoxelGrid.proto).
19+
[[nodiscard]] std::vector<uint8_t> serializeVoxelGrid(const sdk::VoxelGrid& grid);
20+
21+
/// Decodes canonical PJ.VoxelGrid wire bytes. The returned object owns its
22+
/// voxel bytes via `anchor`.
23+
[[nodiscard]] Expected<sdk::VoxelGrid> deserializeVoxelGrid(const uint8_t* data, size_t size);
24+
25+
} // namespace PJ

pj_base/include/pj_base/builtin_object_abi.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ typedef enum PJ_builtin_object_type_t {
5757
PJ_BUILTIN_OBJECT_TYPE_OCCUPANCY_GRID_UPDATE = 15,
5858
PJ_BUILTIN_OBJECT_TYPE_LOG = 16,
5959
PJ_BUILTIN_OBJECT_TYPE_POSES_IN_FRAME = 17,
60+
PJ_BUILTIN_OBJECT_TYPE_VOXEL_GRID = 18,
6061
/* Reserve future types; appended at the tail. Numeric values are stable
6162
* across releases — never renumber. Each new value here must match the
6263
* matching kFoo entry in BuiltinObjectType (builtin_object.hpp). */

0 commit comments

Comments
 (0)