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