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