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