change python scripts so modules which register with blender have a register() functi...
[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         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 [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 [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 [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(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         optionaly, seams are edge keys that will be removed
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
383 class MeshEdge(StructRNA):
384     __slots__ = ()
385
386     @property
387     def key(self):
388         return ord_ind(*tuple(self.verts))
389
390
391 class MeshFace(StructRNA):
392     __slots__ = ()
393
394     @property
395     def center(self):
396         """The midpoint of the face."""
397         face_verts = self.verts[:]
398         mesh_verts = self.id_data.verts
399         if len(face_verts) == 3:
400             return (mesh_verts[face_verts[0]].co + mesh_verts[face_verts[1]].co + mesh_verts[face_verts[2]].co) / 3.0
401         else:
402             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
403
404     @property
405     def edge_keys(self):
406         verts = self.verts[:]
407         if len(verts) == 3:
408             return ord_ind(verts[0], verts[1]), ord_ind(verts[1], verts[2]), ord_ind(verts[2], verts[0])
409
410         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])
411
412
413 import collections
414
415
416 class OrderedMeta(type):
417
418     def __init__(cls, name, bases, attributes):
419         super(OrderedMeta, cls).__init__(name, bases, attributes)
420         cls.order = list(attributes.keys())
421
422     def __prepare__(name, bases, **kwargs):
423         return collections.OrderedDict()
424
425
426 # Only defined so operators members can be used by accessing self.order
427 class Operator(StructRNA, metaclass=OrderedMeta):
428     __slots__ = ()
429
430
431 class Macro(StructRNA, metaclass=OrderedMeta):
432     # bpy_types is imported before ops is defined
433     # so we have to do a local import on each run
434     __slots__ = ()
435
436     @classmethod
437     def define(self, opname):
438         from _bpy import ops
439         return ops.macro_define(self, opname)
440
441
442 class _GenericUI:
443     __slots__ = ()
444
445     @classmethod
446     def _dyn_ui_initialize(cls):
447         draw_funcs = getattr(cls.draw, "_draw_funcs", None)
448
449         if draw_funcs is None:
450
451             def draw_ls(self, context):
452                 for func in draw_ls._draw_funcs:
453                     func(self, context)
454
455             draw_funcs = draw_ls._draw_funcs = [cls.draw]
456             cls.draw = draw_ls
457
458         return draw_funcs
459
460     @classmethod
461     def append(cls, draw_func):
462         """Prepend an draw function to this menu, takes the same arguments as the menus draw function."""
463         draw_funcs = cls._dyn_ui_initialize()
464         draw_funcs.append(draw_func)
465
466     @classmethod
467     def prepend(cls, draw_func):
468         """Prepend a draw function to this menu, takes the same arguments as the menus draw function."""
469         draw_funcs = cls._dyn_ui_initialize()
470         draw_funcs.insert(0, draw_func)
471
472     @classmethod
473     def remove(cls, draw_func):
474         """Remove a draw function that has been added to this menu"""
475         draw_funcs = cls._dyn_ui_initialize()
476         draw_funcs.remove(draw_func)
477
478
479 class Panel(StructRNA, _GenericUI):
480     __slots__ = ()
481
482
483 class Header(StructRNA, _GenericUI):
484     __slots__ = ()
485
486
487 class Menu(StructRNA, _GenericUI):
488     __slots__ = ()
489
490     def path_menu(self, searchpaths, operator):
491         layout = self.layout
492         # hard coded to set the operators 'path' to the filename.
493
494         import os
495         import bpy.utils
496
497         layout = self.layout
498
499         # collect paths
500         files = []
501         for path in searchpaths:
502             files.extend([(f, os.path.join(path, f)) for f in os.listdir(path)])
503
504         files.sort()
505
506         for f, path in files:
507
508             if f.startswith("."):
509                 continue
510
511             layout.operator(operator, text=bpy.utils.display_name(f)).path = path
512
513     def draw_preset(self, context):
514         """Define these on the subclass
515          - preset_operator
516          - preset_subdir
517         """
518         import bpy
519         self.path_menu(bpy.utils.preset_paths(self.preset_subdir), self.preset_operator)