Merge branch 'master' into blender2.8
[blender.git] / release / scripts / startup / bl_operators / object_quick_effects.py
1 # ##### BEGIN GPL LICENSE BLOCK #####
2 #
3 #  This program is free software; you can redistribute it and/or
4 #  modify it under the terms of the GNU General Public License
5 #  as published by the Free Software Foundation; either version 2
6 #  of the License, or (at your option) any later version.
7 #
8 #  This program is distributed in the hope that it will be useful,
9 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
10 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 #  GNU General Public License for more details.
12 #
13 #  You should have received a copy of the GNU General Public License
14 #  along with this program; if not, write to the Free Software Foundation,
15 #  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18
19 # <pep8-80 compliant>
20
21 from mathutils import Vector
22 import bpy
23 from bpy.types import Operator
24 from bpy.props import (
25     BoolProperty,
26     EnumProperty,
27     FloatProperty,
28     FloatVectorProperty,
29     IntProperty,
30 )
31
32
33 def object_ensure_material(obj, mat_name):
34     """ Use an existing material or add a new one.
35     """
36     mat = mat_slot = None
37     for mat_slot in obj.material_slots:
38         mat = mat_slot.material
39         if mat:
40             break
41     if mat is None:
42         mat = bpy.data.materials.new(mat_name)
43         if mat_slot:
44             mat_slot.material = mat
45         else:
46             obj.data.materials.append(mat)
47     return mat
48
49
50 class QuickFur(Operator):
51     bl_idname = "object.quick_fur"
52     bl_label = "Quick Fur"
53     bl_options = {'REGISTER', 'UNDO'}
54
55     density = EnumProperty(
56             name="Fur Density",
57             items=(('LIGHT', "Light", ""),
58                    ('MEDIUM', "Medium", ""),
59                    ('HEAVY', "Heavy", "")),
60             default='MEDIUM',
61             )
62     view_percentage = IntProperty(
63             name="View %",
64             min=1, max=100,
65             soft_min=1, soft_max=100,
66             default=10,
67             )
68     length = FloatProperty(
69             name="Length",
70             min=0.001, max=100,
71             soft_min=0.01, soft_max=10,
72             default=0.1,
73             )
74
75     def execute(self, context):
76         workspace = context.workspace
77         fake_context = context.copy()
78         mesh_objects = [obj for obj in context.selected_objects
79                         if obj.type == 'MESH' and workspace.object_mode == 'OBJECT']
80
81         if not mesh_objects:
82             self.report({'ERROR'}, "Select at least one mesh object")
83             return {'CANCELLED'}
84
85         mat = bpy.data.materials.new("Fur Material")
86         mat.strand.tip_size = 0.25
87         mat.strand.blend_distance = 0.5
88
89         for obj in mesh_objects:
90             fake_context["object"] = obj
91             bpy.ops.object.particle_system_add(fake_context)
92
93             psys = obj.particle_systems[-1]
94             psys.settings.type = 'HAIR'
95
96             if self.density == 'LIGHT':
97                 psys.settings.count = 100
98             elif self.density == 'MEDIUM':
99                 psys.settings.count = 1000
100             elif self.density == 'HEAVY':
101                 psys.settings.count = 10000
102
103             psys.settings.child_nbr = self.view_percentage
104             psys.settings.hair_length = self.length
105             psys.settings.use_strand_primitive = True
106             psys.settings.use_hair_bspline = True
107             psys.settings.child_type = 'INTERPOLATED'
108
109             obj.data.materials.append(mat)
110             psys.settings.material = len(obj.data.materials)
111
112         return {'FINISHED'}
113
114
115 class QuickExplode(Operator):
116     bl_idname = "object.quick_explode"
117     bl_label = "Quick Explode"
118     bl_options = {'REGISTER', 'UNDO'}
119
120     style = EnumProperty(
121             name="Explode Style",
122             items=(('EXPLODE', "Explode", ""),
123                    ('BLEND', "Blend", "")),
124             default='EXPLODE',
125             )
126     amount = IntProperty(
127             name="Amount of pieces",
128             min=2, max=10000,
129             soft_min=2, soft_max=10000,
130             default=100,
131             )
132     frame_duration = IntProperty(
133             name="Duration",
134             min=1, max=300000,
135             soft_min=1, soft_max=10000,
136             default=50,
137             )
138
139     frame_start = IntProperty(
140             name="Start Frame",
141             min=1, max=300000,
142             soft_min=1, soft_max=10000,
143             default=1,
144             )
145     frame_end = IntProperty(
146             name="End Frame",
147             min=1, max=300000,
148             soft_min=1, soft_max=10000,
149             default=10,
150             )
151
152     velocity = FloatProperty(
153             name="Outwards Velocity",
154             min=0, max=300000,
155             soft_min=0, soft_max=10,
156             default=1,
157             )
158
159     fade = BoolProperty(
160             name="Fade",
161             description="Fade the pieces over time",
162             default=True,
163             )
164
165     def execute(self, context):
166         fake_context = context.copy()
167         obj_act = context.active_object
168
169         if obj_act is None or obj_act.type != 'MESH':
170             self.report({'ERROR'}, "Active object is not a mesh")
171             return {'CANCELLED'}
172
173         mesh_objects = [obj for obj in context.selected_objects
174                         if obj.type == 'MESH' and obj != obj_act]
175         mesh_objects.insert(0, obj_act)
176
177         if self.style == 'BLEND' and len(mesh_objects) != 2:
178             self.report({'ERROR'}, "Select two mesh objects")
179             self.style = 'EXPLODE'
180             return {'CANCELLED'}
181         elif not mesh_objects:
182             self.report({'ERROR'}, "Select at least one mesh object")
183             return {'CANCELLED'}
184
185         for obj in mesh_objects:
186             if obj.particle_systems:
187                 self.report({'ERROR'},
188                             "Object %r already has a "
189                             "particle system" % obj.name)
190
191                 return {'CANCELLED'}
192
193         if self.fade:
194             tex = bpy.data.textures.new("Explode fade", 'BLEND')
195             tex.use_color_ramp = True
196
197             if self.style == 'BLEND':
198                 tex.color_ramp.elements[0].position = 0.333
199                 tex.color_ramp.elements[1].position = 0.666
200
201             tex.color_ramp.elements[0].color[3] = 1.0
202             tex.color_ramp.elements[1].color[3] = 0.0
203
204         if self.style == 'BLEND':
205             from_obj = mesh_objects[1]
206             to_obj = mesh_objects[0]
207
208         for obj in mesh_objects:
209             fake_context["object"] = obj
210             bpy.ops.object.particle_system_add(fake_context)
211
212             settings = obj.particle_systems[-1].settings
213             settings.count = self.amount
214             # first set frame end, to prevent frame start clamping
215             settings.frame_end = self.frame_end - self.frame_duration
216             settings.frame_start = self.frame_start
217             settings.lifetime = self.frame_duration
218             settings.normal_factor = self.velocity
219             settings.render_type = 'NONE'
220
221             explode = obj.modifiers.new(name='Explode', type='EXPLODE')
222             explode.use_edge_cut = True
223
224             if self.fade:
225                 explode.show_dead = False
226                 uv = obj.data.uv_layers.new("Explode fade")
227                 explode.particle_uv = uv.name
228
229                 mat = object_ensure_material(obj, "Explode Fade")
230
231                 mat.use_transparency = True
232                 mat.use_transparent_shadows = True
233                 mat.alpha = 0.0
234                 mat.specular_alpha = 0.0
235
236                 tex_slot = mat.texture_slots.add()
237
238                 tex_slot.texture = tex
239                 tex_slot.texture_coords = 'UV'
240                 tex_slot.uv_layer = uv.name
241
242                 tex_slot.use_map_alpha = True
243
244                 if self.style == 'BLEND':
245                     if obj == to_obj:
246                         tex_slot.alpha_factor = -1.0
247                         elem = tex.color_ramp.elements[1]
248                     else:
249                         elem = tex.color_ramp.elements[0]
250                     # Keep already defined alpha!
251                     elem.color[:3] = mat.diffuse_color
252                 else:
253                     tex_slot.use_map_color_diffuse = False
254
255             if self.style == 'BLEND':
256                 settings.physics_type = 'KEYED'
257                 settings.use_emit_random = False
258                 settings.rotation_mode = 'NOR'
259
260                 psys = obj.particle_systems[-1]
261
262                 fake_context["particle_system"] = obj.particle_systems[-1]
263                 bpy.ops.particle.new_target(fake_context)
264                 bpy.ops.particle.new_target(fake_context)
265
266                 if obj == from_obj:
267                     psys.targets[1].object = to_obj
268                 else:
269                     psys.targets[0].object = from_obj
270                     settings.normal_factor = -self.velocity
271                     explode.show_unborn = False
272                     explode.show_dead = True
273             else:
274                 settings.factor_random = self.velocity
275                 settings.angular_velocity_factor = self.velocity / 10.0
276
277         return {'FINISHED'}
278
279     def invoke(self, context, event):
280         self.frame_start = context.scene.frame_current
281         self.frame_end = self.frame_start + self.frame_duration
282         return self.execute(context)
283
284
285 def obj_bb_minmax(obj, min_co, max_co):
286     for i in range(0, 8):
287         bb_vec = obj.matrix_world * Vector(obj.bound_box[i])
288
289         min_co[0] = min(bb_vec[0], min_co[0])
290         min_co[1] = min(bb_vec[1], min_co[1])
291         min_co[2] = min(bb_vec[2], min_co[2])
292         max_co[0] = max(bb_vec[0], max_co[0])
293         max_co[1] = max(bb_vec[1], max_co[1])
294         max_co[2] = max(bb_vec[2], max_co[2])
295
296
297 def grid_location(x, y):
298     return (x * 200, y * 150)
299
300
301 class QuickSmoke(Operator):
302     bl_idname = "object.quick_smoke"
303     bl_label = "Quick Smoke"
304     bl_options = {'REGISTER', 'UNDO'}
305
306     style = EnumProperty(
307             name="Smoke Style",
308             items=(('SMOKE', "Smoke", ""),
309                    ('FIRE', "Fire", ""),
310                    ('BOTH', "Smoke + Fire", ""),
311                    ),
312             default='SMOKE',
313             )
314
315     show_flows = BoolProperty(
316             name="Render Smoke Objects",
317             description="Keep the smoke objects visible during rendering",
318             default=False,
319             )
320
321     def execute(self, context):
322         if not bpy.app.build_options.mod_smoke:
323             self.report({'ERROR'}, "Built without Smoke modifier support")
324             return {'CANCELLED'}
325
326         fake_context = context.copy()
327         mesh_objects = [obj for obj in context.selected_objects
328                         if obj.type == 'MESH']
329         min_co = Vector((100000.0, 100000.0, 100000.0))
330         max_co = -min_co
331
332         if not mesh_objects:
333             self.report({'ERROR'}, "Select at least one mesh object")
334             return {'CANCELLED'}
335
336         for obj in mesh_objects:
337             fake_context["object"] = obj
338             # make each selected object a smoke flow
339             bpy.ops.object.modifier_add(fake_context, type='SMOKE')
340             obj.modifiers[-1].smoke_type = 'FLOW'
341
342             # set type
343             obj.modifiers[-1].flow_settings.smoke_flow_type = self.style
344
345             if not self.show_flows:
346                 obj.draw_type = 'WIRE'
347
348             # store bounding box min/max for the domain object
349             obj_bb_minmax(obj, min_co, max_co)
350
351         # add the smoke domain object
352         bpy.ops.mesh.primitive_cube_add()
353         obj = context.active_object
354         obj.name = "Smoke Domain"
355
356         # give the smoke some room above the flows
357         obj.location = 0.5 * (max_co + min_co) + Vector((0.0, 0.0, 1.0))
358         obj.scale = 0.5 * (max_co - min_co) + Vector((1.0, 1.0, 2.0))
359
360         # setup smoke domain
361         bpy.ops.object.modifier_add(type='SMOKE')
362         obj.modifiers[-1].smoke_type = 'DOMAIN'
363         if self.style == 'FIRE' or self.style == 'BOTH':
364             obj.modifiers[-1].domain_settings.use_high_resolution = True
365
366         # Setup material
367
368         # Cycles
369         if context.scene.view_render.use_shading_nodes or context.view_render.use_shading_nodes:
370             bpy.ops.object.material_slot_add()
371
372             mat = bpy.data.materials.new("Smoke Domain Material")
373             obj.material_slots[0].material = mat
374
375             # Make sure we use nodes
376             mat.use_nodes = True
377
378             # Set node variables and clear the default nodes
379             tree = mat.node_tree
380             nodes = tree.nodes
381             links = tree.links
382
383             nodes.clear()
384
385             # Create shader nodes
386
387             # Material output
388             node_out = nodes.new(type='ShaderNodeOutputMaterial')
389             node_out.location = grid_location(6, 1)
390
391             # Add Principled Volume
392             node_principled = nodes.new(type='ShaderNodeVolumePrincipled')
393             node_principled.location = grid_location(4, 1)
394             links.new(node_principled.outputs["Volume"],
395                     node_out.inputs["Volume"])
396
397             node_principled.inputs["Density"].default_value = 5.0
398
399             if self.style in {'FIRE', 'BOTH'}:
400                 node_principled.inputs["Blackbody Intensity"].default_value = 1.0
401
402         # Blender Internal
403         else:
404             # create a volume material with a voxel data texture for the domain
405             bpy.ops.object.material_slot_add()
406
407             mat = bpy.data.materials.new("Smoke Domain Material")
408             obj.material_slots[0].material = mat
409             mat.type = 'VOLUME'
410             mat.volume.density = 0
411             mat.volume.density_scale = 5
412             mat.volume.step_size = 0.1
413
414             tex = bpy.data.textures.new("Smoke Density", 'VOXEL_DATA')
415             tex.voxel_data.domain_object = obj
416             tex.voxel_data.interpolation = 'TRICUBIC_BSPLINE'
417
418             tex_slot = mat.texture_slots.add()
419             tex_slot.texture = tex
420             tex_slot.texture_coords = 'ORCO'
421             tex_slot.use_map_color_emission = False
422             tex_slot.use_map_density = True
423             tex_slot.use_map_color_reflection = True
424
425             # for fire add a second texture for flame emission
426             mat.volume.emission_color = Vector((0.0, 0.0, 0.0))
427             tex = bpy.data.textures.new("Flame", 'VOXEL_DATA')
428             tex.voxel_data.domain_object = obj
429             tex.voxel_data.smoke_data_type = 'SMOKEFLAME'
430             tex.voxel_data.interpolation = 'TRICUBIC_BSPLINE'
431             tex.use_color_ramp = True
432
433             tex_slot = mat.texture_slots.add()
434             tex_slot.texture = tex
435             tex_slot.texture_coords = 'ORCO'
436
437             # add color ramp for flame color
438             ramp = tex.color_ramp
439             # dark orange
440             elem = ramp.elements.new(0.333)
441             elem.color = (0.2, 0.03, 0.0, 1.0)
442
443             # yellow glow
444             elem = ramp.elements.new(0.666)
445             elem.color = (1, 0.65, 0.25, 1.0)
446
447             mat.texture_slots[1].use_map_density = True
448             mat.texture_slots[1].use_map_emission = True
449             mat.texture_slots[1].emission_factor = 5
450
451         return {'FINISHED'}
452
453
454 class QuickFluid(Operator):
455     bl_idname = "object.quick_fluid"
456     bl_label = "Quick Fluid"
457     bl_options = {'REGISTER', 'UNDO'}
458
459     style = EnumProperty(
460             name="Fluid Style",
461             items=(('INFLOW', "Inflow", ""),
462                    ('BASIC', "Basic", "")),
463             default='BASIC',
464             )
465     initial_velocity = FloatVectorProperty(
466             name="Initial Velocity",
467             description="Initial velocity of the fluid",
468             min=-100.0, max=100.0,
469             default=(0.0, 0.0, 0.0),
470             subtype='VELOCITY',
471             )
472     show_flows = BoolProperty(
473             name="Render Fluid Objects",
474             description="Keep the fluid objects visible during rendering",
475             default=False,
476             )
477     start_baking = BoolProperty(
478             name="Start Fluid Bake",
479             description=("Start baking the fluid immediately "
480                          "after creating the domain object"),
481             default=False,
482             )
483
484     def execute(self, context):
485         if not bpy.app.build_options.mod_fluid:
486             self.report({'ERROR'}, "Built without Fluid modifier support")
487             return {'CANCELLED'}
488
489         fake_context = context.copy()
490         mesh_objects = [obj for obj in context.selected_objects
491                         if (obj.type == 'MESH' and 0.0 not in obj.dimensions)]
492         min_co = Vector((100000.0, 100000.0, 100000.0))
493         max_co = -min_co
494
495         if not mesh_objects:
496             self.report({'ERROR'}, "Select at least one mesh object")
497             return {'CANCELLED'}
498
499         for obj in mesh_objects:
500             fake_context["object"] = obj
501             # make each selected object a fluid
502             bpy.ops.object.modifier_add(fake_context, type='FLUID_SIMULATION')
503
504             # fluid has to be before constructive modifiers,
505             # so it might not be the last modifier
506             for mod in obj.modifiers:
507                 if mod.type == 'FLUID_SIMULATION':
508                     break
509
510             if self.style == 'INFLOW':
511                 mod.settings.type = 'INFLOW'
512                 mod.settings.inflow_velocity = self.initial_velocity
513             else:
514                 mod.settings.type = 'FLUID'
515                 mod.settings.initial_velocity = self.initial_velocity
516
517             obj.hide_render = not self.show_flows
518             if not self.show_flows:
519                 obj.draw_type = 'WIRE'
520
521             # store bounding box min/max for the domain object
522             obj_bb_minmax(obj, min_co, max_co)
523
524         # add the fluid domain object
525         bpy.ops.mesh.primitive_cube_add()
526         obj = context.active_object
527         obj.name = "Fluid Domain"
528
529         # give the fluid some room below the flows
530         # and scale with initial velocity
531         v = 0.5 * self.initial_velocity
532         obj.location = 0.5 * (max_co + min_co) + Vector((0.0, 0.0, -1.0)) + v
533         obj.scale = (0.5 * (max_co - min_co) +
534                      Vector((1.0, 1.0, 2.0)) +
535                      Vector((abs(v[0]), abs(v[1]), abs(v[2])))
536                      )
537
538         # setup smoke domain
539         bpy.ops.object.modifier_add(type='FLUID_SIMULATION')
540         obj.modifiers[-1].settings.type = 'DOMAIN'
541
542         # make the domain smooth so it renders nicely
543         bpy.ops.object.shade_smooth()
544
545         # create a ray-transparent material for the domain
546         bpy.ops.object.material_slot_add()
547
548         mat = bpy.data.materials.new("Fluid Domain Material")
549         obj.material_slots[0].material = mat
550
551         mat.specular_intensity = 1
552         mat.specular_hardness = 100
553         mat.use_transparency = True
554         mat.alpha = 0.0
555         mat.transparency_method = 'RAYTRACE'
556         mat.raytrace_transparency.ior = 1.33
557         mat.raytrace_transparency.depth = 4
558
559         if self.start_baking:
560             bpy.ops.fluid.bake('INVOKE_DEFAULT')
561
562         return {'FINISHED'}
563
564
565 classes = (
566     QuickExplode,
567     QuickFluid,
568     QuickFur,
569     QuickSmoke,
570 )