How the roadmaker Python package wraps the kernel: binding design, error
translation, packaging, and the rules for keeping bindings in sync with the
kernel API.
One nanobind module, defined entirely in python/src/bindings.cpp, exposed
as roadmaker (the compiled extension is _roadmaker, re-exported by the
pure-Python package in python/src/roadmaker/). It mirrors the public kernel
API and depends only on core/ — never on the editor, never on Qt
(layer rules).
import roadmaker as rm
network, diagnostics = rm.load_xodr("assets/samples/straight_road.xodr")
mesh = rm.build_network_mesh(network)
rm.export_glb(mesh, "road.glb")-
Pythonic surface. Properties instead of getters (
network.road_count), natural containers (lists, tuples, pairs), keyword arguments with defaults,__repr__everywhere,__bool__/__eq__/__hash__on the ID types.load_xodr/parse_xodrreturn a(network, diagnostics)tuple rather than a result struct. -
Exceptions instead of
Expected. The kernel returnsrm::Expected<T>(error contract); the binding layer unwraps every result and translatesrm::Errorinto Python exceptions via a registered exception translator:Kernel ErrorCodePython exception FileNotFoundFileNotFoundErrorIoFailureOSErroreverything else ( MalformedXml,InvalidDocument,InvalidArgument)ValueErrorThe error's
context(path, element location, id) is appended to the message. No C++ exception ever crosses the boundary unhandled. -
Coordinates and units are the kernel's: right-handed, Z-up, meters, radians — except
export_glb, which writes Y-up like every glTF file (why).
-
Data model:
RoadNetwork,Road,LaneSection,Lane,Junction,JunctionConnection, plus the ID types (RoadId,LaneSectionId,LaneId,JunctionId) and enums (LaneType,RoadMarkType, …). -
Geometry:
ReferenceLine,PathPoint,Poly3,Waypoint,RoadEnd. -
OpenDRIVE I/O:
load_xodr,parse_xodr,write_xodr,save_xodr, andDiagnostic(severity, location, message,rule_id). -
Authoring:
author_clothoid_road(waypoints → G1 clothoid road),LaneProfile,LaneSpec. -
Editing — the
roadmaker.editsubmodule binds the kernel command layer (kernel):edit.Command,edit.EditStack,edit.DirtySet, and every command factory (move_waypoint,insert_waypoint,delete_waypoint,create_road,split_road,delete_road,create_junction,delete_junction,add_lane,remove_lane,set_lane_type,set_lane_width,set_road_mark,set_node_elevation,rename_road). Create a command with a factory, then push it — pushing applies it:stack = rm.edit.EditStack() stack.push(network, rm.edit.set_lane_width(network, lane_id, 4.0)) stack.undo(network)
-
Meshing / export:
MeshOptions(includingchord_tolerance),build_network_mesh,NetworkMesh,export_glb.
Runnable, tested usage lives in python/examples/ — load_and_export.py,
author_road.py, edit_network.py.
Every public kernel API change updates python/src/bindings.cpp and at
least one example in python/examples/ in the same PR. This is part of the
contribution workflow and is checked in
review.
Development install (builds the extension against the in-tree kernel):
pip install -e python/
pytest python/testsPython tests use pytest idioms — see testing.
The wheel is built with scikit-build-core (python/pyproject.toml):
CMake compiles the kernel and the nanobind module, and the wheel embeds
the static kernel — users pip install roadmaker with no shared-library
or Qt dependency, and the package stays pure Apache-2.0
(licensing). Release wheels are produced by
the release workflow alongside the editor packages
(CI).