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