Source code for xiuminglib.blender.light

"""Add functions in this module usually provide no setter for the lamp's 3D
rotation, because one usually implicitly sets the rotation by pointing the
light to an object (and specifying an up vector), by using
:func:`point_light_to`.
"""

from os.path import basename
import numpy as np

from ..imprt import preset_import

from ..log import get_logger
logger = get_logger()


[docs]def point_light_to(light, target): """Points the directional light to a target. Args: light (bpy_types.Object): Light object. target (tuple(float)): Target location to which light rays point. """ Vector = preset_import('Vector', assert_success=True) target = Vector(target) # Point it to target direction = target - light.location # Find quaternion that rotates lamp facing direction '-Z', # so that it aligns with 'direction' # This rotation is not unique because the rotated lamp can still rotate # about direction vector # Specifying 'Y' gives the rotation quaternion with lamp's 'Y' pointing up rot_quat = direction.to_track_quat('-Z', 'Y') light.rotation_euler = rot_quat.to_euler() logger.info("Lamp '%s' points to %s now", light.name, target)
[docs]def add_light_sun(xyz=(0, 0, 0), rot_vec_rad=(0, 0, 0), name=None, energy=1, size=0.1): """Adds a sun lamp that emits parallel light rays. Args: xyz (tuple(float), optional): Location only used to compute light ray direction. rot_vec_rad (tuple(float), optional): Rotations in radians around x, y and z. name (str, optional): Light name. energy (float, optional): Light intensity. size (float, optional): Light size for ray shadow tracing. Use larger for softer shadows. Returns: bpy_types.Object: Light added. """ bpy = preset_import('bpy', assert_success=True) bpy.ops.object.light_add( type='SUN', location=xyz, rotation=rot_vec_rad) sun = bpy.context.active_object if name is not None: sun.name = name sun.data.shadow_soft_size = size # larger means softer shadows # Strength engine = bpy.context.scene.render.engine if engine == 'CYCLES': sun.data.use_nodes = True sun.data.node_tree.nodes['Emission'].inputs[ 'Strength'].default_value = energy else: raise NotImplementedError(engine) logger.info("Sun lamp (parallel light) added") return sun
[docs]def add_light_area(xyz=(0, 0, 0), rot_vec_rad=(0, 0, 0), name=None, energy=100, size=0.1): """Adds an area light that emits light rays the lambertian way. Args: xyz (tuple(float), optional): Location. rot_vec_rad (tuple(float), optional): Rotations in radians around x, y and z. name (str, optional): Light name. energy (float, optional): Light intensity. size (float, optional): Light size for ray shadow tracing. Use larger values for softer shadows. Returns: bpy_types.Object: Light added. """ bpy = preset_import('bpy', assert_success=True) if (np.abs(rot_vec_rad) > 2 * np.pi).any(): logger.warning( ("Some input value falls outside [-2pi, 2pi]. " "Sure inputs are in radians?")) bpy.ops.object.light_add(type='AREA', location=xyz, rotation=rot_vec_rad) area = bpy.context.active_object if name is not None: area.name = name area.data.size = size # larger means softer shadows # Strength engine = bpy.context.scene.render.engine if engine == 'CYCLES': area.data.node_tree.nodes['Emission'].inputs[ 'Strength'].default_value = energy else: raise NotImplementedError(engine) logger.info("Area light added") return area
[docs]def add_light_point(xyz=(0, 0, 0), name=None, size=0, energy=100): """Adds an omnidirectional point lamp. Args: xyz (tuple(float), optional): Location. name (str, optional): Light name. size (float, optional): Light size; the larger the softer shadows are. energy (float, optional): Light intensity. Returns: bpy_types.Object: Light added. """ bpy = preset_import('bpy', assert_success=True) bpy.ops.object.light_add(type='POINT', location=xyz) point = bpy.context.active_object point.data.use_nodes = True if name is not None: point.name = name point.data.shadow_soft_size = size # Strength engine = bpy.context.scene.render.engine if engine == 'CYCLES': point.data.node_tree.nodes['Emission'].inputs[ 'Strength'].default_value = energy else: raise NotImplementedError(engine) logger.info("Omnidirectional point light added") return point
[docs]def add_light_spot(xyz=(0, 0, 0), name=None, energy=100, shadow_soft_size=0.1, spot_size=0.785, spot_blend=0.15): """Adds a spotlight lamp. Args: xyz (tuple(float), optional): Location. name (str, optional): Light name. energy (float, optional): Light intensity. shadow_soft_size (float, optional): Light size for raytracing the shadow. spot_size (float, optional): Angle, in radians, of the spotlight beam. spot_blend (float, optional): Softness of the spotlight edge. Returns: bpy_types.Object: Light added. """ bpy = preset_import('bpy', assert_success=True) bpy.ops.object.light_add(type='SPOT', location=xyz) spot = bpy.context.active_object if name is not None: spot.name = name # Strength engine = bpy.context.scene.render.engine if engine == 'CYCLES': spot.data.node_tree.nodes['Emission'].inputs[ 'Strength'].default_value = energy else: raise NotImplementedError(engine) spot.data.shadow_soft_size = shadow_soft_size # Spot shape spot.data.spot_size = spot_size spot.data.spot_blend = spot_blend logger.info("Spotlight lamp added") return spot
[docs]def add_light_env(env=(1, 1, 1, 1), strength=1, rot_vec_rad=(0, 0, 0), scale=(1, 1, 1)): r"""Adds environment lighting. Args: env (tuple(float) or str, optional): Environment map. If tuple, it's RGB or RGBA, each element of which :math:`\in [0,1]`. Otherwise, it's the path to an image. strength (float, optional): Light intensity. rot_vec_rad (tuple(float), optional): Rotations in radians around x, y and z. scale (tuple(float), optional): If all changed simultaneously, then no effects. """ bpy = preset_import('bpy', assert_success=True) engine = bpy.context.scene.render.engine assert engine == 'CYCLES', "Rendering engine is not Cycles" if isinstance(env, str): bpy.data.images.load(env, check_existing=True) env = bpy.data.images[basename(env)] else: if len(env) == 3: env += (1,) assert len(env) == 4, "If tuple, env must be of length 3 or 4" world = bpy.context.scene.world world.use_nodes = True node_tree = world.node_tree nodes = node_tree.nodes links = node_tree.links bg_node = nodes.new('ShaderNodeBackground') links.new(bg_node.outputs['Background'], nodes['World Output'].inputs['Surface']) if isinstance(env, tuple): # Color bg_node.inputs['Color'].default_value = env logger.warning(("Environment is pure color, " "so rotation and scale have no effect")) else: # Environment map texcoord_node = nodes.new('ShaderNodeTexCoord') env_node = nodes.new('ShaderNodeTexEnvironment') env_node.image = env mapping_node = nodes.new('ShaderNodeMapping') mapping_node.inputs['Rotation'].default_value = rot_vec_rad mapping_node.inputs['Scale'].default_value = scale links.new( texcoord_node.outputs['Generated'], mapping_node.inputs['Vector']) links.new(mapping_node.outputs['Vector'], env_node.inputs['Vector']) links.new(env_node.outputs['Color'], bg_node.inputs['Color']) bg_node.inputs['Strength'].default_value = strength logger.info("Environment light added")