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