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