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