Files
osmWorkflow/.trellis/tasks/archive/2026-08/08-03-extract-road-module/prd.md

4.3 KiB

Extract road module

Goal

Move Blender road assembly out of blender/generate_scene.py into a dedicated bpy-layer module, keeping generated .blend / .glb / metadata structure unchanged. This completes the P2 feature-module extraction except for the deferred full features/ registry.

Background

  • .trellis/spec/guides/artifact-parity-guide.md records P2 as partial: water.py, grass.py, scrub.py, tree.py, fountain.py, and building.py are already split out, while roads still live in generate_scene.py.
  • blender/generate_scene.py:148 defines add_geojson_layer(), which reads one osm2streets GeoJSON road layer, filters by bounds, projects coordinates, clips polygons, batches them as Road_<layer>, and returns a feature count.
  • blender/generate_scene.py:656 creates road_mats by zipping catalog.ROAD_LAYERS with catalog.road_material_specs().
  • blender/generate_scene.py:741 runs catalog.check_layers(geojson_dir) and prints Layer catalog warning: messages before iterating ROAD_LAYERS.
  • blender/generate_scene.py:750 falls back to simple OSM highway polylines when road_counts["road_surface"] is zero.
  • blender/generate_scene.py:866 stores scene["road_feature_counts"]; the same road_counts object is emitted in SCENE_DONE as road_features.

Requirements

  1. Add a new bpy-layer module at blender/osmassets/roads.py.
  2. Move the current roads assembly behavior into the new module:
    • GeoJSON layer mesh assembly currently in add_geojson_layer();
    • simple OSM highway fallback currently in the road_surface == 0 block.
  3. Keep generate_scene.py responsible for:
    • creating road_mats from catalog.ROAD_LAYERS and catalog.road_material_specs();
    • running catalog.check_layers() and printing existing warning text;
    • owning road_counts;
    • writing scene["road_feature_counts"];
    • writing the existing SCENE_DONE JSON shape.
  4. Preserve existing road object names and mesh names:
    • Road_<layer_id>
    • OSM_Road_<way_id>
  5. Preserve existing GeoJSON behavior:
    • missing layer file returns count 0;
    • feature is skipped when feature_in_bounds() is false;
    • rings come from geometry_rings();
    • projected rings are clipped with clip_polygon();
    • only rings with at least three points are batched and counted.
  6. Preserve existing OSM fallback behavior:
    • fallback triggers only when road_counts.get("road_surface", 0) == 0;
    • only ways with a highway tag and at least two coordinates are drawn;
    • widths remain secondary: 7.0, residential: 5.5, service: 3.5, default 4.0;
    • fallback material remains road_mats["road_surface"];
    • fallback z remains 0.03;
    • fallback does not add to road_counts.
  7. Do not introduce a full features/ registry in this task.
  8. Do not modify ROAD_LAYERS, SCENE_LAYERS, material definitions, MATERIALS order, GeoJSON schema, z-index/z-height values, stdout markers, parity ignore lists, or known unrelated defects D1-D3.

Acceptance Criteria

  • blender/osmassets/roads.py contains road assembly behavior and imports only bpy-layer-safe dependencies.
  • blender/generate_scene.py imports and calls the new roads module; no inline add_geojson_layer() remains there.
  • road_counts, scene["road_feature_counts"], and SCENE_DONE road_features remain unchanged.
  • Existing Layer catalog warning: print behavior remains in generate_scene.py.
  • python3 -m py_compile blender/osmassets/roads.py passes.
  • python3 -m py_compile blender/generate_scene.py passes.
  • python3 -m unittest blender/tests/test_pure.py passes.
  • A before/after parity comparison is run for the Blender/Cesium stages and any non-ignored diff is either absent or explicitly explained as expected. For this refactor, the expected result is no contract diff.

Out Of Scope

  • Full features/ registry implementation.
  • Any change to osm2streets road splitting, SCENE_LAYERS, or QGIS pipeline behavior.
  • Any change to catalog.ROAD_LAYERS, material names, color values, or order.
  • Any change to fallback highway widths, z values, object names, or counts.
  • Turning catalog.check_layers() warnings into errors.
  • Moving road_mats creation or road_feature_counts scene metadata out of generate_scene.py.

Open Questions

None.