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