4.2 KiB
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:
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 bygenerate_scene.py.building.assemble()consumesoffice_overridesand returns building / industrial counts plus footprint points.roads.pyowns road object construction butgenerate_scene.pyownsROAD_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
treeslist.
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_DONEJSON;- 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
generate_scene.pyparses OSM, createsProjector, collections, materials, variant assets, counters, and focus containers exactly as now.generate_scene.pybuilds an ordered tuple ofFeatureHandlerinstances for OSM ways and passes it tofeatures.dispatch_ways(...).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.generate_scene.pyruns road GeoJSON/fallback dispatch after way dispatch, preservingcatalog.ROAD_LAYERSownership.generate_scene.pygathers individual tree points, combines them with tree rows and scrub trees, runs model/procedural tree placement, then processes fountains.- Scene properties, save/render, and
SCENE_DONEremain unchanged.
Compatibility
The refactor must be artifact-neutral. The following are load-bearing:
- handler order must match the existing
if/elifchain; - object creation phase order must remain way features -> roads -> trees -> fountains -> lights/camera/save;
- material creation order must not change;
tree_style_usedfallback semantics must not change;road_featuresand 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.