61c3f11ccef025fa9f07274327e2fce70a1d1951
[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.rotation_part()
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.rotation_part()
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.rotation_part()
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.rotation_part()
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 import collections
554
555 TypeMap = {}
556 # Properties (IDPropertyGroup) are different from types because they need to be registered
557 # before adding sub properties to them, so they are registered on definition
558 # and unregistered on unload
559 PropertiesMap = {}
560
561 # Using our own loading function we set this to false
562 # so when running a script directly in the text editor
563 # registers moduals instantly.
564 _register_immediate = True
565
566
567 def _unregister_module(module, free=True):
568     for t in TypeMap.get(module, ()):
569         try:
570             bpy_types.unregister(t)
571         except:
572             import traceback
573             print("bpy.utils._unregister_module(): Module '%s' failed to unregister class '%s.%s'" % (module, t.__module__, t.__name__))
574             traceback.print_exc()
575
576     if free == True and module in TypeMap:
577         del TypeMap[module]
578
579     for t in PropertiesMap.get(module, ()):
580         try:
581             bpy_types.unregister(t)
582         except:
583             import traceback
584             print("bpy.utils._unload_module(): Module '%s' failed to unregister class '%s.%s'" % (module, t.__module__, t.__name__))
585             traceback.print_exc()
586
587     if free == True and module in PropertiesMap:
588         del PropertiesMap[module]
589
590
591 def _register_module(module):
592     for t in TypeMap.get(module, ()):
593         try:
594             bpy_types.register(t)
595         except:
596             import traceback
597             import sys
598             print("bpy.utils._register_module(): '%s' failed to register class '%s.%s'" % (sys.modules[module].__file__, t.__module__, t.__name__))
599             traceback.print_exc()
600
601
602 class RNAMeta(type):
603     @classmethod
604     def _register_immediate(cls):
605         return _register_immediate
606
607     def __new__(cls, name, bases, classdict, **args):
608         result = type.__new__(cls, name, bases, classdict)
609         if bases and bases[0] != StructRNA:
610             module = result.__module__
611
612             ClassMap = TypeMap
613
614             # Register right away if needed
615             if cls._register_immediate():
616                 bpy_types.register(result)
617                 ClassMap = PropertiesMap
618
619             # first part of packages only
620             if "." in module:
621                 module = module[:module.index(".")]
622
623             ClassMap.setdefault(module, []).append(result)
624
625         return result
626
627
628 class RNAMetaRegister(RNAMeta, StructMetaIDProp):
629     @classmethod
630     def _register_immediate(cls):
631         return True
632
633
634 class OrderedMeta(RNAMeta):
635
636     def __init__(cls, name, bases, attributes):
637         super(OrderedMeta, cls).__init__(name, bases, attributes)
638         cls.order = list(attributes.keys())
639
640     def __prepare__(name, bases, **kwargs):
641         return collections.OrderedDict()
642
643
644 # Only defined so operators members can be used by accessing self.order
645 # with doc generation 'self.properties.bl_rna.properties' can fail
646 class Operator(StructRNA, metaclass=OrderedMeta):
647     __slots__ = ()
648
649     def __getattribute__(self, attr):
650         properties = StructRNA.path_resolve(self, "properties")
651         bl_rna = getattr(properties, "bl_rna", None)
652         if bl_rna and attr in bl_rna.properties:
653             return getattr(properties, attr)
654         return super().__getattribute__(attr)
655
656     def __setattr__(self, attr, value):
657         properties = StructRNA.path_resolve(self, "properties")
658         bl_rna = getattr(properties, "bl_rna", None)
659         if bl_rna and attr in bl_rna.properties:
660             return setattr(properties, attr, value)
661         return super().__setattr__(attr, value)
662
663     def __delattr__(self, attr):
664         properties = StructRNA.path_resolve(self, "properties")
665         bl_rna = getattr(properties, "bl_rna", None)
666         if bl_rna and attr in bl_rna.properties:
667             return delattr(properties, attr)
668         return super().__delattr__(attr)
669
670     def as_keywords(self, ignore=()):
671         """ Return a copy of the properties as a dictionary.
672         """
673         ignore = ignore + ("rna_type",)
674         return {attr: getattr(self, attr) for attr in self.properties.rna_type.properties.keys() if attr not in ignore}
675
676
677 class Macro(StructRNA, metaclass=OrderedMeta):
678     # bpy_types is imported before ops is defined
679     # so we have to do a local import on each run
680     __slots__ = ()
681
682     @classmethod
683     def define(self, opname):
684         from _bpy import ops
685         return ops.macro_define(self, opname)
686
687
688 class IDPropertyGroup(StructRNA, metaclass=RNAMetaRegister):
689         __slots__ = ()
690
691
692 class RenderEngine(StructRNA, metaclass=RNAMeta):
693     __slots__ = ()
694
695
696 class _GenericUI:
697     __slots__ = ()
698
699     @classmethod
700     def _dyn_ui_initialize(cls):
701         draw_funcs = getattr(cls.draw, "_draw_funcs", None)
702
703         if draw_funcs is None:
704
705             def draw_ls(self, context):
706                 for func in draw_ls._draw_funcs:
707                     func(self, context)
708
709             draw_funcs = draw_ls._draw_funcs = [cls.draw]
710             cls.draw = draw_ls
711
712         return draw_funcs
713
714     @classmethod
715     def append(cls, draw_func):
716         """Prepend an draw function to this menu, takes the same arguments as the menus draw function."""
717         draw_funcs = cls._dyn_ui_initialize()
718         draw_funcs.append(draw_func)
719
720     @classmethod
721     def prepend(cls, draw_func):
722         """Prepend a draw function to this menu, takes the same arguments as the menus draw function."""
723         draw_funcs = cls._dyn_ui_initialize()
724         draw_funcs.insert(0, draw_func)
725
726     @classmethod
727     def remove(cls, draw_func):
728         """Remove a draw function that has been added to this menu"""
729         draw_funcs = cls._dyn_ui_initialize()
730         try:
731             draw_funcs.remove(draw_func)
732         except:
733             pass
734
735
736 class Panel(StructRNA, _GenericUI, metaclass=RNAMeta):
737     __slots__ = ()
738
739
740 class Header(StructRNA, _GenericUI, metaclass=RNAMeta):
741     __slots__ = ()
742
743
744 class Menu(StructRNA, _GenericUI, metaclass=RNAMeta):
745     __slots__ = ()
746
747     def path_menu(self, searchpaths, operator, props_default={}):
748         layout = self.layout
749         # hard coded to set the operators 'filepath' to the filename.
750
751         import os
752         import bpy.utils
753
754         layout = self.layout
755
756         if not searchpaths:
757             layout.label("* Missing Paths *")
758
759         # collect paths
760         files = []
761         for directory in searchpaths:
762             files.extend([(f, os.path.join(directory, f)) for f in os.listdir(directory)])
763
764         files.sort()
765
766         for f, filepath in files:
767
768             if f.startswith("."):
769                 continue
770
771             preset_name = bpy.path.display_name(f)
772             props = layout.operator(operator, text=preset_name)
773
774             for attr, value in props_default.items():
775                 setattr(props, attr, value)
776
777             props.filepath = filepath
778             if operator == "script.execute_preset":
779                 props.menu_idname = self.bl_idname
780
781     def draw_preset(self, context):
782         """Define these on the subclass
783          - preset_operator
784          - preset_subdir
785         """
786         import bpy
787         self.path_menu(bpy.utils.preset_paths(self.preset_subdir), self.preset_operator)