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