Code cleanup: style
[blender-staging.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 (BoolProperty,
25                        EnumProperty,
26                        IntProperty,
27                        FloatProperty,
28                        FloatVectorProperty,
29                        )
30
31
32 def object_ensure_material(obj, mat_name):
33     """ Use an existing material or add a new one.
34     """
35     mat = mat_slot = None
36     for mat_slot in obj.material_slots:
37         mat = mat_slot.material
38         if mat:
39             break
40     if mat is None:
41         mat = bpy.data.materials.new(mat_name)
42         if mat_slot:
43             mat_slot.material = mat
44         else:
45             obj.data.materials.append(mat)
46     return mat
47
48
49 class QuickFur(Operator):
50     bl_idname = "object.quick_fur"
51     bl_label = "Quick Fur"
52     bl_options = {'REGISTER', 'UNDO'}
53
54     density = EnumProperty(
55             name="Fur Density",
56             items=(('LIGHT', "Light", ""),
57                    ('MEDIUM', "Medium", ""),
58                    ('HEAVY', "Heavy", "")),
59             default='MEDIUM',
60             )
61     view_percentage = IntProperty(
62             name="View %",
63             min=1, max=100,
64             soft_min=1, soft_max=100,
65             default=10,
66             )
67     length = FloatProperty(
68             name="Length",
69             min=0.001, max=100,
70             soft_min=0.01, soft_max=10,
71             default=0.1,
72             )
73
74     def execute(self, context):
75         fake_context = context.copy()
76         mesh_objects = [obj for obj in context.selected_objects
77                         if obj.type == 'MESH']
78
79         if not mesh_objects:
80             self.report({'ERROR'}, "Select at least one mesh object")
81             return {'CANCELLED'}
82
83         mat = bpy.data.materials.new("Fur Material")
84         mat.strand.tip_size = 0.25
85         mat.strand.blend_distance = 0.5
86
87         for obj in mesh_objects:
88             fake_context["object"] = obj
89             bpy.ops.object.particle_system_add(fake_context)
90
91             psys = obj.particle_systems[-1]
92             psys.settings.type = 'HAIR'
93
94             if self.density == 'LIGHT':
95                 psys.settings.count = 100
96             elif self.density == 'MEDIUM':
97                 psys.settings.count = 1000
98             elif self.density == 'HEAVY':
99                 psys.settings.count = 10000
100
101             psys.settings.child_nbr = self.view_percentage
102             psys.settings.hair_length = self.length
103             psys.settings.use_strand_primitive = True
104             psys.settings.use_hair_bspline = True
105             psys.settings.child_type = 'INTERPOLATED'
106
107             obj.data.materials.append(mat)
108             psys.settings.material = len(obj.data.materials)
109
110         return {'FINISHED'}
111
112
113 class QuickExplode(Operator):
114     bl_idname = "object.quick_explode"
115     bl_label = "Quick Explode"
116     bl_options = {'REGISTER', 'UNDO'}
117
118     style = EnumProperty(
119             name="Explode Style",
120             items=(('EXPLODE', "Explode", ""),
121                    ('BLEND', "Blend", "")),
122             default='EXPLODE',
123             )
124     amount = IntProperty(
125             name="Amount of pieces",
126             min=2, max=10000,
127             soft_min=2, soft_max=10000,
128             default=100,
129             )
130     frame_duration = IntProperty(
131             name="Duration",
132             min=1, max=300000,
133             soft_min=1, soft_max=10000,
134             default=50,
135             )
136
137     frame_start = IntProperty(
138             name="Start Frame",
139             min=1, max=300000,
140             soft_min=1, soft_max=10000,
141             default=1,
142             )
143     frame_end = IntProperty(
144             name="End Frame",
145             min=1, max=300000,
146             soft_min=1, soft_max=10000,
147             default=10,
148             )
149
150     velocity = FloatProperty(
151             name="Outwards Velocity",
152             min=0, max=300000,
153             soft_min=0, soft_max=10,
154             default=1,
155             )
156
157     fade = BoolProperty(
158             name="Fade",
159             description="Fade the pieces over time",
160             default=True,
161             )
162
163     def execute(self, context):
164         fake_context = context.copy()
165         obj_act = context.active_object
166
167         if obj_act is None or obj_act.type != 'MESH':
168             self.report({'ERROR'}, "Active object is not a mesh")
169             return {'CANCELLED'}
170
171         mesh_objects = [obj for obj in context.selected_objects
172                         if obj.type == 'MESH' and obj != obj_act]
173         mesh_objects.insert(0, obj_act)
174
175         if self.style == 'BLEND' and len(mesh_objects) != 2:
176             self.report({'ERROR'}, "Select two mesh objects")
177             self.style = 'EXPLODE'
178             return {'CANCELLED'}
179         elif not mesh_objects:
180             self.report({'ERROR'}, "Select at least one mesh object")
181             return {'CANCELLED'}
182
183         for obj in mesh_objects:
184             if obj.particle_systems:
185                 self.report({'ERROR'},
186                             "Object %r already has a "
187                             "particle system" % obj.name)
188
189                 return {'CANCELLED'}
190
191         if self.fade:
192             tex = bpy.data.textures.new("Explode fade", 'BLEND')
193             tex.use_color_ramp = True
194
195             if self.style == 'BLEND':
196                 tex.color_ramp.elements[0].position = 0.333
197                 tex.color_ramp.elements[1].position = 0.666
198
199             tex.color_ramp.elements[0].color[3] = 1.0
200             tex.color_ramp.elements[1].color[3] = 0.0
201
202         if self.style == 'BLEND':
203             from_obj = mesh_objects[1]
204             to_obj = mesh_objects[0]
205
206         for obj in mesh_objects:
207             fake_context["object"] = obj
208             bpy.ops.object.particle_system_add(fake_context)
209
210             settings = obj.particle_systems[-1].settings
211             settings.count = self.amount
212             settings.frame_start = self.frame_start
213             settings.frame_end = self.frame_end - self.frame_duration
214             settings.lifetime = self.frame_duration
215             settings.normal_factor = self.velocity
216             settings.render_type = 'NONE'
217
218             explode = obj.modifiers.new(name='Explode', type='EXPLODE')
219             explode.use_edge_cut = True
220
221             if self.fade:
222                 explode.show_dead = False
223                 uv = obj.data.uv_textures.new("Explode fade")
224                 explode.particle_uv = uv.name
225
226                 mat = object_ensure_material(obj, "Explode Fade")
227
228                 mat.use_transparency = True
229                 mat.use_transparent_shadows = True
230                 mat.alpha = 0.0
231                 mat.specular_alpha = 0.0
232
233                 tex_slot = mat.texture_slots.add()
234
235                 tex_slot.texture = tex
236                 tex_slot.texture_coords = 'UV'
237                 tex_slot.uv_layer = uv.name
238
239                 tex_slot.use_map_alpha = True
240
241                 if self.style == 'BLEND':
242                     if obj == to_obj:
243                         tex_slot.alpha_factor = -1.0
244                         elem = tex.color_ramp.elements[1]
245                     else:
246                         elem = tex.color_ramp.elements[0]
247                     # Keep already defined alpha!
248                     elem.color[:3] = mat.diffuse_color
249                 else:
250                     tex_slot.use_map_color_diffuse = False
251
252             if self.style == 'BLEND':
253                 settings.physics_type = 'KEYED'
254                 settings.use_emit_random = False
255                 settings.rotation_mode = 'NOR'
256
257                 psys = obj.particle_systems[-1]
258
259                 fake_context["particle_system"] = obj.particle_systems[-1]
260                 bpy.ops.particle.new_target(fake_context)
261                 bpy.ops.particle.new_target(fake_context)
262
263                 if obj == from_obj:
264                     psys.targets[1].object = to_obj
265                 else:
266                     psys.targets[0].object = from_obj
267                     settings.normal_factor = -self.velocity
268                     explode.show_unborn = False
269                     explode.show_dead = True
270             else:
271                 settings.factor_random = self.velocity
272                 settings.angular_velocity_factor = self.velocity / 10.0
273
274         return {'FINISHED'}
275
276     def invoke(self, context, event):
277         self.frame_start = context.scene.frame_current
278         self.frame_end = self.frame_start + self.frame_duration
279         return self.execute(context)
280
281
282 def obj_bb_minmax(obj, min_co, max_co):
283     for i in range(0, 8):
284         bb_vec = obj.matrix_world * Vector(obj.bound_box[i])
285
286         min_co[0] = min(bb_vec[0], min_co[0])
287         min_co[1] = min(bb_vec[1], min_co[1])
288         min_co[2] = min(bb_vec[2], min_co[2])
289         max_co[0] = max(bb_vec[0], max_co[0])
290         max_co[1] = max(bb_vec[1], max_co[1])
291         max_co[2] = max(bb_vec[2], max_co[2])
292
293
294 class QuickSmoke(Operator):
295     bl_idname = "object.quick_smoke"
296     bl_label = "Quick Smoke"
297     bl_options = {'REGISTER', 'UNDO'}
298
299     style = EnumProperty(
300             name="Smoke Style",
301             items=(('STREAM', "Stream", ""),
302                    ('FIRE', "Fire", ""),
303                    ),
304             default='STREAM',
305             )
306
307     show_flows = BoolProperty(
308             name="Render Smoke Objects",
309             description="Keep the smoke objects visible during rendering",
310             default=False,
311             )
312
313     def execute(self, context):
314         fake_context = context.copy()
315         mesh_objects = [obj for obj in context.selected_objects
316                         if obj.type == 'MESH']
317         min_co = Vector((100000.0, 100000.0, 100000.0))
318         max_co = -min_co
319
320         if not mesh_objects:
321             self.report({'ERROR'}, "Select at least one mesh object")
322             return {'CANCELLED'}
323
324         for obj in mesh_objects:
325             fake_context["object"] = obj
326             # make each selected object a smoke flow
327             bpy.ops.object.modifier_add(fake_context, type='SMOKE')
328             obj.modifiers[-1].smoke_type = 'FLOW'
329
330             if self.style == 'FIRE':
331                 obj.modifiers[-1].flow_settings.smoke_flow_type = 'FIRE'
332
333             if not self.show_flows:
334                 obj.draw_type = 'WIRE'
335
336             # store bounding box min/max for the domain object
337             obj_bb_minmax(obj, min_co, max_co)
338
339         # add the smoke domain object
340         bpy.ops.mesh.primitive_cube_add()
341         obj = context.active_object
342         obj.name = "Smoke Domain"
343
344         # give the smoke some room above the flows
345         obj.location = 0.5 * (max_co + min_co) + Vector((0.0, 0.0, 1.0))
346         obj.scale = 0.5 * (max_co - min_co) + Vector((1.0, 1.0, 2.0))
347
348         # setup smoke domain
349         bpy.ops.object.modifier_add(type='SMOKE')
350         obj.modifiers[-1].smoke_type = 'DOMAIN'
351         if self.style == 'FIRE':
352             obj.modifiers[-1].domain_settings.use_high_resolution = True
353
354         # create a volume material with a voxel data texture for the domain
355         bpy.ops.object.material_slot_add()
356
357         mat = bpy.data.materials.new("Smoke Domain Material")
358         obj.material_slots[0].material = mat
359         mat.type = 'VOLUME'
360         mat.volume.density = 0
361         mat.volume.density_scale = 5
362         mat.volume.step_size = 0.1
363
364         tex = bpy.data.textures.new("Smoke Density", 'VOXEL_DATA')
365         tex.voxel_data.domain_object = obj
366         tex.voxel_data.interpolation = 'TRICUBIC_BSPLINE'
367
368         tex_slot = mat.texture_slots.add()
369         tex_slot.texture = tex
370         tex_slot.use_map_color_emission = False
371         tex_slot.use_map_density = True
372         tex_slot.use_map_color_reflection = True
373
374         # for fire add a second texture for flame emission
375         mat.volume.emission_color = Vector((0.0, 0.0, 0.0))
376         tex = bpy.data.textures.new("Flame", 'VOXEL_DATA')
377         tex.voxel_data.domain_object = obj
378         tex.voxel_data.smoke_data_type = 'SMOKEFLAME'
379         tex.voxel_data.interpolation = 'TRICUBIC_BSPLINE'
380         tex.use_color_ramp = True
381
382         tex_slot = mat.texture_slots.add()
383         tex_slot.texture = tex
384
385         # add color ramp for flame color
386         ramp = tex.color_ramp
387         # dark orange
388         elem = ramp.elements.new(0.333)
389         elem.color[0] = 0.2
390         elem.color[1] = 0.03
391         elem.color[2] = 0
392         elem.color[3] = 1
393         # yellow glow
394         elem = ramp.elements.new(0.666)
395         elem.color[0] = elem.color[3] = 1
396         elem.color[1] = 0.65
397         elem.color[2] = 0.25
398
399         mat.texture_slots[1].use_map_density = True
400         mat.texture_slots[1].use_map_emission = True
401         mat.texture_slots[1].emission_factor = 5
402
403         return {'FINISHED'}
404
405
406 class QuickFluid(Operator):
407     bl_idname = "object.quick_fluid"
408     bl_label = "Quick Fluid"
409     bl_options = {'REGISTER', 'UNDO'}
410
411     style = EnumProperty(
412             name="Fluid Style",
413             items=(('INFLOW', "Inflow", ""),
414                    ('BASIC', "Basic", "")),
415             default='BASIC',
416             )
417     initial_velocity = FloatVectorProperty(
418             name="Initial Velocity",
419             description="Initial velocity of the fluid",
420             min=-100.0, max=100.0,
421             default=(0.0, 0.0, 0.0),
422             subtype='VELOCITY',
423             )
424     show_flows = BoolProperty(
425             name="Render Fluid Objects",
426             description="Keep the fluid objects visible during rendering",
427             default=False,
428             )
429     start_baking = BoolProperty(
430             name="Start Fluid Bake",
431             description=("Start baking the fluid immediately "
432                          "after creating the domain object"),
433             default=False,
434             )
435
436     def execute(self, context):
437         fake_context = context.copy()
438         mesh_objects = [obj for obj in context.selected_objects
439                         if (obj.type == 'MESH' and 0.0 not in obj.dimensions)]
440         min_co = Vector((100000.0, 100000.0, 100000.0))
441         max_co = -min_co
442
443         if not mesh_objects:
444             self.report({'ERROR'}, "Select at least one mesh object")
445             return {'CANCELLED'}
446
447         for obj in mesh_objects:
448             fake_context["object"] = obj
449             # make each selected object a fluid
450             bpy.ops.object.modifier_add(fake_context, type='FLUID_SIMULATION')
451
452             # fluid has to be before constructive modifiers,
453             # so it might not be the last modifier
454             for mod in obj.modifiers:
455                 if mod.type == 'FLUID_SIMULATION':
456                     break
457
458             if self.style == 'INFLOW':
459                 mod.settings.type = 'INFLOW'
460                 mod.settings.inflow_velocity = self.initial_velocity
461             else:
462                 mod.settings.type = 'FLUID'
463                 mod.settings.initial_velocity = self.initial_velocity
464
465             obj.hide_render = not self.show_flows
466             if not self.show_flows:
467                 obj.draw_type = 'WIRE'
468
469             # store bounding box min/max for the domain object
470             obj_bb_minmax(obj, min_co, max_co)
471
472         # add the fluid domain object
473         bpy.ops.mesh.primitive_cube_add()
474         obj = context.active_object
475         obj.name = "Fluid Domain"
476
477         # give the fluid some room below the flows
478         # and scale with initial velocity
479         v = 0.5 * self.initial_velocity
480         obj.location = 0.5 * (max_co + min_co) + Vector((0.0, 0.0, -1.0)) + v
481         obj.scale = (0.5 * (max_co - min_co) +
482                      Vector((1.0, 1.0, 2.0)) +
483                      Vector((abs(v[0]), abs(v[1]), abs(v[2])))
484                      )
485
486         # setup smoke domain
487         bpy.ops.object.modifier_add(type='FLUID_SIMULATION')
488         obj.modifiers[-1].settings.type = 'DOMAIN'
489
490         # make the domain smooth so it renders nicely
491         bpy.ops.object.shade_smooth()
492
493         # create a ray-transparent material for the domain
494         bpy.ops.object.material_slot_add()
495
496         mat = bpy.data.materials.new("Fluid Domain Material")
497         obj.material_slots[0].material = mat
498
499         mat.specular_intensity = 1
500         mat.specular_hardness = 100
501         mat.use_transparency = True
502         mat.alpha = 0.0
503         mat.transparency_method = 'RAYTRACE'
504         mat.raytrace_transparency.ior = 1.33
505         mat.raytrace_transparency.depth = 4
506
507         if self.start_baking:
508             bpy.ops.fluid.bake('INVOKE_DEFAULT')
509
510         return {'FINISHED'}