Source code for pypbr.blending.blending

"""
pypbr.blending.blending

This module provides class-based interfaces for blending PBR materials using various methodologies.

Classes:
    BlendMethod: Abstract base class for different blending methods.
    MaskBlend: Blend two materials using a provided mask.
    HeightBlend: Blend two materials based on their height maps.
    PropertyBlend: Blend two materials based on a specified property map.
    GradientBlend: Blend two materials using a linear gradient mask.
    BlendFactory: Factory class to instantiate blending methods.
"""

from abc import ABC, abstractmethod

import torch

from ..material import MaterialBase
from .functional import (
    blend_on_height,
    blend_on_properties,
    blend_with_gradient,
    blend_with_mask,
)


[docs] class BlendMethod(ABC): """ Abstract base class for different blending methods. """ @abstractmethod def __call__( self, material1: MaterialBase, material2: MaterialBase ) -> MaterialBase: """ Apply the blending method to two materials. Args: material1 (MaterialBase): The first material. material2 (MaterialBase): The second material. Returns: MaterialBase: A new material resulting from blending material1 and material2. """ pass
[docs] class MaskBlend(BlendMethod): """ Blend two materials using a provided mask. Args: mask (torch.FloatTensor): The blending mask tensor, with values in [0, 1], shape [1, H, W] or [H, W]. Raises: ValueError: If the mask has an invalid shape. """
[docs] def __init__(self, mask: torch.FloatTensor): if mask.dim() == 2: self.mask: torch.FloatTensor = mask.unsqueeze(0) elif mask.dim() == 3 and mask.size(0) == 1: self.mask = mask else: raise ValueError("Mask must have shape [1, H, W] or [H, W].")
def __call__( self, material1: MaterialBase, material2: MaterialBase ) -> MaterialBase: """ Apply mask-based blending to two materials. Args: material1 (MaterialBase): The first material. material2 (MaterialBase): The second material. Returns: MaterialBase: A new material resulting from blending material1 and material2. """ return blend_with_mask(material1, material2, self.mask)
[docs] class HeightBlend(BlendMethod): """ Blend two materials based on their height maps. Args: blend_width (float): Controls the sharpness of the blending transition. shift (float): Shifts the blending transition vertically. Positive values raise the blending height, while negative values lower it. Defaults to 0.0. """
[docs] def __init__( self, blend_width: float = 0.1, shift: float = 0.0, ): self.blend_width: float = blend_width self.shift: float = shift
def __call__( self, material1: MaterialBase, material2: MaterialBase ) -> MaterialBase: """ Apply height-based blending to two materials. Args: material1 (MaterialBase): The first material. material2 (MaterialBase): The second material. Returns: MaterialBase: A new material resulting from blending material1 and material2. """ return blend_on_height(material1, material2, self.blend_width, self.shift)
[docs] class PropertyBlend(BlendMethod): """ Blend two materials based on a specified property map. Args: property_name (str): The name of the property map to use for blending (e.g., 'metallic', 'roughness'). blend_width (float): Controls the sharpness of the blending transition. """
[docs] def __init__(self, property_name: str = "metallic", blend_width: float = 0.1): self.property_name: str = property_name self.blend_width: float = blend_width
def __call__( self, material1: MaterialBase, material2: MaterialBase ) -> MaterialBase: """ Apply property-based blending to two materials. Args: material1 (MaterialBase): The first material. material2 (MaterialBase): The second material. Returns: MaterialBase: A new material resulting from blending material1 and material2. """ return blend_on_properties( material1, material2, self.property_name, self.blend_width )
[docs] class GradientBlend(BlendMethod): """ Blend two materials using a linear gradient mask. Args: direction (str): The direction of the gradient ('horizontal' or 'vertical'). Raises: ValueError: If an invalid direction is specified. """
[docs] def __init__(self, direction: str = "horizontal"): if direction not in ["horizontal", "vertical"]: raise ValueError("Direction must be 'horizontal' or 'vertical'.") self.direction: str = direction
def __call__( self, material1: MaterialBase, material2: MaterialBase ) -> MaterialBase: """ Apply gradient-based blending to two materials. Args: material1 (MaterialBase): The first material. material2 (MaterialBase): The second material. Returns: MaterialBase: A new material resulting from blending material1 and material2. """ return blend_with_gradient(material1, material2, self.direction)
[docs] class BlendFactory: """ Factory class to instantiate blending methods. """ _BLEND_METHODS = { "mask": MaskBlend, "height": HeightBlend, "properties": PropertyBlend, "gradient": GradientBlend, }
[docs] @staticmethod def get_blend_method(method_name: str, **kwargs) -> BlendMethod: """ Factory method to get a blending method instance. Args: method_name (str): Name of the blending method ('mask', 'height', 'properties', 'gradient'). **kwargs: Parameters for the blending method. Returns: BlendMethod: An instance of a BlendMethod subclass. Raises: ValueError: If an unknown blending method is specified. """ method_class = BlendFactory._BLEND_METHODS.get(method_name.lower()) if not method_class: raise ValueError(f"Unknown blending method: {method_name}") return method_class(**kwargs)