svn merge ^/trunk/blender -r41200:41226 .
[blender.git] / release / scripts / modules / bpy_types.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 from _bpy import types as bpy_types
22 import _bpy
23
24 StructRNA = bpy_types.Struct.__bases__[0]
25 StructMetaPropGroup = _bpy.StructMetaPropGroup
26 # StructRNA = bpy_types.Struct
27
28 bpy_types.BlendDataLibraries.load = _bpy._library_load
29
30
31 class Context(StructRNA):
32     __slots__ = ()
33
34     def copy(self):
35         from types import BuiltinMethodType
36         new_context = {}
37         generic_attrs = (list(StructRNA.__dict__.keys()) +
38                          ["bl_rna", "rna_type", "copy"])
39         for attr in dir(self):
40             if not (attr.startswith("_") or attr in generic_attrs):
41                 value = getattr(self, attr)
42                 if type(value) != BuiltinMethodType:
43                     new_context[attr] = value
44
45         return new_context
46
47
48 class Library(bpy_types.ID):
49     __slots__ = ()
50
51     @property
52     def users_id(self):
53         """ID data blocks which use this library"""
54         import bpy
55
56         # See: readblenentry.c, IDTYPE_FLAGS_ISLINKABLE,
57         # we could make this an attribute in rna.
58         attr_links = ("actions", "armatures", "brushes", "cameras",
59                       "curves", "grease_pencil", "groups", "images",
60                       "lamps", "lattices", "materials", "metaballs",
61                       "meshes", "node_groups", "objects", "scenes",
62                       "sounds", "speakers", "textures", "texts",
63                       "fonts", "worlds")
64
65         return tuple(id_block
66                      for attr in attr_links
67                      for id_block in getattr(bpy.data, attr)
68                      if id_block.library == self)
69
70
71 class Texture(bpy_types.ID):
72     __slots__ = ()
73
74     @property
75     def users_material(self):
76         """Materials that use this texture"""
77         import bpy
78         return tuple(mat for mat in bpy.data.materials
79                      if self in [slot.texture
80                                  for slot in mat.texture_slots
81                                  if slot]
82                      )
83
84     @property
85     def users_object_modifier(self):
86         """Object modifiers that use this texture"""
87         import bpy
88         return tuple(obj for obj in bpy.data.objects if
89                      self in [mod.texture
90                               for mod in obj.modifiers
91                               if mod.type == 'DISPLACE']
92                      )
93
94
95 class Group(bpy_types.ID):
96     __slots__ = ()
97
98     @property
99     def users_dupli_group(self):
100         """The dupli group this group is used in"""
101         import bpy
102         return tuple(obj for obj in bpy.data.objects
103                      if self == obj.dupli_group)
104
105
106 class Object(bpy_types.ID):
107     __slots__ = ()
108
109     @property
110     def children(self):
111         """All the children of this object"""
112         import bpy
113         return tuple(child for child in bpy.data.objects
114                      if child.parent == self)
115
116     @property
117     def users_group(self):
118         """The groups this object is in"""
119         import bpy
120         return tuple(group for group in bpy.data.groups
121                      if self in group.objects[:])
122
123     @property
124     def users_scene(self):
125         """The scenes this object is in"""
126         import bpy
127         return tuple(scene for scene in bpy.data.scenes
128                      if self in scene.objects[:])
129
130
131 class _GenericBone:
132     """
133     functions for bones, common between Armature/Pose/Edit bones.
134     internal subclassing use only.
135     """
136     __slots__ = ()
137
138     def translate(self, vec):
139         """Utility function to add *vec* to the head and tail of this bone"""
140         self.head += vec
141         self.tail += vec
142
143     def parent_index(self, parent_test):
144         """
145         The same as 'bone in other_bone.parent_recursive'
146         but saved generating a list.
147         """
148         # use the name so different types can be tested.
149         name = parent_test.name
150
151         parent = self.parent
152         i = 1
153         while parent:
154             if parent.name == name:
155                 return i
156             parent = parent.parent
157             i += 1
158
159         return 0
160
161     @property
162     def x_axis(self):
163         """ Vector pointing down the x-axis of the bone.
164         """
165         from mathutils import Vector
166         return self.matrix.to_3x3() * Vector((1.0, 0.0, 0.0))
167
168     @property
169     def y_axis(self):
170         """ Vector pointing down the x-axis of the bone.
171         """
172         from mathutils import Vector
173         return self.matrix.to_3x3() * Vector((0.0, 1.0, 0.0))
174
175     @property
176     def z_axis(self):
177         """ Vector pointing down the x-axis of the bone.
178         """
179         from mathutils import Vector
180         return self.matrix.to_3x3() * Vector((0.0, 0.0, 1.0))
181
182     @property
183     def basename(self):
184         """The name of this bone before any '.' character"""
185         #return self.name.rsplit(".", 1)[0]
186         return self.name.split(".")[0]
187
188     @property
189     def parent_recursive(self):
190         """A list of parents, starting with the immediate parent"""
191         parent_list = []
192         parent = self.parent
193
194         while parent:
195             if parent:
196                 parent_list.append(parent)
197
198             parent = parent.parent
199
200         return parent_list
201
202     @property
203     def center(self):
204         """The midpoint between the head and the tail."""
205         return self.head.lerp(self.tail, 0.5)
206
207     @property
208     def length(self):
209         """ The distance from head to tail,
210             when set the head is moved to fit the length.
211         """
212         return self.vector.length
213
214     @length.setter
215     def length(self, value):
216         self.tail = self.head + ((self.tail - self.head).normalized() * value)
217
218     @property
219     def vector(self):
220         """ The direction this bone is pointing.
221             Utility function for (tail - head)
222         """
223         return (self.tail - self.head)
224
225     @property
226     def children(self):
227         """A list of all the bones children."""
228         return [child for child in self._other_bones if child.parent == self]
229
230     @property
231     def children_recursive(self):
232         """A list of all children from this bone."""
233         bones_children = []
234         for bone in self._other_bones:
235             index = bone.parent_index(self)
236             if index:
237                 bones_children.append((index, bone))
238
239         # sort by distance to parent
240         bones_children.sort(key=lambda bone_pair: bone_pair[0])
241         return [bone for index, bone in bones_children]
242
243     @property
244     def children_recursive_basename(self):
245         """
246         Returns a chain of children with the same base name as this bone.
247         Only direct chains are supported, forks caused by multiple children
248         with matching base names will terminate the function
249         and not be returned.
250         """
251         basename = self.basename
252         chain = []
253
254         child = self
255         while True:
256             children = child.children
257             children_basename = []
258
259             for child in children:
260                 if basename == child.basename:
261                     children_basename.append(child)
262
263             if len(children_basename) == 1:
264                 child = children_basename[0]
265                 chain.append(child)
266             else:
267                 if len(children_basename):
268                     print("multiple basenames found, "
269                           "this is probably not what you want!",
270                           self.name, children_basename)
271
272                 break
273
274         return chain
275
276     @property
277     def _other_bones(self):
278         id_data = self.id_data
279         id_data_type = type(id_data)
280
281         if id_data_type == bpy_types.Object:
282             bones = id_data.pose.bones
283         elif id_data_type == bpy_types.Armature:
284             bones = id_data.edit_bones
285             if not bones:  # not in edit mode
286                 bones = id_data.bones
287
288         return bones
289
290
291 class PoseBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup):
292     __slots__ = ()
293
294
295 class Bone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup):
296     __slots__ = ()
297
298
299 class EditBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup):
300     __slots__ = ()
301
302     def align_orientation(self, other):
303         """
304         Align this bone to another by moving its tail and settings its roll
305         the length of the other bone is not used.
306         """
307         vec = other.vector.normalize() * self.length
308         self.tail = self.head + vec
309         self.roll = other.roll
310
311     def transform(self, matrix, scale=True, roll=True):
312         """
313         Transform the the bones head, tail, roll and envelope
314         (when the matrix has a scale component).
315
316         :arg matrix: 3x3 or 4x4 transformation matrix.
317         :type matrix: :class:`mathutils.Matrix`
318         :arg scale: Scale the bone envelope by the matrix.
319         :type scale: bool
320         :arg roll:
321
322            Correct the roll to point in the same relative
323            direction to the head and tail.
324
325         :type roll: bool
326         """
327         from mathutils import Vector
328         z_vec = self.matrix.to_3x3() * Vector((0.0, 0.0, 1.0))
329         self.tail = matrix * self.tail
330         self.head = matrix * self.head
331
332         if scale:
333             scalar = matrix.median_scale
334             self.head_radius *= scalar
335             self.tail_radius *= scalar
336
337         if roll:
338             self.align_roll(matrix * z_vec)
339
340
341 def ord_ind(i1, i2):
342     if i1 < i2:
343         return i1, i2
344     return i2, i1
345
346
347 class Mesh(bpy_types.ID):
348     __slots__ = ()
349
350     def from_pydata(self, vertices, edges, faces):
351         """
352         Make a mesh from a list of vertices/edges/faces
353         Until we have a nicer way to make geometry, use this.
354
355         :arg vertices:
356
357            float triplets each representing (X, Y, Z)
358            eg: [(0.0, 1.0, 0.5), ...].
359
360         :type vertices: iterable object
361         :arg edges:
362
363            int pairs, each pair contains two indices to the
364            *vertices* argument. eg: [(1, 2), ...]
365
366         :type edges: iterable object
367         :arg faces:
368
369            iterator of faces, each faces contains three or four indices to
370            the *vertices* argument. eg: [(5, 6, 8, 9), (1, 2, 3), ...]
371
372         :type faces: iterable object
373         """
374         self.vertices.add(len(vertices))
375         self.edges.add(len(edges))
376         self.loops.add(sum((len(f) for f in faces)))
377         self.polygons.add(len(faces))
378
379         vertices_flat = [f for v in vertices for f in v]
380         self.vertices.foreach_set("co", vertices_flat)
381         del vertices_flat
382
383         edges_flat = [i for e in edges for i in e]
384         self.edges.foreach_set("vertices", edges_flat)
385         del edges_flat
386
387         # this is different in bmesh
388         loop_index = 0
389         for i, p in enumerate(self.polygons):
390             f = faces[i]
391             loop_len = len(f)
392             p.loop_start = loop_index
393             p.loop_total = loop_len
394             p.vertices = f
395             loop_index += loop_len
396
397     @property
398     def edge_keys(self):
399         return [ed.key for ed in self.edges]
400
401
402 class MeshEdge(StructRNA):
403     __slots__ = ()
404
405     @property
406     def key(self):
407         return ord_ind(*tuple(self.vertices))
408
409
410 class MeshFace(StructRNA):
411     __slots__ = ()
412
413     @property
414     def center(self):
415         """The midpoint of the face."""
416         face_verts = self.vertices[:]
417         mesh_verts = self.id_data.vertices
418         if len(face_verts) == 3:
419             return (mesh_verts[face_verts[0]].co +
420                     mesh_verts[face_verts[1]].co +
421                     mesh_verts[face_verts[2]].co
422                     ) / 3.0
423         else:
424             return (mesh_verts[face_verts[0]].co +
425                     mesh_verts[face_verts[1]].co +
426                     mesh_verts[face_verts[2]].co +
427                     mesh_verts[face_verts[3]].co
428                     ) / 4.0
429
430     @property
431     def edge_keys(self):
432         verts = self.vertices[:]
433         if len(verts) == 3:
434             return (ord_ind(verts[0], verts[1]),
435                     ord_ind(verts[1], verts[2]),
436                     ord_ind(verts[2], verts[0]),
437                     )
438         else:
439             return (ord_ind(verts[0], verts[1]),
440                     ord_ind(verts[1], verts[2]),
441                     ord_ind(verts[2], verts[3]),
442                     ord_ind(verts[3], verts[0]),
443                     )
444
445
446 class Text(bpy_types.ID):
447     __slots__ = ()
448
449     def as_string(self):
450         """Return the text as a string."""
451         return "\n".join(line.body for line in self.lines)
452
453     def from_string(self, string):
454         """Replace text with this string."""
455         self.clear()
456         self.write(string)
457
458     @property
459     def users_logic(self):
460         """Logic bricks that use this text"""
461         import bpy
462         return tuple(obj for obj in bpy.data.objects
463                      if self in [cont.text for cont in obj.game.controllers
464                                  if cont.type == 'PYTHON']
465                      )
466
467 # values are module: [(cls, path, line), ...]
468 TypeMap = {}
469
470
471 class Sound(bpy_types.ID):
472     __slots__ = ()
473
474     @property
475     def factory(self):
476         """The aud.Factory object of the sound."""
477         import aud
478         return aud._sound_from_pointer(self.as_pointer())
479
480
481 class RNAMeta(type):
482     def __new__(cls, name, bases, classdict, **args):
483         result = type.__new__(cls, name, bases, classdict)
484         if bases and bases[0] is not StructRNA:
485             from _weakref import ref as ref
486             module = result.__module__
487
488             # first part of packages only
489             if "." in module:
490                 module = module[:module.index(".")]
491
492             TypeMap.setdefault(module, []).append(ref(result))
493
494         return result
495
496     @property
497     def is_registered(cls):
498         return "bl_rna" in cls.__dict__
499
500
501 class OrderedDictMini(dict):
502     def __init__(self, *args):
503         self.order = []
504         dict.__init__(self, args)
505
506     def __setitem__(self, key, val):
507         dict.__setitem__(self, key, val)
508         if key not in self.order:
509             self.order.append(key)
510
511     def __delitem__(self, key):
512         dict.__delitem__(self, key)
513         self.order.remove(key)
514
515
516 class RNAMetaPropGroup(RNAMeta, StructMetaPropGroup):
517     pass
518
519
520 class OrderedMeta(RNAMeta):
521     def __init__(cls, name, bases, attributes):
522         if attributes.__class__ is OrderedDictMini:
523             cls.order = attributes.order
524
525     def __prepare__(name, bases, **kwargs):
526         return OrderedDictMini()  # collections.OrderedDict()
527
528
529 # Only defined so operators members can be used by accessing self.order
530 # with doc generation 'self.properties.bl_rna.properties' can fail
531 class Operator(StructRNA, metaclass=OrderedMeta):
532     __slots__ = ()
533
534     def __getattribute__(self, attr):
535         properties = StructRNA.path_resolve(self, "properties")
536         bl_rna = getattr(properties, "bl_rna", None)
537         if bl_rna and attr in bl_rna.properties:
538             return getattr(properties, attr)
539         return super().__getattribute__(attr)
540
541     def __setattr__(self, attr, value):
542         properties = StructRNA.path_resolve(self, "properties")
543         bl_rna = getattr(properties, "bl_rna", None)
544         if bl_rna and attr in bl_rna.properties:
545             return setattr(properties, attr, value)
546         return super().__setattr__(attr, value)
547
548     def __delattr__(self, attr):
549         properties = StructRNA.path_resolve(self, "properties")
550         bl_rna = getattr(properties, "bl_rna", None)
551         if bl_rna and attr in bl_rna.properties:
552             return delattr(properties, attr)
553         return super().__delattr__(attr)
554
555     def as_keywords(self, ignore=()):
556         """Return a copy of the properties as a dictionary"""
557         ignore = ignore + ("rna_type",)
558         return {attr: getattr(self, attr)
559                 for attr in self.properties.rna_type.properties.keys()
560                 if attr not in ignore}
561
562
563 class Macro(StructRNA, metaclass=OrderedMeta):
564     # bpy_types is imported before ops is defined
565     # so we have to do a local import on each run
566     __slots__ = ()
567
568     @classmethod
569     def define(self, opname):
570         from _bpy import ops
571         return ops.macro_define(self, opname)
572
573
574 class PropertyGroup(StructRNA, metaclass=RNAMetaPropGroup):
575         __slots__ = ()
576
577
578 class RenderEngine(StructRNA, metaclass=RNAMeta):
579     __slots__ = ()
580
581
582 class KeyingSetInfo(StructRNA, metaclass=RNAMeta):
583     __slots__ = ()
584
585
586 class _GenericUI:
587     __slots__ = ()
588
589     @classmethod
590     def _dyn_ui_initialize(cls):
591         draw_funcs = getattr(cls.draw, "_draw_funcs", None)
592
593         if draw_funcs is None:
594
595             def draw_ls(self, context):
596                 # ensure menus always get default context
597                 operator_context_default = self.layout.operator_context
598
599                 for func in draw_ls._draw_funcs:
600                     # so bad menu functions don't stop
601                     # the entire menu from drawing
602                     try:
603                         func(self, context)
604                     except:
605                         import traceback
606                         traceback.print_exc()
607
608                     self.layout.operator_context = operator_context_default
609
610             draw_funcs = draw_ls._draw_funcs = [cls.draw]
611             cls.draw = draw_ls
612
613         return draw_funcs
614
615     @classmethod
616     def append(cls, draw_func):
617         """
618         Append a draw function to this menu,
619         takes the same arguments as the menus draw function
620         """
621         draw_funcs = cls._dyn_ui_initialize()
622         draw_funcs.append(draw_func)
623
624     @classmethod
625     def prepend(cls, draw_func):
626         """
627         Prepend a draw function to this menu, takes the same arguments as
628         the menus draw function
629         """
630         draw_funcs = cls._dyn_ui_initialize()
631         draw_funcs.insert(0, draw_func)
632
633     @classmethod
634     def remove(cls, draw_func):
635         """Remove a draw function that has been added to this menu"""
636         draw_funcs = cls._dyn_ui_initialize()
637         try:
638             draw_funcs.remove(draw_func)
639         except ValueError:
640             pass
641
642
643 class Panel(StructRNA, _GenericUI, metaclass=RNAMeta):
644     __slots__ = ()
645
646
647 class Header(StructRNA, _GenericUI, metaclass=RNAMeta):
648     __slots__ = ()
649
650
651 class Menu(StructRNA, _GenericUI, metaclass=RNAMeta):
652     __slots__ = ()
653
654     def path_menu(self, searchpaths, operator, props_default={}):
655         layout = self.layout
656         # hard coded to set the operators 'filepath' to the filename.
657
658         import os
659         import bpy.utils
660
661         layout = self.layout
662
663         if not searchpaths:
664             layout.label("* Missing Paths *")
665
666         # collect paths
667         files = []
668         for directory in searchpaths:
669             files.extend([(f, os.path.join(directory, f))
670                            for f in os.listdir(directory)])
671
672         files.sort()
673
674         for f, filepath in files:
675
676             if f.startswith("."):
677                 continue
678
679             preset_name = bpy.path.display_name(f)
680             props = layout.operator(operator, text=preset_name)
681
682             for attr, value in props_default.items():
683                 setattr(props, attr, value)
684
685             props.filepath = filepath
686             if operator == "script.execute_preset":
687                 props.menu_idname = self.bl_idname
688
689     def draw_preset(self, context):
690         """
691         Define these on the subclass
692         - preset_operator
693         - preset_subdir
694         """
695         import bpy
696         self.path_menu(bpy.utils.preset_paths(self.preset_subdir),
697                        self.preset_operator)