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