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