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