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