# SPDX-License-Identifier: GPL-2.0-or-later # author Daniel Schalla, maintained by meta-androcto bl_info = { "name": "Tri-lighting", "author": "Daniel Schalla", "version": (0, 1, 4), "blender": (2, 80, 0), "location": "View3D > Add > Lights", "description": "Add 3 Point Lighting to Selected / Active Object", "warning": "", "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", "doc_url": "{BLENDER_MANUAL_URL}/addons/lighting/trilighting.html", "category": "Lighting", } import bpy from bpy.types import Operator from bpy.props import ( EnumProperty, FloatProperty, IntProperty, ) from math import ( sin, cos, radians, sqrt, ) class OBJECT_OT_TriLighting(Operator): bl_idname = "object.trilighting" bl_label = "Tri-Lighting Creator" bl_description = ("Add 3 Point Lighting to Selected / Active Object\n" "Needs an existing Active Object") bl_options = {'REGISTER', 'UNDO'} COMPAT_ENGINES = {'CYCLES', 'EEVEE'} height: FloatProperty( name="Height", default=5 ) distance: FloatProperty( name="Distance", default=5, min=0.1, subtype="DISTANCE" ) energy: IntProperty( name="Base Energy", default=3, min=1 ) contrast: IntProperty( name="Contrast", default=50, min=-100, max=100, subtype="PERCENTAGE" ) leftangle: IntProperty( name="Left Angle", default=26, min=1, max=90, subtype="ANGLE" ) rightangle: IntProperty( name="Right Angle", default=45, min=1, max=90, subtype="ANGLE" ) backangle: IntProperty( name="Back Angle", default=235, min=90, max=270, subtype="ANGLE" ) Light_Type_List = [ ('POINT', "Point", "Point Light"), ('SUN', "Sun", "Sun Light"), ('SPOT', "Spot", "Spot Light"), ('AREA', "Area", "Area Light") ] primarytype: EnumProperty( attr='tl_type', name="Key Type", description="Choose the types of Key Lights you would like", items=Light_Type_List, default='AREA' ) secondarytype: EnumProperty( attr='tl_type', name="Fill + Back Type", description="Choose the types of secondary Lights you would like", items=Light_Type_List, default="AREA" ) @classmethod def poll(cls, context): return context.active_object is not None def draw(self, context): layout = self.layout layout.label(text="Position:") col = layout.column(align=True) col.prop(self, "height") col.prop(self, "distance") layout.label(text="Light:") col = layout.column(align=True) col.prop(self, "energy") col.prop(self, "contrast") layout.label(text="Orientation:") col = layout.column(align=True) col.prop(self, "leftangle") col.prop(self, "rightangle") col.prop(self, "backangle") col = layout.column() col.label(text="Key Light Type:") col.prop(self, "primarytype", text="") col.label(text="Fill + Back Type:") col.prop(self, "secondarytype", text="") def execute(self, context): try: collection = context.collection scene = context.scene view = context.space_data if view.type == 'VIEW_3D': camera = view.camera else: camera = scene.camera if (camera is None): cam_data = bpy.data.cameras.new(name='Camera') cam_obj = bpy.data.objects.new(name='Camera', object_data=cam_data) collection.objects.link(cam_obj) scene.camera = cam_obj bpy.ops.view3d.camera_to_view() camera = cam_obj # Leave camera view again, otherwise redo does not work correctly. bpy.ops.view3d.view_camera() obj = bpy.context.view_layer.objects.active # Calculate Energy for each Lamp if(self.contrast > 0): keyEnergy = self.energy backEnergy = (self.energy / 100) * abs(self.contrast) fillEnergy = (self.energy / 100) * abs(self.contrast) else: keyEnergy = (self.energy / 100) * abs(self.contrast) backEnergy = self.energy fillEnergy = self.energy # Calculate Direction for each Lamp # Calculate current Distance and get Delta obj_position = obj.location cam_position = camera.location delta_position = cam_position - obj_position vector_length = sqrt( (pow(delta_position.x, 2) + pow(delta_position.y, 2) + pow(delta_position.z, 2)) ) if not vector_length: # division by zero most likely self.report({'WARNING'}, "Operation Cancelled. No viable object in the scene") return {'CANCELLED'} single_vector = (1 / vector_length) * delta_position # Calc back position singleback_vector = single_vector.copy() singleback_vector.x = cos(radians(self.backangle)) * single_vector.x + \ (-sin(radians(self.backangle)) * single_vector.y) singleback_vector.y = sin(radians(self.backangle)) * single_vector.x + \ (cos(radians(self.backangle)) * single_vector.y) backx = obj_position.x + self.distance * singleback_vector.x backy = obj_position.y + self.distance * singleback_vector.y backData = bpy.data.lights.new(name="TriLamp-Back", type=self.secondarytype) backData.energy = backEnergy backLamp = bpy.data.objects.new(name="TriLamp-Back", object_data=backData) collection.objects.link(backLamp) backLamp.location = (backx, backy, self.height) trackToBack = backLamp.constraints.new(type="TRACK_TO") trackToBack.target = obj trackToBack.track_axis = "TRACK_NEGATIVE_Z" trackToBack.up_axis = "UP_Y" # Calc right position singleright_vector = single_vector.copy() singleright_vector.x = cos(radians(self.rightangle)) * single_vector.x + \ (-sin(radians(self.rightangle)) * single_vector.y) singleright_vector.y = sin(radians(self.rightangle)) * single_vector.x + \ (cos(radians(self.rightangle)) * single_vector.y) rightx = obj_position.x + self.distance * singleright_vector.x righty = obj_position.y + self.distance * singleright_vector.y rightData = bpy.data.lights.new(name="TriLamp-Fill", type=self.secondarytype) rightData.energy = fillEnergy rightLamp = bpy.data.objects.new(name="TriLamp-Fill", object_data=rightData) collection.objects.link(rightLamp) rightLamp.location = (rightx, righty, self.height) trackToRight = rightLamp.constraints.new(type="TRACK_TO") trackToRight.target = obj trackToRight.track_axis = "TRACK_NEGATIVE_Z" trackToRight.up_axis = "UP_Y" # Calc left position singleleft_vector = single_vector.copy() singleleft_vector.x = cos(radians(-self.leftangle)) * single_vector.x + \ (-sin(radians(-self.leftangle)) * single_vector.y) singleleft_vector.y = sin(radians(-self.leftangle)) * single_vector.x + \ (cos(radians(-self.leftangle)) * single_vector.y) leftx = obj_position.x + self.distance * singleleft_vector.x lefty = obj_position.y + self.distance * singleleft_vector.y leftData = bpy.data.lights.new(name="TriLamp-Key", type=self.primarytype) leftData.energy = keyEnergy leftLamp = bpy.data.objects.new(name="TriLamp-Key", object_data=leftData) collection.objects.link(leftLamp) leftLamp.location = (leftx, lefty, self.height) trackToLeft = leftLamp.constraints.new(type="TRACK_TO") trackToLeft.target = obj trackToLeft.track_axis = "TRACK_NEGATIVE_Z" trackToLeft.up_axis = "UP_Y" except Exception as e: self.report({'WARNING'}, "Some operations could not be performed (See Console for more info)") print("\n[Add Advanced Objects]\nOperator: " "object.trilighting\nError: {}".format(e)) return {'CANCELLED'} return {'FINISHED'} def menu_func(self, context): self.layout.operator(OBJECT_OT_TriLighting.bl_idname, text="3 Point Lights", icon='LIGHT') # Register all operators and menu def register(): bpy.utils.register_class(OBJECT_OT_TriLighting) bpy.types.VIEW3D_MT_light_add.append(menu_func) def unregister(): bpy.utils.unregister_class(OBJECT_OT_TriLighting) bpy.types.VIEW3D_MT_light_add.remove(menu_func) if __name__ == "__main__": register()