Source code for optiland.nonsequential.ir.bsdf_ir

"""BsdfIR -- data-only description of a surface scatter model.

Kramer Harrison, 2026
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, Literal

# Every BaseBSDF subclass this revamp lowers, plus "none" for a bare
# specular/refractive/absorbing surface with no attached scatter lobe.
# BaseBSDF is restructured around explicit REFLECT/TRANSMIT lobes:
# sample() returns which side of the surface each scattered ray landed on,
# so RefractiveComponent can resolve n_current/k_current from the lobe's own
# choice instead of the independent Fresnel branch draw. The kind list
# itself is unchanged by that -- only params gained transmissive_fraction.
BsdfKind = Literal["none", "specular", "lambertian", "harvey_shack", "tabulated"]


[docs] @dataclass(frozen=True) class BsdfIR: """A surface scatter model, as plain data. ``params`` is kind-specific and mirrors the corresponding ``BaseBSDF`` subclass's constructor arguments exactly, so lowering is a direct field copy with no interpretation: - ``"none"``: ``{}`` - ``"specular"``: ``{}`` (always reflective; no transmissive lobe) - ``"lambertian"``: ``{"reflectance_value": <float>, "transmissive_fraction": <float>}`` - ``"harvey_shack"``: ``{"b0": <float>, "l0": <float>, "s": <float>, "transmissive_fraction": <float>}`` - ``"tabulated"``: ``{"path": <str>, "transmissive_fraction": <float>}`` ``transmissive_fraction`` is the probability that a given scatter event samples the transmissive (far-side) hemisphere instead of the reflective one; it defaults to 0.0 on every kind that has it, so an un-set BSDF scatters exactly as it did before D-5. Attributes: kind: Which scatter model this is. params: Kind-specific parameters (see above). """ kind: BsdfKind params: dict[str, Any] = field(default_factory=dict)