/release/scripts: Removed final points in UI strings and messages.
[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 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(
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 isnt as relyable 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         C = bpy.context
493         ob_act = C.active_object
494         objects = [ob for ob in C.selected_editable_objects if ob != ob_act]
495
496         if 1:  # swap from/to, means we cant copy to many at once.
497             if len(objects) != 1:
498                 self.report({'ERROR'},
499                             ("Expected one other selected "
500                              "mesh object to copy from"))
501
502                 return {'CANCELLED'}
503             ob_act, objects = objects[0], [ob_act]
504
505         if ob_act.type != 'MESH':
506             self.report({'ERROR'}, "Other object is not a mesh")
507             return {'CANCELLED'}
508
509         if ob_act.active_shape_key is None:
510             self.report({'ERROR'}, "Other object has no shape key")
511             return {'CANCELLED'}
512         return self._main(ob_act, objects, self.mode, self.use_clamp)
513
514
515 class JoinUVs(Operator):
516     '''Copy UV Layout to objects with matching geometry'''
517     bl_idname = "object.join_uvs"
518     bl_label = "Join as UVs"
519
520     @classmethod
521     def poll(cls, context):
522         obj = context.active_object
523         return (obj and obj.type == 'MESH')
524
525     def _main(self, context):
526         import array
527         obj = context.active_object
528         mesh = obj.data
529
530         is_editmode = (obj.mode == 'EDIT')
531         if is_editmode:
532             bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
533
534         if not mesh.uv_textures:
535             self.report({'WARNING'},
536                         "Object: %s, Mesh: '%s' has no UVs"
537                         % (obj.name, mesh.name))
538         else:
539             len_faces = len(mesh.faces)
540
541             # seems to be the fastest way to create an array
542             uv_array = array.array('f', [0.0] * 8) * len_faces
543             mesh.uv_textures.active.data.foreach_get("uv_raw", uv_array)
544
545             objects = context.selected_editable_objects[:]
546
547             for obj_other in objects:
548                 if obj_other.type == 'MESH':
549                     obj_other.data.tag = False
550
551             for obj_other in objects:
552                 if obj_other != obj and obj_other.type == 'MESH':
553                     mesh_other = obj_other.data
554                     if mesh_other != mesh:
555                         if mesh_other.tag == False:
556                             mesh_other.tag = True
557
558                             if len(mesh_other.faces) != len_faces:
559                                 self.report({'WARNING'}, "Object: %s, Mesh: "
560                                             "'%s' has %d faces, expected %d\n"
561                                             % (obj_other.name,
562                                                mesh_other.name,
563                                                len(mesh_other.faces),
564                                                len_faces),
565                                                )
566                             else:
567                                 uv_other = mesh_other.uv_textures.active
568                                 if not uv_other:
569                                     # should return the texture it adds
570                                     uv_other = mesh_other.uv_textures.new()
571
572                                 # finally do the copy
573                                 uv_other.data.foreach_set("uv_raw", uv_array)
574
575         if is_editmode:
576             bpy.ops.object.mode_set(mode='EDIT', toggle=False)
577
578     def execute(self, context):
579         self._main(context)
580         return {'FINISHED'}
581
582
583 class MakeDupliFace(Operator):
584     '''Make linked objects into dupli-faces'''
585     bl_idname = "object.make_dupli_face"
586     bl_label = "Make Dupli-Face"
587
588     @classmethod
589     def poll(cls, context):
590         obj = context.active_object
591         return (obj and obj.type == 'MESH')
592
593     def _main(self, context):
594         from mathutils import Vector
595
596         SCALE_FAC = 0.01
597         offset = 0.5 * SCALE_FAC
598         base_tri = (Vector((-offset, -offset, 0.0)),
599                     Vector((+offset, -offset, 0.0)),
600                     Vector((+offset, +offset, 0.0)),
601                     Vector((-offset, +offset, 0.0)),
602                     )
603
604         def matrix_to_quat(matrix):
605             # scale = matrix.median_scale
606             trans = matrix.to_translation()
607             rot = matrix.to_3x3()  # also contains scale
608
609             return [(rot * b) + trans for b in base_tri]
610         scene = bpy.context.scene
611         linked = {}
612         for obj in bpy.context.selected_objects:
613             data = obj.data
614             if data:
615                 linked.setdefault(data, []).append(obj)
616
617         for data, objects in linked.items():
618             face_verts = [axis for obj in objects
619                           for v in matrix_to_quat(obj.matrix_world)
620                           for axis in v]
621
622             faces = list(range(len(face_verts) // 3))
623
624             mesh = bpy.data.meshes.new(data.name + "_dupli")
625
626             mesh.vertices.add(len(face_verts) // 3)
627             mesh.faces.add(len(face_verts) // 12)
628
629             mesh.vertices.foreach_set("co", face_verts)
630             mesh.faces.foreach_set("vertices_raw", faces)
631             mesh.update()  # generates edge data
632
633             # pick an object to use
634             obj = objects[0]
635
636             ob_new = bpy.data.objects.new(mesh.name, mesh)
637             base = scene.objects.link(ob_new)
638             base.layers[:] = obj.layers
639
640             ob_inst = bpy.data.objects.new(data.name, data)
641             base = scene.objects.link(ob_inst)
642             base.layers[:] = obj.layers
643
644             for obj in objects:
645                 scene.objects.unlink(obj)
646
647             ob_new.dupli_type = 'FACES'
648             ob_inst.parent = ob_new
649             ob_new.use_dupli_faces_scale = True
650             ob_new.dupli_faces_scale = 1.0 / SCALE_FAC
651
652     def execute(self, context):
653         self._main(context)
654         return {'FINISHED'}
655
656
657 class IsolateTypeRender(Operator):
658     '''Hide unselected render objects of same type as active ''' \
659     '''by setting the hide render flag'''
660     bl_idname = "object.isolate_type_render"
661     bl_label = "Restrict Render Unselected"
662     bl_options = {'REGISTER', 'UNDO'}
663
664     def execute(self, context):
665         act_type = context.object.type
666
667         for obj in context.visible_objects:
668
669             if obj.select:
670                 obj.hide_render = False
671             else:
672                 if obj.type == act_type:
673                     obj.hide_render = True
674
675         return {'FINISHED'}
676
677
678 class ClearAllRestrictRender(Operator):
679     '''Reveal all render objects by setting the hide render flag'''
680     bl_idname = "object.hide_render_clear_all"
681     bl_label = "Clear All Restrict Render"
682     bl_options = {'REGISTER', 'UNDO'}
683
684     def execute(self, context):
685         for obj in context.scene.objects:
686             obj.hide_render = False
687         return {'FINISHED'}
688
689
690 class TransformsToDeltasAnim(Operator):
691     '''Convert object animation for normal transforms to delta transforms'''
692     bl_idname = "object.anim_transforms_to_deltas"
693     bl_label = "Animated Transforms to Deltas"
694     bl_options = {'REGISTER', 'UNDO'}
695
696     @classmethod
697     def poll(cls, context):
698         obs = context.selected_editable_objects
699         return (obs is not None)
700
701     def execute(self, context):
702         for obj in context.selected_editable_objects:
703             # get animation data
704             adt = obj.animation_data
705             if (adt is None) or (adt.action is None):
706                 self.report({'WARNING'},
707                             "No animation data to convert on object: %r" %
708                             obj.name)
709                 continue
710
711             # if F-Curve uses standard transform path
712             # just append "delta_" to this path
713             for fcu in adt.action.fcurves:
714                 if fcu.data_path == "location":
715                     fcu.data_path = "delta_location"
716                     obj.location.zero()
717                 elif fcu.data_path == "rotation_euler":
718                     fcu.data_path = "delta_rotation_euler"
719                     obj.rotation_euler.zero()
720                 elif fcu.data_path == "rotation_quaternion":
721                     fcu.data_path = "delta_rotation_quaternion"
722                     obj.rotation_quaternion.identity()
723                 # XXX: currently not implemented
724                 # elif fcu.data_path == "rotation_axis_angle":
725                 #    fcu.data_path = "delta_rotation_axis_angle"
726                 elif fcu.data_path == "scale":
727                     fcu.data_path = "delta_scale"
728                     obj.scale = 1.0, 1.0, 1.0
729
730         # hack: force animsys flush by changing frame, so that deltas get run
731         context.scene.frame_set(context.scene.frame_current)
732
733         return {'FINISHED'}