Merging with trunk up to r38631.
[blender.git] / release / scripts / startup / bl_operators / object.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 import bpy
22 from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty
23
24
25 class SelectPattern(bpy.types.Operator):
26     '''Select object matching a naming pattern'''
27     bl_idname = "object.select_pattern"
28     bl_label = "Select Pattern"
29     bl_options = {'REGISTER', 'UNDO'}
30
31     pattern = StringProperty(name="Pattern", description="Name filter using '*' and '?' wildcard chars", maxlen=32, default="*")
32     case_sensitive = BoolProperty(name="Case Sensitive", description="Do a case sensitive compare", default=False)
33     extend = BoolProperty(name="Extend", description="Extend the existing selection", default=True)
34
35     def execute(self, context):
36
37         import fnmatch
38
39         if self.case_sensitive:
40             pattern_match = fnmatch.fnmatchcase
41         else:
42             pattern_match = lambda a, b: fnmatch.fnmatchcase(a.upper(), b.upper())
43
44         obj = context.object
45         if obj and obj.mode == 'POSE':
46             items = obj.data.bones
47         elif obj and obj.type == 'ARMATURE' and obj.mode == 'EDIT':
48             items = obj.data.edit_bones
49         else:
50             items = context.visible_objects
51
52         # Can be pose bones or objects
53         for item in items:
54             if pattern_match(item.name, self.pattern):
55                 item.select = True
56             elif not self.extend:
57                 item.select = False
58
59         return {'FINISHED'}
60
61     def invoke(self, context, event):
62         wm = context.window_manager
63         return wm.invoke_props_popup(self, event)
64
65     def draw(self, context):
66         layout = self.layout
67
68         layout.prop(self, "pattern")
69         row = layout.row()
70         row.prop(self, "case_sensitive")
71         row.prop(self, "extend")
72
73
74 class SelectCamera(bpy.types.Operator):
75     '''Select object matching a naming pattern'''
76     bl_idname = "object.select_camera"
77     bl_label = "Select Camera"
78     bl_options = {'REGISTER', 'UNDO'}
79
80     @classmethod
81     def poll(cls, context):
82         return context.scene.camera is not None
83
84     def execute(self, context):
85         scene = context.scene
86         camera = scene.camera
87         if camera.name not in scene.objects:
88             self.report({'WARNING'}, "Active camera is not in this scene")
89
90         context.scene.objects.active = camera
91         camera.select = True
92         return {'FINISHED'}
93
94
95 class SelectHierarchy(bpy.types.Operator):
96     '''Select object relative to the active objects position in the hierarchy'''
97     bl_idname = "object.select_hierarchy"
98     bl_label = "Select Hierarchy"
99     bl_options = {'REGISTER', 'UNDO'}
100
101     direction = EnumProperty(items=(
102                         ('PARENT', "Parent", ""),
103                         ('CHILD', "Child", "")),
104                 name="Direction",
105                 description="Direction to select in the hierarchy",
106                 default='PARENT')
107
108     extend = BoolProperty(name="Extend", description="Extend the existing selection", default=False)
109
110     @classmethod
111     def poll(cls, context):
112         return context.object
113
114     def execute(self, context):
115         select_new = []
116         act_new = None
117
118         selected_objects = context.selected_objects
119         obj_act = context.object
120
121         if context.object not in selected_objects:
122             selected_objects.append(context.object)
123
124         if self.direction == 'PARENT':
125             for obj in selected_objects:
126                 parent = obj.parent
127
128                 if parent:
129                     if obj_act == obj:
130                         act_new = parent
131
132                     select_new.append(parent)
133
134         else:
135             for obj in selected_objects:
136                 select_new.extend(obj.children)
137
138             if select_new:
139                 select_new.sort(key=lambda obj_iter: obj_iter.name)
140                 act_new = select_new[0]
141
142         # dont edit any object settings above this
143         if select_new:
144             if not self.extend:
145                 bpy.ops.object.select_all(action='DESELECT')
146
147             for obj in select_new:
148                 obj.select = True
149
150             context.scene.objects.active = act_new
151             return {'FINISHED'}
152
153         return {'CANCELLED'}
154
155
156 class SubdivisionSet(bpy.types.Operator):
157     '''Sets a Subdivision Surface Level (1-5)'''
158
159     bl_idname = "object.subdivision_set"
160     bl_label = "Subdivision Set"
161     bl_options = {'REGISTER', 'UNDO'}
162
163     level = IntProperty(name="Level",
164             default=1, min=-100, max=100, soft_min=-6, soft_max=6)
165
166     relative = BoolProperty(name="Relative", description="Apply the subsurf level as an offset relative to the current level", default=False)
167
168     @classmethod
169     def poll(cls, context):
170         obs = context.selected_editable_objects
171         return (obs is not None)
172
173     def execute(self, context):
174         level = self.level
175         relative = self.relative
176
177         if relative and level == 0:
178             return {'CANCELLED'}  # nothing to do
179
180         if not relative and level < 0:
181             self.level = level = 0
182
183         def set_object_subd(obj):
184             for mod in obj.modifiers:
185                 if mod.type == 'MULTIRES':
186                     if not relative:
187                         if level <= mod.total_levels:
188                             if obj.mode == 'SCULPT':
189                                 if mod.sculpt_levels != level:
190                                     mod.sculpt_levels = level
191                             elif obj.mode == 'OBJECT':
192                                 if mod.levels != level:
193                                     mod.levels = level
194                         return
195                     else:
196                         if obj.mode == 'SCULPT':
197                             if mod.sculpt_levels + level <= mod.total_levels:
198                                 mod.sculpt_levels += level
199                         elif obj.mode == 'OBJECT':
200                             if mod.levels + level <= mod.total_levels:
201                                 mod.levels += level
202                         return
203
204                 elif mod.type == 'SUBSURF':
205                     if relative:
206                         mod.levels += level
207                     else:
208                         if mod.levels != level:
209                             mod.levels = level
210
211                     return
212
213             # add a new modifier
214             try:
215                 mod = obj.modifiers.new("Subsurf", 'SUBSURF')
216                 mod.levels = level
217             except:
218                 self.report({'WARNING'}, "Modifiers cannot be added to object: " + obj.name)
219
220         for obj in context.selected_editable_objects:
221             set_object_subd(obj)
222
223         return {'FINISHED'}
224
225
226 class ShapeTransfer(bpy.types.Operator):
227     '''Copy another selected objects active shape to this one by applying the relative offsets'''
228
229     bl_idname = "object.shape_key_transfer"
230     bl_label = "Transfer Shape Key"
231     bl_options = {'REGISTER', 'UNDO'}
232
233     mode = EnumProperty(items=(
234                         ('OFFSET', "Offset", "Apply the relative positional offset"),
235                         ('RELATIVE_FACE', "Relative Face", "Calculate the geometricly relative position (using faces)."),
236                         ('RELATIVE_EDGE', "Relative Edge", "Calculate the geometricly relative position (using edges).")),
237                 name="Transformation Mode",
238                 description="Method to apply relative shape positions to the new shape",
239                 default='OFFSET')
240
241     use_clamp = BoolProperty(name="Clamp Offset",
242                 description="Clamp the transformation to the distance each vertex moves in the original shape.",
243                 default=False)
244
245     def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False):
246
247         def me_nos(verts):
248             return [v.normal.copy() for v in verts]
249
250         def me_cos(verts):
251             return [v.co.copy() for v in verts]
252
253         def ob_add_shape(ob, name):
254             me = ob.data
255             key = ob.shape_key_add(from_mix=False)
256             if len(me.shape_keys.key_blocks) == 1:
257                 key.name = "Basis"
258                 key = ob.shape_key_add(from_mix=False)  # we need a rest
259             key.name = name
260             ob.active_shape_key_index = len(me.shape_keys.key_blocks) - 1
261             ob.show_only_shape_key = True
262
263         from mathutils.geometry import barycentric_transform
264         from mathutils import Vector
265
266         if use_clamp and mode == 'OFFSET':
267             use_clamp = False
268
269         me = ob_act.data
270         orig_key_name = ob_act.active_shape_key.name
271
272         orig_shape_coords = me_cos(ob_act.active_shape_key.data)
273
274         orig_normals = me_nos(me.vertices)
275         # orig_coords = me_cos(me.vertices) # the actual mverts location isnt as relyable as the base shape :S
276         orig_coords = me_cos(me.shape_keys.key_blocks[0].data)
277
278         for ob_other in objects:
279             me_other = ob_other.data
280             if len(me_other.vertices) != len(me.vertices):
281                 self.report({'WARNING'}, "Skipping '%s', vertex count differs" % ob_other.name)
282                 continue
283
284             target_normals = me_nos(me_other.vertices)
285             if me_other.shape_keys:
286                 target_coords = me_cos(me_other.shape_keys.key_blocks[0].data)
287             else:
288                 target_coords = me_cos(me_other.vertices)
289
290             ob_add_shape(ob_other, orig_key_name)
291
292             # editing the final coords, only list that stores wrapped coords
293             target_shape_coords = [v.co for v in ob_other.active_shape_key.data]
294
295             median_coords = [[] for i in range(len(me.vertices))]
296
297             # Method 1, edge
298             if mode == 'OFFSET':
299                 for i, vert_cos in enumerate(median_coords):
300                     vert_cos.append(target_coords[i] + (orig_shape_coords[i] - orig_coords[i]))
301
302             elif mode == 'RELATIVE_FACE':
303                 for face in me.faces:
304                     i1, i2, i3, i4 = face.vertices_raw
305                     if i4 != 0:
306                         pt = barycentric_transform(orig_shape_coords[i1],
307                             orig_coords[i4], orig_coords[i1], orig_coords[i2],
308                             target_coords[i4], target_coords[i1], target_coords[i2])
309                         median_coords[i1].append(pt)
310
311                         pt = barycentric_transform(orig_shape_coords[i2],
312                             orig_coords[i1], orig_coords[i2], orig_coords[i3],
313                             target_coords[i1], target_coords[i2], target_coords[i3])
314                         median_coords[i2].append(pt)
315
316                         pt = barycentric_transform(orig_shape_coords[i3],
317                             orig_coords[i2], orig_coords[i3], orig_coords[i4],
318                             target_coords[i2], target_coords[i3], target_coords[i4])
319                         median_coords[i3].append(pt)
320
321                         pt = barycentric_transform(orig_shape_coords[i4],
322                             orig_coords[i3], orig_coords[i4], orig_coords[i1],
323                             target_coords[i3], target_coords[i4], target_coords[i1])
324                         median_coords[i4].append(pt)
325
326                     else:
327                         pt = barycentric_transform(orig_shape_coords[i1],
328                             orig_coords[i3], orig_coords[i1], orig_coords[i2],
329                             target_coords[i3], target_coords[i1], target_coords[i2])
330                         median_coords[i1].append(pt)
331
332                         pt = barycentric_transform(orig_shape_coords[i2],
333                             orig_coords[i1], orig_coords[i2], orig_coords[i3],
334                             target_coords[i1], target_coords[i2], target_coords[i3])
335                         median_coords[i2].append(pt)
336
337                         pt = barycentric_transform(orig_shape_coords[i3],
338                             orig_coords[i2], orig_coords[i3], orig_coords[i1],
339                             target_coords[i2], target_coords[i3], target_coords[i1])
340                         median_coords[i3].append(pt)
341
342             elif mode == 'RELATIVE_EDGE':
343                 for ed in me.edges:
344                     i1, i2 = ed.vertices
345                     v1, v2 = orig_coords[i1], orig_coords[i2]
346                     edge_length = (v1 - v2).length
347                     n1loc = v1 + orig_normals[i1] * edge_length
348                     n2loc = v2 + orig_normals[i2] * edge_length
349
350                     # now get the target nloc's
351                     v1_to, v2_to = target_coords[i1], target_coords[i2]
352                     edlen_to = (v1_to - v2_to).length
353                     n1loc_to = v1_to + target_normals[i1] * edlen_to
354                     n2loc_to = v2_to + target_normals[i2] * edlen_to
355
356                     pt = barycentric_transform(orig_shape_coords[i1],
357                         v2, v1, n1loc,
358                         v2_to, v1_to, n1loc_to)
359                     median_coords[i1].append(pt)
360
361                     pt = barycentric_transform(orig_shape_coords[i2],
362                         v1, v2, n2loc,
363                         v1_to, v2_to, n2loc_to)
364                     median_coords[i2].append(pt)
365
366             # apply the offsets to the new shape
367             from functools import reduce
368             VectorAdd = Vector.__add__
369
370             for i, vert_cos in enumerate(median_coords):
371                 if vert_cos:
372                     co = reduce(VectorAdd, vert_cos) / len(vert_cos)
373
374                     if use_clamp:
375                         # clamp to the same movement as the original
376                         # breaks copy between different scaled meshes.
377                         len_from = (orig_shape_coords[i] - orig_coords[i]).length
378                         ofs = co - target_coords[i]
379                         ofs.length = len_from
380                         co = target_coords[i] + ofs
381
382                     target_shape_coords[i][:] = co
383
384         return {'FINISHED'}
385
386     @classmethod
387     def poll(cls, context):
388         obj = context.active_object
389         return (obj and obj.mode != 'EDIT')
390
391     def execute(self, context):
392         C = bpy.context
393         ob_act = C.active_object
394         objects = [ob for ob in C.selected_editable_objects if ob != ob_act]
395
396         if 1:  # swap from/to, means we cant copy to many at once.
397             if len(objects) != 1:
398                 self.report({'ERROR'}, "Expected one other selected mesh object to copy from")
399                 return {'CANCELLED'}
400             ob_act, objects = objects[0], [ob_act]
401
402         if ob_act.type != 'MESH':
403             self.report({'ERROR'}, "Other object is not a mesh.")
404             return {'CANCELLED'}
405
406         if ob_act.active_shape_key is None:
407             self.report({'ERROR'}, "Other object has no shape key")
408             return {'CANCELLED'}
409         return self._main(ob_act, objects, self.mode, self.use_clamp)
410
411
412 class JoinUVs(bpy.types.Operator):
413     '''Copy UV Layout to objects with matching geometry'''
414     bl_idname = "object.join_uvs"
415     bl_label = "Join as UVs"
416
417     @classmethod
418     def poll(cls, context):
419         obj = context.active_object
420         return (obj and obj.type == 'MESH')
421
422     def _main(self, context):
423         import array
424         obj = context.active_object
425         mesh = obj.data
426
427         is_editmode = (obj.mode == 'EDIT')
428         if is_editmode:
429             bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
430
431         if not mesh.uv_textures:
432             self.report({'WARNING'}, "Object: %s, Mesh: '%s' has no UVs\n" % (obj.name, mesh.name))
433         else:
434             len_faces = len(mesh.faces)
435
436             uv_array = array.array('f', [0.0] * 8) * len_faces  # seems to be the fastest way to create an array
437             mesh.uv_textures.active.data.foreach_get("uv_raw", uv_array)
438
439             objects = context.selected_editable_objects[:]
440
441             for obj_other in objects:
442                 if obj_other.type == 'MESH':
443                     obj_other.data.tag = False
444
445             for obj_other in objects:
446                 if obj_other != obj and obj_other.type == 'MESH':
447                     mesh_other = obj_other.data
448                     if mesh_other != mesh:
449                         if mesh_other.tag == False:
450                             mesh_other.tag = True
451
452                             if len(mesh_other.faces) != len_faces:
453                                 self.report({'WARNING'}, "Object: %s, Mesh: '%s' has %d faces, expected %d\n" % (obj_other.name, mesh_other.name, len(mesh_other.faces), len_faces))
454                             else:
455                                 uv_other = mesh_other.uv_textures.active
456                                 if not uv_other:
457                                     uv_other = mesh_other.uv_textures.new()  # should return the texture it adds
458
459                                 # finally do the copy
460                                 uv_other.data.foreach_set("uv_raw", uv_array)
461
462         if is_editmode:
463             bpy.ops.object.mode_set(mode='EDIT', toggle=False)
464
465     def execute(self, context):
466         self._main(context)
467         return {'FINISHED'}
468
469
470 class MakeDupliFace(bpy.types.Operator):
471     '''Make linked objects into dupli-faces'''
472     bl_idname = "object.make_dupli_face"
473     bl_label = "Make Dupli-Face"
474
475     @classmethod
476     def poll(cls, context):
477         obj = context.active_object
478         return (obj and obj.type == 'MESH')
479
480     def _main(self, context):
481         from mathutils import Vector
482
483         SCALE_FAC = 0.01
484         offset = 0.5 * SCALE_FAC
485         base_tri = Vector((-offset, -offset, 0.0)), Vector((offset, -offset, 0.0)), Vector((offset, offset, 0.0)), Vector((-offset, offset, 0.0))
486
487         def matrix_to_quat(matrix):
488             # scale = matrix.median_scale
489             trans = matrix.to_translation()
490             rot = matrix.to_3x3()  # also contains scale
491
492             return [(b * rot) + trans for b in base_tri]
493         scene = bpy.context.scene
494         linked = {}
495         for obj in bpy.context.selected_objects:
496             data = obj.data
497             if data:
498                 linked.setdefault(data, []).append(obj)
499
500         for data, objects in linked.items():
501             face_verts = [axis for obj in objects for v in matrix_to_quat(obj.matrix_world) for axis in v]
502             faces = list(range(len(face_verts) // 3))
503
504             mesh = bpy.data.meshes.new(data.name + "_dupli")
505
506             mesh.vertices.add(len(face_verts) // 3)
507             mesh.faces.add(len(face_verts) // 12)
508
509             mesh.vertices.foreach_set("co", face_verts)
510             mesh.faces.foreach_set("vertices_raw", faces)
511             mesh.update()  # generates edge data
512
513             # pick an object to use
514             obj = objects[0]
515
516             ob_new = bpy.data.objects.new(mesh.name, mesh)
517             base = scene.objects.link(ob_new)
518             base.layers[:] = obj.layers
519
520             ob_inst = bpy.data.objects.new(data.name, data)
521             base = scene.objects.link(ob_inst)
522             base.layers[:] = obj.layers
523
524             for obj in objects:
525                 scene.objects.unlink(obj)
526
527             ob_new.dupli_type = 'FACES'
528             ob_inst.parent = ob_new
529             ob_new.use_dupli_faces_scale = True
530             ob_new.dupli_faces_scale = 1.0 / SCALE_FAC
531
532     def execute(self, context):
533         self._main(context)
534         return {'FINISHED'}
535
536
537 class IsolateTypeRender(bpy.types.Operator):
538     '''Hide unselected render objects of same type as active by setting the hide render flag'''
539     bl_idname = "object.isolate_type_render"
540     bl_label = "Restrict Render Unselected"
541     bl_options = {'REGISTER', 'UNDO'}
542
543     def execute(self, context):
544         act_type = context.object.type
545
546         for obj in context.visible_objects:
547
548             if obj.select:
549                 obj.hide_render = False
550             else:
551                 if obj.type == act_type:
552                     obj.hide_render = True
553
554         return {'FINISHED'}
555
556
557 class ClearAllRestrictRender(bpy.types.Operator):
558     '''Reveal all render objects by setting the hide render flag'''
559     bl_idname = "object.hide_render_clear_all"
560     bl_label = "Clear All Restrict Render"
561     bl_options = {'REGISTER', 'UNDO'}
562
563     def execute(self, context):
564         for obj in context.scene.objects:
565             obj.hide_render = False
566         return {'FINISHED'}
567
568 class TransformsToDeltasAnim(bpy.types.Operator):
569     '''Convert object animation for normal transforms to delta transforms'''
570     bl_idname = "object.anim_transforms_to_deltas"
571     bl_label = "Animated Transforms to Deltas"
572     bl_options = {'REGISTER', 'UNDO'}
573     
574     @classmethod
575     def poll(cls, context):
576         obs = context.selected_editable_objects
577         return (obs is not None)
578     
579     def execute(self, context):
580         for obj in context.selected_editable_objects:
581             # get animation data
582             adt = obj.animation_data
583             if (adt is None) or (adt.action is None):
584                 self.report({'WARNING'}, "No animation data to convert on object: " + obj.name)
585                 continue
586             
587             # if F-Curve uses standard transform path, just append "delta_" to this path
588             for fcu in adt.action.fcurves:
589                 if fcu.data_path == "location":
590                     fcu.data_path = "delta_location"
591                     obj.location.zero()
592                 elif fcu.data_path == "rotation_euler":
593                     fcu.data_path = "delta_rotation_euler"
594                     obj.rotation_euler.zero()
595                 elif fcu.data_path == "rotation_quaternion":
596                     fcu.data_path = "delta_rotation_quaternion"
597                     obj.rotation_quaternion.identity()
598                 #elif fcu.data_path == "rotation_axis_angle":  # XXX: currently not implemented 
599                 #   fcu.data_path = "delta_rotation_axis_angle"
600                 elif fcu.data_path == "scale":
601                     fcu.data_path = "delta_scale"
602                     obj.scale = (1, 1, 1)
603         
604         # hack: force animsys flush by changing frame, so that deltas get run
605         context.scene.frame_set(context.scene.frame_current)
606         
607         return {'FINISHED'}