104 lines
4.2 KiB
Markdown
104 lines
4.2 KiB
Markdown
# 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.
|