Commit eba024f
committed
Refactor Surface lifecycle: introduce finalize_texture() gate
Adds an explicit `_finalized` lifecycle to `Surface`, gated by a new
`finalize_texture()` method. The flow becomes:
Surface(...) # _finalized = False, texture fields possibly None
surface.update_texture(...) # multi-call, fills only None fields (eg asset
# material layered after user surface). Raises if
# _finalized.
surface.finalize_texture() # fills remaining None fields with class
# `default_*` (color, opacity, ior, roughness).
# Sets _finalized = True. Raises if called twice.
Renames `surface.emission` → `surface.emissive_tex` and adds matching
`*_tex` accessors (opacity_tex / roughness_tex / metallic_tex). Adds
`get_arm()` returning a Texture/BatchTexture combining AO/roughness/metallic.
The internal `_make_rgba` / `_make_arm` are refactored into a shared
`_combine_textures` helper that records per-channel provenance on the
output texture's `explicit_channels` dict (used by future exporters to
decide whether a child material should clear a parent's embedded texture
slot when overriding with a flat color).
textures.py: adds the `explicit_channels: dict[str, bool]` field on
`Texture` and a `has_image` cached_property on each subclass:
ColorTexture is always False, ImageTexture reflects `image_array is not
None`, BatchTexture aggregates with `any()`.
METAL_COLOR is added as a lookup table of measured reflectance per
`MetalType` for downstream use in `Metal` surface defaults.
raytracer.py: the only upstream caller of `.emission` is updated to
`.emissive_tex`. No other entity-level changes — the engine/mesh.py and
entity-side `finalize_texture()` call sites are deferred to the follow-up
`pr/mesh-init-surface-lifecycle` PR.
tests: adds three lifecycle tests (`test_surface_finalize_idempotency`,
`test_update_texture_raises_after_finalize`, `test_finalize_fills_defaults`)
covering the new gate.1 parent acf941b commit eba024f
4 files changed
Lines changed: 531 additions & 180 deletions
0 commit comments