File headers: use SPDX license identifiers
[blender-addons.git] / amaranth / render / meshlight_add.py
bloba7febbfab8e8bcc1219ab6eea600678dfb62f479
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 import bpy
4 from mathutils import Vector
5 from amaranth.utils import cycles_exists
8 # FEATURE: Add Meshlight
9 class AMTH_OBJECT_OT_meshlight_add(bpy.types.Operator):
11 """Add a light emitting mesh"""
12 bl_idname = "object.meshlight_add"
13 bl_label = "Add Meshlight"
14 bl_options = {'REGISTER', 'UNDO'}
16 single_sided: bpy.props.BoolProperty(
17 name="Single Sided",
18 default=True,
19 description="Only emit light on one side",
22 is_constant: bpy.props.BoolProperty(
23 name="Constant Falloff",
24 default=False,
25 description="Energy is constant (i.e. the Sun), "
26 "independent of how close to the source you are",
29 visible: bpy.props.BoolProperty(
30 name="Visible on Camera",
31 default=False,
32 description="Whether to show the meshlight source on Camera",
35 size: bpy.props.FloatProperty(
36 name="Size",
37 description="Meshlight size. Lower is sharper shadows, higher is softer",
38 min=0.01, max=100.0,
39 default=1.0,
42 strength: bpy.props.FloatProperty(
43 name="Strength",
44 min=0.01, max=100000.0,
45 default=1.5,
46 step=0.25,
49 temperature: bpy.props.FloatProperty(
50 name="Temperature",
51 min=800, max=12000.0,
52 default=5500.0,
53 step=100.0,
54 description="Temperature in Kelvin. Lower is warmer, higher is colder",
57 rotation: bpy.props.FloatVectorProperty(
58 name="Rotation",
59 subtype='EULER',
62 def execute(self, context):
63 scene = context.scene
64 # exists = False
65 number = 1
67 for obs in bpy.data.objects:
68 if obs.name.startswith("light_meshlight"):
69 number += 1
71 meshlight_name = 'light_meshlight_%.2d' % number
73 bpy.ops.mesh.primitive_grid_add(
74 x_subdivisions=4, y_subdivisions=4,
75 rotation=self.rotation, size=self.size)
77 bpy.context.object.name = meshlight_name
78 meshlight = scene.objects[meshlight_name]
79 meshlight.show_wire = True
80 meshlight.show_all_edges = True
82 material = bpy.data.materials.get(meshlight_name)
84 if not material:
85 material = bpy.data.materials.new(meshlight_name)
87 bpy.ops.object.material_slot_add()
88 meshlight.active_material = material
90 material.use_nodes = True
91 material.diffuse_color = (1, 0.5, 0, 1)
92 nodes = material.node_tree.nodes
93 links = material.node_tree.links
95 # clear default nodes to start nice fresh
96 for no in nodes:
97 nodes.remove(no)
99 if self.single_sided:
100 geometry = nodes.new(type="ShaderNodeNewGeometry")
102 transparency = nodes.new(type="ShaderNodeBsdfTransparent")
103 transparency.inputs[0].default_value = (1, 1, 1, 1)
104 transparency.location = geometry.location
105 transparency.location += Vector((0.0, -55.0))
107 emission = nodes.new(type="ShaderNodeEmission")
108 emission.inputs['Strength'].default_value = self.strength
109 emission.location = transparency.location
110 emission.location += Vector((0.0, -80.0))
112 blackbody = nodes.new(type="ShaderNodeBlackbody")
113 blackbody.inputs['Temperature'].default_value = self.temperature
114 blackbody.location = emission.location
115 blackbody.location += Vector((-180.0, 0.0))
116 blackbody.label = 'Temperature'
118 mix = nodes.new(type="ShaderNodeMixShader")
119 mix.location = geometry.location
120 mix.location += Vector((180.0, 0.0))
121 mix.inputs[2].show_expanded = True
123 output = nodes.new(type="ShaderNodeOutputMaterial")
124 output.inputs[1].hide = True
125 output.inputs[2].hide = True
126 output.location = mix.location
127 output.location += Vector((180.0, 0.0))
129 # Make links
130 links.new(geometry.outputs['Backfacing'], mix.inputs[0])
131 links.new(transparency.outputs['BSDF'], mix.inputs[1])
132 links.new(emission.outputs['Emission'], mix.inputs[2])
133 links.new(blackbody.outputs['Color'], emission.inputs['Color'])
134 links.new(mix.outputs['Shader'], output.inputs['Surface'])
136 for sockets in geometry.outputs:
137 sockets.hide = True
138 else:
139 emission = nodes.new(type="ShaderNodeEmission")
140 emission.inputs['Strength'].default_value = self.strength
142 blackbody = nodes.new(type="ShaderNodeBlackbody")
143 blackbody.inputs['Temperature'].default_value = self.temperature
144 blackbody.location = emission.location
145 blackbody.location += Vector((-180.0, 0.0))
146 blackbody.label = 'Temperature'
148 output = nodes.new(type="ShaderNodeOutputMaterial")
149 output.inputs[1].hide = True
150 output.inputs[2].hide = True
151 output.location = emission.location
152 output.location += Vector((180.0, 0.0))
154 links.new(blackbody.outputs['Color'], emission.inputs['Color'])
155 links.new(emission.outputs['Emission'], output.inputs['Surface'])
157 if self.is_constant:
158 falloff = nodes.new(type="ShaderNodeLightFalloff")
159 falloff.inputs['Strength'].default_value = self.strength
160 falloff.location = emission.location
161 falloff.location += Vector((-180.0, -80.0))
163 links.new(falloff.outputs['Constant'], emission.inputs['Strength'])
165 for sockets in falloff.outputs:
166 sockets.hide = True
168 # so it shows slider on properties editor
169 for sockets in emission.inputs:
170 sockets.show_expanded = True
172 material.cycles.sample_as_light = True
173 meshlight.visible_shadow = False
174 meshlight.visible_camera = self.visible
176 return {'FINISHED'}
179 def ui_menu_lamps_add(self, context):
180 if cycles_exists() and context.scene.render.engine == 'CYCLES':
181 self.layout.separator()
182 self.layout.operator(
183 AMTH_OBJECT_OT_meshlight_add.bl_idname,
184 icon="LIGHT_AREA", text="Meshlight")
186 # //FEATURE: Add Meshlight: Single Sided
189 def register():
190 bpy.utils.register_class(AMTH_OBJECT_OT_meshlight_add)
191 bpy.types.VIEW3D_MT_light_add.append(ui_menu_lamps_add)
194 def unregister():
195 bpy.utils.unregister_class(AMTH_OBJECT_OT_meshlight_add)
196 bpy.types.VIEW3D_MT_light_add.remove(ui_menu_lamps_add)