misc changes from trunk (avoid confusion since these were not intentional differences)
[blender-staging.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 + 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 class MeshPolygon(StructRNA):
446     __slots__ = ()
447
448     @property
449     def edge_keys(self):
450         verts = self.vertices[:]
451         vlen = len(self.vertices)
452         return [ord_ind(verts[i], verts[(i+1) % vlen]) for i in range(vlen)]
453
454     @property
455     def loops(self):
456         start = self.loop_start
457         end = start + self.loop_total
458         return range(start, end)
459
460 class Text(bpy_types.ID):
461     __slots__ = ()
462
463     def as_string(self):
464         """Return the text as a string."""
465         return "\n".join(line.body for line in self.lines)
466
467     def from_string(self, string):
468         """Replace text with this string."""
469         self.clear()
470         self.write(string)
471
472     @property
473     def users_logic(self):
474         """Logic bricks that use this text"""
475         import bpy
476         return tuple(obj for obj in bpy.data.objects
477                      if self in [cont.text for cont in obj.game.controllers
478                                  if cont.type == 'PYTHON']
479                      )
480
481 # values are module: [(cls, path, line), ...]
482 TypeMap = {}
483
484
485 class Sound(bpy_types.ID):
486     __slots__ = ()
487
488     @property
489     def factory(self):
490         """The aud.Factory object of the sound."""
491         import aud
492         return aud._sound_from_pointer(self.as_pointer())
493
494
495 class RNAMeta(type):
496     def __new__(cls, name, bases, classdict, **args):
497         result = type.__new__(cls, name, bases, classdict)
498         if bases and bases[0] is not StructRNA:
499             from _weakref import ref as ref
500             module = result.__module__
501
502             # first part of packages only
503             if "." in module:
504                 module = module[:module.index(".")]
505
506             TypeMap.setdefault(module, []).append(ref(result))
507
508         return result
509
510     @property
511     def is_registered(cls):
512         return "bl_rna" in cls.__dict__
513
514
515 class OrderedDictMini(dict):
516     def __init__(self, *args):
517         self.order = []
518         dict.__init__(self, args)
519
520     def __setitem__(self, key, val):
521         dict.__setitem__(self, key, val)
522         if key not in self.order:
523             self.order.append(key)
524
525     def __delitem__(self, key):
526         dict.__delitem__(self, key)
527         self.order.remove(key)
528
529
530 class RNAMetaPropGroup(RNAMeta, StructMetaPropGroup):
531     pass
532
533
534 class OrderedMeta(RNAMeta):
535     def __init__(cls, name, bases, attributes):
536         if attributes.__class__ is OrderedDictMini:
537             cls.order = attributes.order
538
539     def __prepare__(name, bases, **kwargs):
540         return OrderedDictMini()  # collections.OrderedDict()
541
542
543 # Only defined so operators members can be used by accessing self.order
544 # with doc generation 'self.properties.bl_rna.properties' can fail
545 class Operator(StructRNA, metaclass=OrderedMeta):
546     __slots__ = ()
547
548     def __getattribute__(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 getattr(properties, attr)
553         return super().__getattribute__(attr)
554
555     def __setattr__(self, attr, value):
556         properties = StructRNA.path_resolve(self, "properties")
557         bl_rna = getattr(properties, "bl_rna", None)
558         if bl_rna and attr in bl_rna.properties:
559             return setattr(properties, attr, value)
560         return super().__setattr__(attr, value)
561
562     def __delattr__(self, attr):
563         properties = StructRNA.path_resolve(self, "properties")
564         bl_rna = getattr(properties, "bl_rna", None)
565         if bl_rna and attr in bl_rna.properties:
566             return delattr(properties, attr)
567         return super().__delattr__(attr)
568
569     def as_keywords(self, ignore=()):
570         """Return a copy of the properties as a dictionary"""
571         ignore = ignore + ("rna_type",)
572         return {attr: getattr(self, attr)
573                 for attr in self.properties.rna_type.properties.keys()
574                 if attr not in ignore}
575
576
577 class Macro(StructRNA, metaclass=OrderedMeta):
578     # bpy_types is imported before ops is defined
579     # so we have to do a local import on each run
580     __slots__ = ()
581
582     @classmethod
583     def define(self, opname):
584         from _bpy import ops
585         return ops.macro_define(self, opname)
586
587
588 class PropertyGroup(StructRNA, metaclass=RNAMetaPropGroup):
589         __slots__ = ()
590
591
592 class RenderEngine(StructRNA, metaclass=RNAMeta):
593     __slots__ = ()
594
595
596 class KeyingSetInfo(StructRNA, metaclass=RNAMeta):
597     __slots__ = ()
598
599
600 class _GenericUI:
601     __slots__ = ()
602
603     @classmethod
604     def _dyn_ui_initialize(cls):
605         draw_funcs = getattr(cls.draw, "_draw_funcs", None)
606
607         if draw_funcs is None:
608
609             def draw_ls(self, context):
610                 # ensure menus always get default context
611                 operator_context_default = self.layout.operator_context
612
613                 for func in draw_ls._draw_funcs:
614                     # so bad menu functions don't stop
615                     # the entire menu from drawing
616                     try:
617                         func(self, context)
618                     except:
619                         import traceback
620                         traceback.print_exc()
621
622                     self.layout.operator_context = operator_context_default
623
624             draw_funcs = draw_ls._draw_funcs = [cls.draw]
625             cls.draw = draw_ls
626
627         return draw_funcs
628
629     @classmethod
630     def append(cls, draw_func):
631         """
632         Append a draw function to this menu,
633         takes the same arguments as the menus draw function
634         """
635         draw_funcs = cls._dyn_ui_initialize()
636         draw_funcs.append(draw_func)
637
638     @classmethod
639     def prepend(cls, draw_func):
640         """
641         Prepend a draw function to this menu, takes the same arguments as
642         the menus draw function
643         """
644         draw_funcs = cls._dyn_ui_initialize()
645         draw_funcs.insert(0, draw_func)
646
647     @classmethod
648     def remove(cls, draw_func):
649         """Remove a draw function that has been added to this menu"""
650         draw_funcs = cls._dyn_ui_initialize()
651         try:
652             draw_funcs.remove(draw_func)
653         except:
654             pass
655
656
657 class Panel(StructRNA, _GenericUI, metaclass=RNAMeta):
658     __slots__ = ()
659
660
661 class Header(StructRNA, _GenericUI, metaclass=RNAMeta):
662     __slots__ = ()
663
664
665 class Menu(StructRNA, _GenericUI, metaclass=RNAMeta):
666     __slots__ = ()
667
668     def path_menu(self, searchpaths, operator, props_default={}):
669         layout = self.layout
670         # hard coded to set the operators 'filepath' to the filename.
671
672         import os
673         import bpy.utils
674
675         layout = self.layout
676
677         if not searchpaths:
678             layout.label("* Missing Paths *")
679
680         # collect paths
681         files = []
682         for directory in searchpaths:
683             files.extend([(f, os.path.join(directory, f))
684                            for f in os.listdir(directory)])
685
686         files.sort()
687
688         for f, filepath in files:
689
690             if f.startswith("."):
691                 continue
692
693             preset_name = bpy.path.display_name(f)
694             props = layout.operator(operator, text=preset_name)
695
696             for attr, value in props_default.items():
697                 setattr(props, attr, value)
698
699             props.filepath = filepath
700             if operator == "script.execute_preset":
701                 props.menu_idname = self.bl_idname
702
703     def draw_preset(self, context):
704         """
705         Define these on the subclass
706         - preset_operator
707         - preset_subdir
708         """
709         import bpy
710         self.path_menu(bpy.utils.preset_paths(self.preset_subdir),
711                        self.preset_operator)