Handoff — XML Open3D visualizer: show all schema 1.1 feature classes

2026-08-29 · for the next agent session · generated by Claude (Fable 5)

Summary

Target: extend the Open3D XML viewer ~/dev/3dai.lanefinder/scripts/visualize_xml_open3d.py (repo 3dai.lanefinder, master @ 8a99587, v0.9.1, clean apart from an untracked .wrangler/) so a HighwayData schema 1.1 document is shown with every feature class visibly distinguishable — point symbols (trees, signs, shafts, gullies, KM signs, …), tree/trunk/crown shapes, direction arrows, closed outlines, annotation text — instead of the current look where only the lane lines and road axes stand out.

Nothing implemented yet. Schema 1.1 is finished and merged on the producer side (~/dev/3dai.iolabs.pointcloud.modellingexport, PR #4, ticket AI3D-376): spec, one-feature-per-class fixture and a realistic example XML exist there and are the inputs.

At handoff: uv run --extra dev pytest test/test_visualize_xml_open3d.py -q3 passed; the script parses the 1.1 example headlessly (--no-viewer) in ~3 s without errors — it just draws the new content invisibly/uniformly.

Not the target: the 2D raster-overlay package iolabs-point-cloud-visualization-overlays (Step 7b) has the same limitation (allow-list of 3 lane types, no Points parsing) — a separate task if wanted.

Current state of the viewer (922 lines, single script)

PieceToday
parse_and_accumulate() Walks Features/Feature; per feature computes lane_color = _lane_id_to_color01(LaneID) (md5 hash of the LaneID string) and calls _handle_points / _handle_lines / _handle_polylines / _handle_splines / _handle_labels. All four containers are already parsedFeature/Type is never read.
_handle_points Each Point → one vertex in a single PointCloud, colour from Point/Color if parsable, else lane colour. 1-px dots → point-symbol classes are invisible at road scale. Direction, Shapes, Annotation, Attributes ignored.
_handle_polylines Consecutive-vertex segments into a LineSet, colour gain 0.95. Closed, Polyline/Type, SortingCode, Annotation ignored → tree-group rings stay open.
_handle_splines Bezier sampling when fit/ctrl counts match, else fit polyline, else control polygon (this last branch is what 1.1 Tree Line/Bush hit: empty Fit_Points, ≥2 Control_Points). Works.
_handle_labels Text meshes (o3d.t.geometry.TriangleMesh.create_text) for the feature ID (anchored at first child) and every child ID. Label offset/scale/z-lift/depth CLI-tunable. No annotation text.
GeometryAccumulator Flat lists per primitive kind (points / lines / polylines / splines / fit / ctrl / labels); build_open3d() honours --hide-* flags; build_open3d_by_type() + save_geometries_as_ply() export one PLY per kind.
Viewer Legacy o3d.visualization.Visualizer, white background, lighting off, back-faces on (so text meshes read from any angle). WSLg needs X11: the script re-execs itself with WAYLAND_DISPLAY unset (_reexec_with_x11_for_open3d_if_needed); see docs/Open3D_WSLg_Wayland_GLFW_workaround.md.
Extras --show-surface-obbs/--show-surface-meshes overlay Step 4 road-surface PLYs (build_surface_overlay_geometries). Keep intact.
Tests test/test_visualize_xml_open3d.py: 3 tests (label positions from a legacy-style XML with id attributes and <LaneExport> root; surface overlay meshes/OBBs/labels). Imports via from scripts import visualize_xml_open3d as viz.

Recent history on the file: AI3D-331 (smaller labels, anchor at first segment), AI3D-174 (labels + surface overlays), AI3D-242 (WSLg workaround).

What schema 1.1 adds and why it is invisible now

Spec: ~/dev/3dai.iolabs.pointcloud.modellingexport/docs/xml_schema_1_1_spec.md — §3 element tree, §4 element reference, §7.1 summary table (32 Feature/Type values; the authoritative type→container list is PROFILES in src/iolabs_point_cloud_modelling_export/feature_classes.py).

Recommended design

Keep the script as the CLI entry (tests import it) but move new parsing/builders into a module — AGENTS.md wants modules < 500 lines and the script is already 922. The empty dirs src/visualization/ and src/xml_tools/ exist; check whether they are packages before choosing, e.g. src/visualization/xml_feature_symbols.py.

1. Read Feature/Type; colour by type

2. Point symbols as bodies, not dots

3. Lines/polylines

4. Labels

5. Export + agent-verifiability

Next steps (ordered)

  1. Branch in LaneFinder: git checkout -b ai3d-376-xml-viz-1-1-classes (reuse AI3D-376 unless Miro gives a new ticket; commit style AI3D-376 <summary>).
  2. Copy ~/dev/3dai.iolabs.pointcloud.modellingexport/tests/fixtures/sample_v1_1.xml to test/fixtures/ (one feature per class, coordinates within ±60 m, Geoshift 2600000/1200000/400) as the test input.
  3. TDD, one slice each (follow the existing test style, headless — never open a window in tests): (a) type palette + --color-by; (b) point-symbol spheres + direction arrows (assert geometry counts/types and centroid positions); (c) shapes cylinder/cone placement (assert axis endpoints after transform, ±1e-6); (d) closed polyline segment count; (e) annotation + elevation labels in acc.label_specs; (f) type filters; (g) PLY/screenshot export.
  4. Refactor into a module if the script crosses the 500-line guideline further; keep parse_and_accumulate, build_surface_overlay_geometries, _surface_label_text importable from scripts.visualize_xml_open3d (tests depend on them).
  5. Visual check on the realistic example XML (below), iterate on radii/arrow length/label scale from screenshots.
  6. Update README section "Visualization helpers" (lines ~309–326) with the new flags; flake8 clean.
  7. Review (Opus + Sol in parallel, Fable final pass per Miro's model policy), Bitbucket PR via bitbucket-pr.

Verification

cd ~/dev/3dai.lanefinder
uv run --extra dev pytest test/test_visualize_xml_open3d.py -q   # 3 passed at handoff (~2 s)
# NB: plain `uv run pytest` fails — pytest lives in the dev extra despite AGENTS.md's shorter command

XML=~/dev/3dai.iolabs.pointcloud.modellingexport/docs/examples/run7_branch_001_schema_1_1.xml
mkdir -p /tmp/viz11
uv run python scripts/visualize_xml_open3d.py --xml "$XML" --no-viewer --save-ply-prefix /tmp/viz11/ex   # ~3 s at handoff
env -u WAYLAND_DISPLAY XDG_SESSION_TYPE=x11 uv run python scripts/visualize_xml_open3d.py --xml "$XML"    # interactive (WSLg)

The example XML = real Abschnitt 1 / branch 001 lane geometry (first ~290 m, frozen classes) + synthetic instances of every non-frozen class placed beside that road; Geoshift 694541.44 / 5386372.37 / 467.41; 39 features, 1 112 Point elements (mostly cross-section samples + spline points), 13 polylines, 11 splines, 60 lines.

Not run at handoff: any interactive window; anything rendering 1.1 content (nothing does yet).

Risks and open questions

Key artifacts

Suggested skills / delegation