48b547980d4b6bd8dbffff4801532ae37891fd90
[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(
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 = bpy.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             obj.particle_systems[-1].settings.material = \
109                     len(obj.data.materials)
110
111         return {'FINISHED'}
112
113
114 class QuickExplode(Operator):
115     bl_idname = "object.quick_explode"
116     bl_label = "Quick Explode"
117     bl_options = {'REGISTER', 'UNDO'}
118
119     style = EnumProperty(
120             name="Explode Style",
121             items=(('EXPLODE', "Explode", ""),
122                    ('BLEND', "Blend", "")),
123             default='EXPLODE',
124             )
125     amount = IntProperty(
126             name="Amount of pieces",
127             min=2, max=10000,
128             soft_min=2, soft_max=10000,
129             default=100,
130             )
131     frame_duration = IntProperty(
132             name="Duration",
133             min=1, max=300000,
134             soft_min=1, soft_max=10000,
135             default=50,
136             )
137
138     frame_start = IntProperty(
139             name="Start Frame",
140             min=1, max=300000,
141             soft_min=1, soft_max=10000,
142             default=1,
143             )
144     frame_end = IntProperty(
145             name="End Frame",
146             min=1, max=300000,
147             soft_min=1, soft_max=10000,
148             default=10,
149             )
150
151     velocity = FloatProperty(
152             name="Outwards Velocity",
153             min=0, max=300000,
154             soft_min=0, soft_max=10,
155             default=1,
156             )
157
158     fade = BoolProperty(
159             name="Fade",
160             description="Fade the pieces over time.",
161             default=True,
162             )
163
164     def execute(self, context):
165         fake_context = bpy.context.copy()
166         obj_act = context.active_object
167
168         if obj_act is None or obj_act.type != 'MESH':
169             self.report({'ERROR'}, "Active object is not a mesh")
170             return {'CANCELLED'}
171
172         mesh_objects = [obj for obj in context.selected_objects
173                         if obj.type == 'MESH' and obj != obj_act]
174         mesh_objects.insert(0, obj_act)
175
176         if self.style == 'BLEND' and len(mesh_objects) != 2:
177             self.report({'ERROR'}, "Select two mesh objects")
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                         elem.color = mat.diffuse_color
246                     else:
247                         elem = tex.color_ramp.elements[0]
248                         elem.color = 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                    ('PUFF', "Puff", ""),
303                    ('FIRE', "Fire", ""),
304                    ),
305             default='STREAM',
306             )
307
308     show_flows = BoolProperty(
309             name="Render Smoke Objects",
310             description="Keep the smoke objects visible during rendering.",
311             default=False,
312             )
313
314     def execute(self, context):
315         fake_context = bpy.context.copy()
316         mesh_objects = [obj for obj in context.selected_objects
317                         if obj.type == 'MESH']
318         min_co = Vector((100000.0, 100000.0, 100000.0))
319         max_co = -min_co
320
321         if not mesh_objects:
322             self.report({'ERROR'}, "Select at least one mesh object.")
323             return {'CANCELLED'}
324
325         for obj in mesh_objects:
326             fake_context["object"] = obj
327             # make each selected object a smoke flow
328             bpy.ops.object.modifier_add(fake_context, type='SMOKE')
329             obj.modifiers[-1].smoke_type = 'FLOW'
330
331             psys = obj.particle_systems[-1]
332             if self.style == 'PUFF':
333                 psys.settings.frame_end = psys.settings.frame_start
334                 psys.settings.emit_from = 'VOLUME'
335                 psys.settings.distribution = 'RAND'
336             elif self.style == 'FIRE':
337                 psys.settings.effector_weights.gravity = -1
338                 psys.settings.lifetime = 5
339                 psys.settings.count = 100000
340
341                 obj.modifiers[-2].flow_settings.initial_velocity = True
342                 obj.modifiers[-2].flow_settings.temperature = 2
343
344             psys.settings.use_render_emitter = self.show_flows
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':
364             obj.modifiers[-1].domain_settings.use_dissolve_smoke = True
365             obj.modifiers[-1].domain_settings.dissolve_speed = 20
366             obj.modifiers[-1].domain_settings.use_high_resolution = True
367
368         # create a volume material with a voxel data texture for the domain
369         bpy.ops.object.material_slot_add()
370
371         mat = bpy.data.materials.new("Smoke Domain Material")
372         obj.material_slots[0].material = mat
373         mat.type = 'VOLUME'
374         mat.volume.density = 0
375         mat.volume.density_scale = 5
376
377         tex = bpy.data.textures.new("Smoke Density", 'VOXEL_DATA')
378         tex.voxel_data.domain_object = obj
379
380         tex_slot = mat.texture_slots.add()
381         tex_slot.texture = tex
382         tex_slot.use_map_color_emission = False
383         tex_slot.use_map_density = True
384
385         # for fire add a second texture for emission and emission color
386         if self.style == 'FIRE':
387             mat.volume.emission = 5
388             tex = bpy.data.textures.new("Smoke Heat", 'VOXEL_DATA')
389             tex.voxel_data.domain_object = obj
390             tex.use_color_ramp = True
391
392             tex_slot = mat.texture_slots.add()
393             tex_slot.texture = tex
394
395             ramp = tex.color_ramp
396
397             elem = ramp.elements.new(0.333)
398             elem.color[0] = elem.color[3] = 1
399             elem.color[1] = elem.color[2] = 0
400
401             elem = ramp.elements.new(0.666)
402             elem.color[0] = elem.color[1] = elem.color[3] = 1
403             elem.color[2] = 0
404
405             mat.texture_slots[1].use_map_emission = True
406             mat.texture_slots[1].blend_type = 'MULTIPLY'
407
408         return {'FINISHED'}
409
410
411 class QuickFluid(Operator):
412     bl_idname = "object.quick_fluid"
413     bl_label = "Quick Fluid"
414     bl_options = {'REGISTER', 'UNDO'}
415
416     style = EnumProperty(
417             name="Fluid Style",
418             items=(('INFLOW', "Inflow", ""),
419                    ('BASIC', "Basic", "")),
420             default='BASIC',
421             )
422     initial_velocity = FloatVectorProperty(
423             name="Initial Velocity",
424             description="Initial velocity of the fluid",
425             min=-100.0, max=100.0,
426             default=(0.0, 0.0, 0.0),
427             subtype='VELOCITY',
428             )
429     show_flows = BoolProperty(
430             name="Render Fluid Objects",
431             description="Keep the fluid objects visible during rendering.",
432             default=False,
433             )
434     start_baking = BoolProperty(
435             name="Start Fluid Bake",
436             description=("Start baking the fluid immediately "
437                          "after creating the domain object"),
438             default=False,
439             )
440
441     def execute(self, context):
442         fake_context = bpy.context.copy()
443         mesh_objects = [obj for obj in context.selected_objects
444                         if (obj.type == 'MESH' and not 0.0 in obj.dimensions)]
445         min_co = Vector((100000, 100000, 100000))
446         max_co = Vector((-100000, -100000, -100000))
447
448         if not mesh_objects:
449             self.report({'ERROR'}, "Select at least one mesh object.")
450             return {'CANCELLED'}
451
452         for obj in mesh_objects:
453             fake_context["object"] = obj
454             # make each selected object a fluid
455             bpy.ops.object.modifier_add(fake_context, type='FLUID_SIMULATION')
456
457             # fluid has to be before constructive modifiers,
458             # so it might not be the last modifier
459             for mod in obj.modifiers:
460                 if mod.type == 'FLUID_SIMULATION':
461                     break
462
463             if self.style == 'INFLOW':
464                 mod.settings.type = 'INFLOW'
465                 mod.settings.inflow_velocity = self.initial_velocity.copy()
466             else:
467                 mod.settings.type = 'FLUID'
468                 mod.settings.initial_velocity = self.initial_velocity.copy()
469
470             obj.hide_render = not self.show_flows
471             if not self.show_flows:
472                 obj.draw_type = 'WIRE'
473
474             # store bounding box min/max for the domain object
475             obj_bb_minmax(obj, min_co, max_co)
476
477         # add the fluid domain object
478         bpy.ops.mesh.primitive_cube_add()
479         obj = context.active_object
480         obj.name = "Fluid Domain"
481
482         # give the fluid some room below the flows
483         # and scale with initial velocity
484         v = 0.5 * self.initial_velocity
485         obj.location = 0.5 * (max_co + min_co) + Vector((0.0, 0.0, -1.0)) + v
486         obj.scale = (0.5 * (max_co - min_co) +
487                      Vector((1.0, 1.0, 2.0)) +
488                      Vector((abs(v[0]), abs(v[1]), abs(v[2])))
489                      )
490
491         # setup smoke domain
492         bpy.ops.object.modifier_add(type='FLUID_SIMULATION')
493         obj.modifiers[-1].settings.type = 'DOMAIN'
494
495         # make the domain smooth so it renders nicely
496         bpy.ops.object.shade_smooth()
497
498         # create a ray-transparent material for the domain
499         bpy.ops.object.material_slot_add()
500
501         mat = bpy.data.materials.new("Fluid Domain Material")
502         obj.material_slots[0].material = mat
503
504         mat.specular_intensity = 1
505         mat.specular_hardness = 100
506         mat.use_transparency = True
507         mat.alpha = 0.0
508         mat.transparency_method = 'RAYTRACE'
509         mat.raytrace_transparency.ior = 1.33
510         mat.raytrace_transparency.depth = 4
511
512         if self.start_baking:
513             bpy.ops.fluid.bake()
514
515         return {'FINISHED'}