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