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