chore(task): archive 08-03-design-feature-registry

This commit is contained in:
2026-08-03 14:02:57 +08:00
parent ff18217af5
commit ca4385a8f5
6 changed files with 2 additions and 2 deletions

View File

@@ -0,0 +1,103 @@
# Design
## Architecture
Add `blender/osmassets/features.py` as a bpy-layer-safe orchestration helper.
It should not import `bpy` directly unless implementation proves that necessary.
The preferred shape is a small registry / dispatcher contract:
```python
FeatureHandler = namedtuple("FeatureHandler", ("name", "matches", "handle"))
def dispatch_ways(ways, projector, handlers):
...
```
`generate_scene.py` will create local handler callbacks that close over the
current build state: collections, materials, `counts`, `focus_points`,
`grass_rings`, `scrub_trees`, `tree_rows`, and user args. The registry owns
ordering and first-match dispatch; `generate_scene.py` owns the state and exact
side effects.
This is intentionally thinner than moving all feature ownership into modules.
The current feature signatures are uneven for good reasons:
- `water.assemble()` returns only a count.
- `grass.assemble()` returns count, tuft count, and focus points.
- `scrub.assemble()` returns count and focus points, then feeds scrub-tree
sampling owned by `generate_scene.py`.
- `building.assemble()` consumes `office_overrides` and returns building /
industrial counts plus footprint points.
- `roads.py` owns road object construction but `generate_scene.py` owns
`ROAD_LAYERS`, `road_counts`, and warning behavior.
- tree placement is a phase after way and road processing because tree rows,
scrub interior trees, individual point trees, and model fallback combine into
one `trees` list.
## Boundaries
`features.py` may own:
- feature handler data structures;
- way dispatch loop mechanics;
- optional small phase helpers if they preserve phase order;
- registration order for current high-level feature phases.
`generate_scene.py` keeps:
- material creation and its order;
- collection creation and its order;
- `catalog.check_layers()` warning text;
- all counters and scene metadata keys;
- `SCENE_DONE` JSON;
- tree fallback decision and tree material creation;
- loading of grass tuft and scrub bush variants.
Existing feature modules keep object assembly only. They should not gain global
state or start reading CLI args, area config, catalog layer lists, or scene
metadata.
## Data Flow
1. `generate_scene.py` parses OSM, creates `Projector`, collections, materials,
variant assets, counters, and focus containers exactly as now.
2. `generate_scene.py` builds an ordered tuple of `FeatureHandler` instances for
OSM ways and passes it to `features.dispatch_ways(...)`.
3. `features.dispatch_ways(...)` preserves the current outer loop behavior:
skip ways with no coordinate inside bounds, project the ring once, check
handlers in order, run the first match, then continue to the next way.
4. `generate_scene.py` runs road GeoJSON/fallback dispatch after way dispatch,
preserving `catalog.ROAD_LAYERS` ownership.
5. `generate_scene.py` gathers individual tree points, combines them with tree
rows and scrub trees, runs model/procedural tree placement, then processes
fountains.
6. Scene properties, save/render, and `SCENE_DONE` remain unchanged.
## Compatibility
The refactor must be artifact-neutral. The following are load-bearing:
- handler order must match the existing `if` / `elif` chain;
- object creation phase order must remain way features -> roads -> trees ->
fountains -> lights/camera/save;
- material creation order must not change;
- `tree_style_used` fallback semantics must not change;
- `road_features` and all non-road counts must remain identical;
- no new global mutable registry state may leak across Blender runs.
## Trade-Offs
A full ownership inversion where every module owns its materials, counts, and
metadata would make `generate_scene.py` smaller, but it would touch material
order, scene metadata, and several feature-specific side effects at once. That
is too much blast radius for a parity-preserving refactor.
The conservative registry gives the next feature a stable insertion point and
removes the main `elif` dispatch chain without pretending all current feature
modules have the same contract.
## Rollback
Rollback is mechanical: inline the handler registration back into the existing
way loop, delete the `features.py` import and module, and keep feature module
calls unchanged.