Source code for eradiate.scenes.surface._basic

from __future__ import annotations

import warnings

import attrs
import mitsuba as mi

from ._core import Surface
from ..bsdfs import BSDF, LambertianBSDF, bsdf_factory
from ..core import Ref, SceneTraversal, traverse
from ..shapes import RectangleShape, SphereShape, shape_factory
from ...attrs import documented, parse_docs
from ...exceptions import OverriddenValueWarning, TraversalError
from ...kernel import TypeIdLookupStrategy, UpdateParameter


[docs] @parse_docs @attrs.define(eq=False, slots=False) class BasicSurface(Surface): """ Basic surface [``basic``]. A basic surface description consisting of a single shape and BSDF. """ shape: None | RectangleShape | SphereShape = documented( attrs.field( default=None, converter=attrs.converters.optional(shape_factory.convert), validator=attrs.validators.optional( attrs.validators.instance_of((RectangleShape, SphereShape)) ), ), doc="Shape describing the surface. This parameter may be left unset " "for situations in which the task of setting its value is delegated to " "another component (*e.g.* an :class:`.Experiment` instance owning the " "surface object); however, if it is still unset upon kernel dictionary " "generation, the call to :meth:`.traverse` will raise a " ":class:`.TraversalError`.", type=".RectangleShape or .SphereShape or None", init_type=".RectangleShape or .SphereShape or dict, optional", default=":class:`.RectangleShape <RectangleShape()>`", ) @shape.validator def _shape_validator(self, attribute, value): if value is not None and value.bsdf is not None: warnings.warn( f"while validating '{attribute.name}': " f"'{attribute.name}.bsdf' should be set to None; it will " "be overridden during kernel dictionary generation", OverriddenValueWarning, ) bsdf: BSDF = documented( attrs.field( factory=LambertianBSDF, converter=bsdf_factory.convert, validator=attrs.validators.instance_of(BSDF), ), doc="The reflection model attached to the surface.", type=".BSDF", init_type=".BSDF or dict, optional", default=":class:`LambertianBSDF() <.LambertianBSDF>`", )
[docs] def update(self) -> None: # Inherit docstring # Fix BSDF ID self.bsdf.id = self._bsdf_id # Force BSDF referencing if the shape is defined if self.shape is not None: if isinstance(self.shape.bsdf, BSDF): warnings.warn("Set BSDF will be overridden by surface BSDF settings.") self.shape.bsdf = Ref(id=self._bsdf_id)
@property def _shape_id(self): """ Mitsuba shape object identifier. """ return f"{self.id}_shape" @property def _bsdf_id(self): """ Mitsuba BSDF object identifier. """ return f"{self.id}_bsdf" @property def _template_bsdfs(self) -> dict: kdict_template = traverse(self.bsdf)[0].data result = {} for key, param in kdict_template.items(): result[f"{self._bsdf_id}.{key}"] = param return result @property def _template_shapes(self) -> dict: kdict_template = traverse(self.shape)[0].data result = {} for key, param in kdict_template.items(): result[f"{self._shape_id}.{key}"] = param return result @property def _params_bsdfs(self) -> dict: umap_template = traverse(self.bsdf)[1].data result = {} for key, param in umap_template.items(): # If no lookup strategy is set, we must add one if isinstance(param, UpdateParameter) and param.lookup_strategy is None: param = attrs.evolve( param, lookup_strategy=TypeIdLookupStrategy( mi.BSDF, self._bsdf_id, parameter_relpath=key, ), ) result[f"{self._bsdf_id}.{key}"] = param return result @property def _params_shapes(self) -> dict: return {}
[docs] def traverse(self, callback: SceneTraversal) -> None: # Inherit docstring if self.shape is None: raise TraversalError( "A 'BasicSurface' cannot be traversed if its 'shape' field is unset." ) super().traverse(callback)