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