Add feature registry dispatch
This commit is contained in:
4
.trellis/tasks/08-03-design-feature-registry/check.jsonl
Normal file
4
.trellis/tasks/08-03-design-feature-registry/check.jsonl
Normal file
@@ -0,0 +1,4 @@
|
||||
{"file": ".trellis/spec/blender/index.md", "reason": "Check generated Blender entrypoint changes against layer and stdout marker constraints."}
|
||||
{"file": ".trellis/spec/blender/module-structure.md", "reason": "Verify registry placement and feature module boundaries remain consistent."}
|
||||
{"file": ".trellis/spec/guides/artifact-parity-guide.md", "reason": "Verify required before/after parity and no unreviewed artifact contract drift."}
|
||||
{"file": ".trellis/spec/guides/code-reuse-thinking-guide.md", "reason": "Check no duplicate ROAD_LAYERS/material/config facts were introduced."}
|
||||
103
.trellis/tasks/08-03-design-feature-registry/design.md
Normal file
103
.trellis/tasks/08-03-design-feature-registry/design.md
Normal 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.
|
||||
@@ -0,0 +1,5 @@
|
||||
{"file": ".trellis/spec/blender/index.md", "reason": "Blender layer overview, bpy/pure-Python boundary, parity expectations, and entrypoint contracts."}
|
||||
{"file": ".trellis/spec/blender/module-structure.md", "reason": "Feature module responsibilities and the stated goal of adding features through a registry line."}
|
||||
{"file": ".trellis/spec/guides/artifact-parity-guide.md", "reason": "Pure refactor validation contract and P2 registry status."}
|
||||
{"file": ".trellis/spec/guides/code-reuse-thinking-guide.md", "reason": "Registry and single-source constraints; avoid duplicate road/material/catalog facts."}
|
||||
{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "Cross-stage stdout, material, and Blender/Cesium artifact contract risks."}
|
||||
57
.trellis/tasks/08-03-design-feature-registry/implement.md
Normal file
57
.trellis/tasks/08-03-design-feature-registry/implement.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Implementation Plan
|
||||
|
||||
## Checklist
|
||||
|
||||
1. Capture a parity baseline before product-code edits:
|
||||
`node scripts/parity.js capture feature-registry-before --stages blender,cesium`
|
||||
2. Add `blender/osmassets/features.py` with a small `FeatureHandler` contract
|
||||
and `dispatch_ways(...)`.
|
||||
3. Import `features.py` in `blender/generate_scene.py`.
|
||||
4. Convert the current OSM way `if` / `elif` chain into ordered local handlers:
|
||||
water, grass, scrub, tree-row, building.
|
||||
5. Keep handler bodies mechanically equivalent to the current branch bodies.
|
||||
6. Leave road GeoJSON/fallback, tree placement, fountain point processing,
|
||||
scene metadata, and `SCENE_DONE` behavior unchanged except for any small
|
||||
phase comments needed to clarify ordering.
|
||||
7. Run Python checks:
|
||||
- `python3 -m py_compile blender/osmassets/features.py`
|
||||
- `python3 -m py_compile blender/generate_scene.py`
|
||||
- `python3 -m unittest blender/tests/test_pure.py`
|
||||
8. Capture parity after the refactor:
|
||||
`node scripts/parity.js capture feature-registry-after --stages blender,cesium`
|
||||
9. Compare parity:
|
||||
`node scripts/parity.js compare feature-registry-before feature-registry-after`
|
||||
10. Update `.trellis/spec` and `docs/changelog.md` only for durable registry
|
||||
conventions or P2 status changes discovered during implementation.
|
||||
11. Commit, archive the task, and record the journal entry.
|
||||
|
||||
## Validation Commands
|
||||
|
||||
```bash
|
||||
node scripts/parity.js capture feature-registry-before --stages blender,cesium
|
||||
python3 -m py_compile blender/osmassets/features.py
|
||||
python3 -m py_compile blender/generate_scene.py
|
||||
python3 -m unittest blender/tests/test_pure.py
|
||||
node scripts/parity.js capture feature-registry-after --stages blender,cesium
|
||||
node scripts/parity.js compare feature-registry-before feature-registry-after
|
||||
```
|
||||
|
||||
## Risk Points
|
||||
|
||||
- Handler order is equivalent to the old `elif` chain. Changing it can alter
|
||||
which feature claims a way.
|
||||
- Object creation phase order affects `.blend` and GLB digests.
|
||||
- Material creation order affects GLB material indices.
|
||||
- Tree rows and scrub interior trees feed the later tree placement phase; do
|
||||
not place trees inside the way dispatch loop.
|
||||
- `catalog.check_layers()` must stay warning-only and keep the exact printed
|
||||
prefix.
|
||||
- Do not add a new road layer id list; keep deriving road layers from
|
||||
`catalog.ROAD_LAYERS`.
|
||||
|
||||
## Rollback Point
|
||||
|
||||
If parity shows a non-ignored difference, first compare object order, object
|
||||
names, mesh/material names, `SCENE_DONE`, and scene custom properties. If the
|
||||
cause is not obvious, revert the registry dispatch and return to the current
|
||||
way loop before trying a narrower refactor.
|
||||
94
.trellis/tasks/08-03-design-feature-registry/prd.md
Normal file
94
.trellis/tasks/08-03-design-feature-registry/prd.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# Design feature registry
|
||||
|
||||
## Goal
|
||||
|
||||
Introduce a conservative Blender feature registry so `generate_scene.py` no
|
||||
longer hard-codes every OSM feature dispatch branch, while keeping generated
|
||||
`.blend` / `.glb` / scene metadata and stdout contracts unchanged.
|
||||
|
||||
The user value is maintainability: adding or moving a Blender feature should
|
||||
become a registration-level edit plus a feature module implementation, not a
|
||||
new branch woven through the main scene builder.
|
||||
|
||||
## Background
|
||||
|
||||
- P2 module extraction is now complete for current feature assembly modules:
|
||||
`water.py`, `grass.py`, `scrub.py`, `tree.py`, `fountain.py`,
|
||||
`building.py`, and `roads.py` exist under `blender/osmassets/`.
|
||||
- `.trellis/spec/guides/artifact-parity-guide.md` records the remaining P2
|
||||
gap as the missing `features/` registry.
|
||||
- `blender/generate_scene.py` still owns the orchestration sequence:
|
||||
material creation, OSM way dispatch, road GeoJSON/fallback dispatch, tree
|
||||
collection/fallback, fountain point dispatch, camera focus points, scene
|
||||
metadata, save/render, and `SCENE_DONE`.
|
||||
- Artifact parity is mandatory because this is intended as a pure refactor.
|
||||
|
||||
## Requirements
|
||||
|
||||
1. Add a new Blender-side registry module under `blender/osmassets/`, expected
|
||||
path `blender/osmassets/features.py`.
|
||||
2. Replace the inline OSM way `if` / `elif` feature dispatch in
|
||||
`generate_scene.py` with registry-driven dispatch while preserving exact
|
||||
behavior and order:
|
||||
- water before grass;
|
||||
- grass before scrub;
|
||||
- scrub before tree-row collection;
|
||||
- tree-row collection before building;
|
||||
- first matching handler wins, as the current `elif` chain does.
|
||||
3. Make the current feature phases explicit in code:
|
||||
- OSM way feature phase;
|
||||
- road GeoJSON / OSM fallback phase;
|
||||
- tree placement phase;
|
||||
- point prop phase for fountains.
|
||||
4. Keep `generate_scene.py` responsible for high-level scene contracts:
|
||||
- CLI parsing and tree-style validation;
|
||||
- collection creation and collection order;
|
||||
- material creation order;
|
||||
- `counts`, `road_counts`, tree counts, and `focus_points` ownership;
|
||||
- `catalog.check_layers()` and exact `Layer catalog warning:` print text;
|
||||
- scene custom properties;
|
||||
- `SCENE_DONE` marker and JSON shape.
|
||||
5. Do not change current feature module assembly signatures unless the change
|
||||
is a mechanical adapter around the same behavior.
|
||||
6. Do not reorder `catalog.MATERIALS`, `catalog.ROAD_LAYERS`, collection
|
||||
creation, object creation phases, or `SCENE_DONE` fields.
|
||||
7. Do not introduce a second list of osm2streets road layer ids. Road layer
|
||||
iteration must still derive from `catalog.ROAD_LAYERS`.
|
||||
8. Do not fix unrelated known defects D1-D3 in this task.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- [ ] `blender/osmassets/features.py` exists and contains the registry /
|
||||
dispatcher contract for Blender feature phases.
|
||||
- [ ] `generate_scene.py` uses the registry for OSM way feature dispatch; the
|
||||
previous inline `if` / `elif` way feature chain is removed or reduced to
|
||||
small handler callbacks.
|
||||
- [ ] Current generated object names, mesh names, material creation order,
|
||||
collection order, scene properties, and `SCENE_DONE` JSON remain
|
||||
unchanged.
|
||||
- [ ] `road_counts`, `counts`, tree counts, and `focus_points` continue to be
|
||||
owned by `generate_scene.py` or by explicit objects passed from it, not
|
||||
hidden in global module state.
|
||||
- [ ] `catalog.check_layers()` warning behavior remains in `generate_scene.py`
|
||||
with the exact `Layer catalog warning:` text.
|
||||
- [ ] `python3 -m py_compile blender/osmassets/features.py` passes.
|
||||
- [ ] `python3 -m py_compile blender/generate_scene.py` passes.
|
||||
- [ ] `python3 -m unittest blender/tests/test_pure.py` passes.
|
||||
- [ ] Blender/Cesium parity before/after is run for
|
||||
`nantaizi-lake-innovation-valley` and `hanyang-block`; expected compare
|
||||
result is `identical` for both.
|
||||
|
||||
## Out Of Scope
|
||||
|
||||
- Changing feature behavior, geometry, z values, material definitions, material
|
||||
order, or catalog declarations.
|
||||
- Moving material creation into feature modules.
|
||||
- Moving scene metadata / `SCENE_DONE` construction out of `generate_scene.py`.
|
||||
- Reworking tree model loading, procedural tree fallback, grass tuft loading,
|
||||
scrub bush loading, or camera focus logic beyond adapter-level plumbing.
|
||||
- Adding new rendered feature types.
|
||||
- Changing parity ignore lists or stdout marker parsing.
|
||||
|
||||
## Open Questions
|
||||
|
||||
None.
|
||||
26
.trellis/tasks/08-03-design-feature-registry/task.json
Normal file
26
.trellis/tasks/08-03-design-feature-registry/task.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"id": "design-feature-registry",
|
||||
"name": "design-feature-registry",
|
||||
"title": "Design feature registry",
|
||||
"description": "",
|
||||
"status": "in_progress",
|
||||
"dev_type": null,
|
||||
"scope": null,
|
||||
"package": null,
|
||||
"priority": "P2",
|
||||
"creator": "dingkang",
|
||||
"assignee": "dingkang",
|
||||
"createdAt": "2026-08-03",
|
||||
"completedAt": null,
|
||||
"branch": null,
|
||||
"base_branch": "main",
|
||||
"worktree_path": null,
|
||||
"commit": null,
|
||||
"pr_url": null,
|
||||
"subtasks": [],
|
||||
"children": [],
|
||||
"parent": null,
|
||||
"relatedFiles": [],
|
||||
"notes": "",
|
||||
"meta": {}
|
||||
}
|
||||
Reference in New Issue
Block a user