# Extract building module ## Goal Move building assembly out of `blender/generate_scene.py` into a dedicated bpy-layer module, keeping generated `.blend` / `.glb` / metadata structure unchanged. This continues the documented P2 feature-module direction after `fountain.py`. ## Background - `.trellis/spec/guides/artifact-parity-guide.md` records P2 as partial: `water.py`, `grass.py`, `scrub.py`, `tree.py`, and `fountain.py` are already split out, while `building` and `roads` still live in `generate_scene.py`. - `blender/generate_scene.py:72` defines `_assemble_building()`. - `blender/generate_scene.py:179` defines `add_building_details()`, which creates office / industrial window bands with `MeshBatch` and `add_wall_panel()`. - `blender/generate_scene.py:789` dispatches OSM ways with a `building` tag, increments `counts["building_count"]` and `counts["industrial_count"]`, and extends `focus_points` with the returned ring. - `blender/generate_scene.py:710` creates `building_mats` from the existing `catalog.MATERIALS["building_" + key]` entries. - `blender/generate_scene.py:910` persists `office_override_way_ids` as a scene custom property from `args["office_overrides"]`. ## Requirements 1. Add a new bpy-layer module at `blender/osmassets/building.py`. 2. Move the current building assembly behavior into the new module: - `_assemble_building()` - `add_building_details()` 3. Expose an `assemble(...)` function from the new module that returns the same tuple shape currently returned by `_assemble_building()`: `(added, industrial_added, ring_pts)`. 4. Keep `generate_scene.py` responsible for: - parsing `--office-overrides`; - creating `building_mats`; - filtering OSM ways with a `building` tag and `len(ring) >= 3`; - updating `building_count`, `industrial_count`, and `focus_points`; - writing `scene["office_override_way_ids"]`; - writing the existing `SCENE_DONE` JSON shape. 5. Preserve existing building object names and mesh names: - `Building_` - `Building__Roof` - `Building__Windows` 6. Preserve existing building custom properties: - `osm_height` - `render_height` - `building_kind` - `osm_building_tag` - `office_override` 7. Preserve current height rules: - industrial buildings use the parsed source height; - non-industrial buildings under 30m render at 11.4m; - source height has a 3.0m floor and `parse_height(tag, 12.0)` default. 8. Preserve current facade behavior: - industrial buildings use one high horizontal band; - office buildings use floor-based window bands; - bevel modifier name, width, and segment count stay unchanged. 9. Do not introduce a full `features/` registry in this task. 10. Do not modify roads, material definitions, `MATERIALS` order, `ROAD_LAYERS` order, stdout markers, parity ignore lists, or known unrelated defects D1-D3. ## Acceptance Criteria - [x] `blender/osmassets/building.py` contains building assembly behavior and imports only bpy-layer-safe dependencies. - [x] `blender/generate_scene.py` imports and calls the new building module; no inline `_assemble_building()` or `add_building_details()` remains there. - [x] Building counts, industrial counts, focus point behavior, scene custom properties, and `SCENE_DONE` JSON keys remain unchanged. - [x] `python3 -m py_compile blender/osmassets/building.py` passes. - [x] `python3 -m py_compile blender/generate_scene.py` passes. - [x] `python3 -m unittest blender/tests/test_pure.py` passes. - [x] 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 feature registry implementation. - Road extraction. - Building visual redesign. - Building material or Cesium export contract changes. - Moving CLI parsing or `office_override_way_ids` scene metadata out of `generate_scene.py`. - Changing geometry helper APIs in `osmassets.mesh`. ## Open Questions None.