2c329de36440fd4145a9d4962726e02104ea5952
[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-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 object's 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         # don't 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(
199             name="Level",
200             min=-100, max=100,
201             soft_min=-6, soft_max=6,
202             default=1,
203             )
204
205     relative = BoolProperty(
206             name="Relative",
207             description=("Apply the subsurf level as an offset "
208                          "relative to the current level"),
209             default=False,
210             )
211
212     @classmethod
213     def poll(cls, context):
214         obs = context.selected_editable_objects
215         return (obs is not None)
216
217     def execute(self, context):
218         level = self.level
219         relative = self.relative
220
221         if relative and level == 0:
222             return {'CANCELLED'}  # nothing to do
223
224         if not relative and level < 0:
225             self.level = level = 0
226
227         def set_object_subd(obj):
228             for mod in obj.modifiers:
229                 if mod.type == 'MULTIRES':
230                     if not relative:
231                         if level <= mod.total_levels:
232                             if obj.mode == 'SCULPT':
233                                 if mod.sculpt_levels != level:
234                                     mod.sculpt_levels = level
235                             elif obj.mode == 'OBJECT':
236                                 if mod.levels != level:
237                                     mod.levels = level
238                         return
239                     else:
240                         if obj.mode == 'SCULPT':
241                             if mod.sculpt_levels + level <= mod.total_levels:
242                                 mod.sculpt_levels += level
243                         elif obj.mode == 'OBJECT':
244                             if mod.levels + level <= mod.total_levels:
245                                 mod.levels += level
246                         return
247
248                 elif mod.type == 'SUBSURF':
249                     if relative:
250                         mod.levels += level
251                     else:
252                         if mod.levels != level:
253                             mod.levels = level
254
255                     return
256
257             # add a new modifier
258             try:
259                 mod = obj.modifiers.new("Subsurf", 'SUBSURF')
260                 mod.levels = level
261             except:
262                 self.report({'WARNING'},
263                             "Modifiers cannot be added to object: " + obj.name)
264
265         for obj in context.selected_editable_objects:
266             set_object_subd(obj)
267
268         return {'FINISHED'}
269
270
271 class ShapeTransfer(Operator):
272     '''Copy another selected objects active shape to this one by ''' \
273     '''applying the relative offsets'''
274
275     bl_idname = "object.shape_key_transfer"
276     bl_label = "Transfer Shape Key"
277     bl_options = {'REGISTER', 'UNDO'}
278
279     mode = EnumProperty(
280             items=(('OFFSET',
281                     "Offset",
282                     "Apply the relative positional offset",
283                     ),
284                    ('RELATIVE_FACE',
285                     "Relative Face",
286                     "Calculate relative position (using faces)",
287                     ),
288                    ('RELATIVE_EDGE',
289                    "Relative Edge",
290                    "Calculate relative position (using edges)",
291                    ),
292                    ),
293             name="Transformation Mode",
294             description="Relative shape positions to the new shape method",
295             default='OFFSET',
296             )
297     use_clamp = BoolProperty(
298             name="Clamp Offset",
299             description=("Clamp the transformation to the distance each "
300                          "vertex moves in the original shape"),
301             default=False,
302             )
303
304     def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False):
305
306         def me_nos(verts):
307             return [v.normal.copy() for v in verts]
308
309         def me_cos(verts):
310             return [v.co.copy() for v in verts]
311
312         def ob_add_shape(ob, name):
313             me = ob.data
314             key = ob.shape_key_add(from_mix=False)
315             if len(me.shape_keys.key_blocks) == 1:
316                 key.name = "Basis"
317                 key = ob.shape_key_add(from_mix=False)  # we need a rest
318             key.name = name
319             ob.active_shape_key_index = len(me.shape_keys.key_blocks) - 1
320             ob.show_only_shape_key = True
321
322         from mathutils.geometry import barycentric_transform
323         from mathutils import Vector
324
325         if use_clamp and mode == 'OFFSET':
326             use_clamp = False
327
328         me = ob_act.data
329         orig_key_name = ob_act.active_shape_key.name
330
331         orig_shape_coords = me_cos(ob_act.active_shape_key.data)
332
333         orig_normals = me_nos(me.vertices)
334         # the actual mverts location isn't as reliable as the base shape :S
335         # orig_coords = me_cos(me.vertices)
336         orig_coords = me_cos(me.shape_keys.key_blocks[0].data)
337
338         for ob_other in objects:
339             me_other = ob_other.data
340             if len(me_other.vertices) != len(me.vertices):
341                 self.report({'WARNING'},
342                             ("Skipping '%s', "
343                              "vertex count differs") % ob_other.name)
344                 continue
345
346             target_normals = me_nos(me_other.vertices)
347             if me_other.shape_keys:
348                 target_coords = me_cos(me_other.shape_keys.key_blocks[0].data)
349             else:
350                 target_coords = me_cos(me_other.vertices)
351
352             ob_add_shape(ob_other, orig_key_name)
353
354             # editing the final coords, only list that stores wrapped coords
355             target_shape_coords = [v.co for v in
356                                    ob_other.active_shape_key.data]
357
358             median_coords = [[] for i in range(len(me.vertices))]
359
360             # Method 1, edge
361             if mode == 'OFFSET':
362                 for i, vert_cos in enumerate(median_coords):
363                     vert_cos.append(target_coords[i] +
364                                     (orig_shape_coords[i] - orig_coords[i]))
365
366             elif mode == 'RELATIVE_FACE':
367                 for face in me.faces:
368                     i1, i2, i3, i4 = face.vertices_raw
369                     if i4 != 0:
370                         pt = barycentric_transform(orig_shape_coords[i1],
371                                                    orig_coords[i4],
372                                                    orig_coords[i1],
373                                                    orig_coords[i2],
374                                                    target_coords[i4],
375                                                    target_coords[i1],
376                                                    target_coords[i2],
377                                                    )
378                         median_coords[i1].append(pt)
379
380                         pt = barycentric_transform(orig_shape_coords[i2],
381                                                    orig_coords[i1],
382                                                    orig_coords[i2],
383                                                    orig_coords[i3],
384                                                    target_coords[i1],
385                                                    target_coords[i2],
386                                                    target_coords[i3],
387                                                    )
388                         median_coords[i2].append(pt)
389
390                         pt = barycentric_transform(orig_shape_coords[i3],
391                                                    orig_coords[i2],
392                                                    orig_coords[i3],
393                                                    orig_coords[i4],
394                                                    target_coords[i2],
395                                                    target_coords[i3],
396                                                    target_coords[i4],
397                                                    )
398                         median_coords[i3].append(pt)
399
400                         pt = barycentric_transform(orig_shape_coords[i4],
401                                                    orig_coords[i3],
402                                                    orig_coords[i4],
403                                                    orig_coords[i1],
404                                                    target_coords[i3],
405                                                    target_coords[i4],
406                                                    target_coords[i1],
407                                                    )
408                         median_coords[i4].append(pt)
409
410                     else:
411                         pt = barycentric_transform(orig_shape_coords[i1],
412                                                    orig_coords[i3],
413                                                    orig_coords[i1],
414                                                    orig_coords[i2],
415                                                    target_coords[i3],
416                                                    target_coords[i1],
417                                                    target_coords[i2],
418                                                    )
419                         median_coords[i1].append(pt)
420
421                         pt = barycentric_transform(orig_shape_coords[i2],
422                                                    orig_coords[i1],
423                                                    orig_coords[i2],
424                                                    orig_coords[i3],
425                                                    target_coords[i1],
426                                                    target_coords[i2],
427                                                    target_coords[i3],
428                                                    )
429                         median_coords[i2].append(pt)
430
431                         pt = barycentric_transform(orig_shape_coords[i3],
432                                                    orig_coords[i2],
433                                                    orig_coords[i3],
434                                                    orig_coords[i1],
435                                                    target_coords[i2],
436                                                    target_coords[i3],
437                                                    target_coords[i1],
438                                                    )
439                         median_coords[i3].append(pt)
440
441             elif mode == 'RELATIVE_EDGE':
442                 for ed in me.edges:
443                     i1, i2 = ed.vertices
444                     v1, v2 = orig_coords[i1], orig_coords[i2]
445                     edge_length = (v1 - v2).length
446                     n1loc = v1 + orig_normals[i1] * edge_length
447                     n2loc = v2 + orig_normals[i2] * edge_length
448
449                     # now get the target nloc's
450                     v1_to, v2_to = target_coords[i1], target_coords[i2]
451                     edlen_to = (v1_to - v2_to).length
452                     n1loc_to = v1_to + target_normals[i1] * edlen_to
453                     n2loc_to = v2_to + target_normals[i2] * edlen_to
454
455                     pt = barycentric_transform(orig_shape_coords[i1],
456                         v2, v1, n1loc,
457                         v2_to, v1_to, n1loc_to)
458                     median_coords[i1].append(pt)
459
460                     pt = barycentric_transform(orig_shape_coords[i2],
461                         v1, v2, n2loc,
462                         v1_to, v2_to, n2loc_to)
463                     median_coords[i2].append(pt)
464
465             # apply the offsets to the new shape
466             from functools import reduce
467             VectorAdd = Vector.__add__
468
469             for i, vert_cos in enumerate(median_coords):
470                 if vert_cos:
471                     co = reduce(VectorAdd, vert_cos) / len(vert_cos)
472
473                     if use_clamp:
474                         # clamp to the same movement as the original
475                         # breaks copy between different scaled meshes.
476                         len_from = (orig_shape_coords[i] -
477                                     orig_coords[i]).length
478                         ofs = co - target_coords[i]
479                         ofs.length = len_from
480                         co = target_coords[i] + ofs
481
482                     target_shape_coords[i][:] = co
483
484         return {'FINISHED'}
485
486     @classmethod
487     def poll(cls, context):
488         obj = context.active_object
489         return (obj and obj.mode != 'EDIT')
490
491     def execute(self, context):
492         ob_act = context.active_object
493         objects = [ob for ob in C.selected_editable_objects if ob != ob_act]
494
495         if 1:  # swap from/to, means we cant copy to many at once.
496             if len(objects) != 1:
497                 self.report({'ERROR'},
498                             ("Expected one other selected "
499                              "mesh object to copy from"))
500
501                 return {'CANCELLED'}
502             ob_act, objects = objects[0], [ob_act]
503
504         if ob_act.type != 'MESH':
505             self.report({'ERROR'}, "Other object is not a mesh")
506             return {'CANCELLED'}
507
508         if ob_act.active_shape_key is None:
509             self.report({'ERROR'}, "Other object has no shape key")
510             return {'CANCELLED'}
511         return self._main(ob_act, objects, self.mode, self.use_clamp)
512
513
514 class JoinUVs(Operator):
515     '''Copy UV Layout to objects with matching geometry'''
516     bl_idname = "object.join_uvs"
517     bl_label = "Join as UVs"
518
519     @classmethod
520     def poll(cls, context):
521         obj = context.active_object
522         return (obj and obj.type == 'MESH')
523
524     def _main(self, context):
525         import array
526         obj = context.active_object
527         mesh = obj.data
528
529         is_editmode = (obj.mode == 'EDIT')
530         if is_editmode:
531             bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
532
533         if not mesh.uv_textures:
534             self.report({'WARNING'},
535                         "Object: %s, Mesh: '%s' has no UVs"
536                         % (obj.name, mesh.name))
537         else:
538             len_faces = len(mesh.faces)
539
540             # seems to be the fastest way to create an array
541             uv_array = array.array('f', [0.0] * 8) * len_faces
542             mesh.uv_textures.active.data.foreach_get("uv_raw", uv_array)
543
544             objects = context.selected_editable_objects[:]
545
546             for obj_other in objects:
547                 if obj_other.type == 'MESH':
548                     obj_other.data.tag = False
549
550             for obj_other in objects:
551                 if obj_other != obj and obj_other.type == 'MESH':
552                     mesh_other = obj_other.data
553                     if mesh_other != mesh:
554                         if mesh_other.tag == False:
555                             mesh_other.tag = True
556
557                             if len(mesh_other.faces) != len_faces:
558                                 self.report({'WARNING'}, "Object: %s, Mesh: "
559                                             "'%s' has %d faces, expected %d\n"
560                                             % (obj_other.name,
561                                                mesh_other.name,
562                                                len(mesh_other.faces),
563                                                len_faces),
564                                                )
565                             else:
566                                 uv_other = mesh_other.uv_textures.active
567                                 if not uv_other:
568                                     # should return the texture it adds
569                                     uv_other = mesh_other.uv_textures.new()
570
571                                 # finally do the copy
572                                 uv_other.data.foreach_set("uv_raw", uv_array)
573
574         if is_editmode:
575             bpy.ops.object.mode_set(mode='EDIT', toggle=False)
576
577     def execute(self, context):
578         self._main(context)
579         return {'FINISHED'}
580
581
582 class MakeDupliFace(Operator):
583     '''Make linked objects into dupli-faces'''
584     bl_idname = "object.make_dupli_face"
585     bl_label = "Make Dupli-Face"
586
587     def _main(self, context):
588         from mathutils import Vector
589
590         SCALE_FAC = 0.01
591         offset = 0.5 * SCALE_FAC
592         base_tri = (Vector((-offset, -offset, 0.0)),
593                     Vector((+offset, -offset, 0.0)),
594                     Vector((+offset, +offset, 0.0)),
595                     Vector((-offset, +offset, 0.0)),
596                     )
597
598         def matrix_to_quad(matrix):
599             # scale = matrix.median_scale
600             trans = matrix.to_translation()
601             rot = matrix.to_3x3()  # also contains scale
602
603             return [(rot * b) + trans for b in base_tri]
604         scene = context.scene
605         linked = {}
606         for obj in context.selected_objects:
607             data = obj.data
608             if data:
609                 linked.setdefault(data, []).append(obj)
610
611         for data, objects in linked.items():
612             face_verts = [axis for obj in objects
613                           for v in matrix_to_quad(obj.matrix_world)
614                           for axis in v]
615
616             faces = list(range(len(face_verts) // 3))
617
618             mesh = bpy.data.meshes.new(data.name + "_dupli")
619
620             mesh.vertices.add(len(face_verts) // 3)
621             mesh.faces.add(len(face_verts) // 12)
622
623             mesh.vertices.foreach_set("co", face_verts)
624             mesh.faces.foreach_set("vertices_raw", faces)
625             mesh.update()  # generates edge data
626
627             # pick an object to use
628             obj = objects[0]
629
630             ob_new = bpy.data.objects.new(mesh.name, mesh)
631             base = scene.objects.link(ob_new)
632             base.layers[:] = obj.layers
633
634             ob_inst = bpy.data.objects.new(data.name, data)
635             base = scene.objects.link(ob_inst)
636             base.layers[:] = obj.layers
637
638             for obj in objects:
639                 scene.objects.unlink(obj)
640
641             ob_new.dupli_type = 'FACES'
642             ob_inst.parent = ob_new
643             ob_new.use_dupli_faces_scale = True
644             ob_new.dupli_faces_scale = 1.0 / SCALE_FAC
645
646     def execute(self, context):
647         self._main(context)
648         return {'FINISHED'}
649
650
651 class IsolateTypeRender(Operator):
652     '''Hide unselected render objects of same type as active ''' \
653     '''by setting the hide render flag'''
654     bl_idname = "object.isolate_type_render"
655     bl_label = "Restrict Render Unselected"
656     bl_options = {'REGISTER', 'UNDO'}
657
658     def execute(self, context):
659         act_type = context.object.type
660
661         for obj in context.visible_objects:
662
663             if obj.select:
664                 obj.hide_render = False
665             else:
666                 if obj.type == act_type:
667                     obj.hide_render = True
668
669         return {'FINISHED'}
670
671
672 class ClearAllRestrictRender(Operator):
673     '''Reveal all render objects by setting the hide render flag'''
674     bl_idname = "object.hide_render_clear_all"
675     bl_label = "Clear All Restrict Render"
676     bl_options = {'REGISTER', 'UNDO'}
677
678     def execute(self, context):
679         for obj in context.scene.objects:
680             obj.hide_render = False
681         return {'FINISHED'}
682
683
684 class TransformsToDeltasAnim(Operator):
685     '''Convert object animation for normal transforms to delta transforms'''
686     bl_idname = "object.anim_transforms_to_deltas"
687     bl_label = "Animated Transforms to Deltas"
688     bl_options = {'REGISTER', 'UNDO'}
689
690     @classmethod
691     def poll(cls, context):
692         obs = context.selected_editable_objects
693         return (obs is not None)
694
695     def execute(self, context):
696         for obj in context.selected_editable_objects:
697             # get animation data
698             adt = obj.animation_data
699             if (adt is None) or (adt.action is None):
700                 self.report({'WARNING'},
701                             "No animation data to convert on object: %r" %
702                             obj.name)
703                 continue
704
705             # if F-Curve uses standard transform path
706             # just append "delta_" to this path
707             for fcu in adt.action.fcurves:
708                 if fcu.data_path == "location":
709                     fcu.data_path = "delta_location"
710                     obj.location.zero()
711                 elif fcu.data_path == "rotation_euler":
712                     fcu.data_path = "delta_rotation_euler"
713                     obj.rotation_euler.zero()
714                 elif fcu.data_path == "rotation_quaternion":
715                     fcu.data_path = "delta_rotation_quaternion"
716                     obj.rotation_quaternion.identity()
717                 # XXX: currently not implemented
718                 #~ elif fcu.data_path == "rotation_axis_angle":
719                 #~    fcu.data_path = "delta_rotation_axis_angle"
720                 elif fcu.data_path == "scale":
721                     fcu.data_path = "delta_scale"
722                     obj.scale = 1.0, 1.0, 1.0
723
724         # hack: force animsys flush by changing frame, so that deltas get run
725         context.scene.frame_set(context.scene.frame_current)
726
727         return {'FINISHED'}