Support for the C Macro system in Python.
[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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16 #
17 # ##### END GPL LICENSE BLOCK #####
18 from _bpy import types as bpy_types
19
20 StructRNA = bpy_types.Struct.__bases__[0]
21 # StructRNA = bpy_types.Struct
22
23
24 class Context(StructRNA):
25
26     def copy(self):
27         new_context = {}
28         generic_keys = StructRNA.__dict__.keys()
29         for item in dir(self):
30             if item not in generic_keys:
31                 new_context[item] = getattr(self, item)
32
33         return new_context
34
35
36 class Object(bpy_types.ID):
37
38     @property
39     def children(self):
40         import bpy
41         return [child for child in bpy.data.objects if child.parent == self]
42
43
44 class _GenericBone:
45     '''
46     functions for bones, common between Armature/Pose/Edit bones.
47     internal subclassing use only.
48     '''
49
50     def translate(self, vec):
51         self.head += vec
52         self.tail += vec
53
54     def parent_index(self, parent_test):
55         '''
56         The same as 'bone in other_bone.parent_recursive' but saved generating a list.
57         '''
58         # use the name so different types can be tested.
59         name = parent_test.name
60         
61         parent = self.parent
62         i = 1
63         while parent:
64             if parent.name == name:
65                 return i
66             parent = parent.parent
67             i += 1
68         
69         return 0
70
71     @property
72     def basename(self):
73         return self.name.rsplit(".", 1)[0]
74
75     @property
76     def parent_recursive(self):
77         parent_list = []
78         parent = self.parent
79         
80         while parent:
81             if parent:
82                 parent_list.append(parent)
83             
84             parent = parent.parent
85         
86         return parent_list
87
88     @property
89     def length(self):
90         return (self.head - self.tail).length
91     
92     @length.setter
93     def length(self, value):
94         """The distance from head to tail"""
95         self.tail = self.head + ((self.tail - self.head).normalize() * value)
96
97     @property
98     def children(self):
99         return [child for child in self._other_bones if child.parent == self]
100
101     @property
102     def children_recursive(self):
103         bones_children = []
104         for bone in self._other_bones:
105             index = bone.parent_index(self)
106             if index:
107                 bones_children.append((index, bone))
108         
109         # sort by distance to parent
110         bones_children.sort(key=lambda bone_pair: bone_pair[0])
111         return [bone for index, bone in bones_children]
112
113     @property
114     def children_recursive_basename(self):
115         '''
116         Returns a chain of children with the same base name as this bone
117         Only direct chains are supported, forks caused by multiple children with matching basenames will.
118         '''
119         basename = self.basename
120         chain = []
121
122         child = self
123         while True:
124             children = child.children
125             children_basename = []
126
127             for child in children:
128                 if basename == child.basename:
129                     children_basename.append(child)
130
131             if len(children_basename) == 1:
132                 child = children_basename[0]
133                 chain.append(child)
134             else:
135                 if len(children_basename):
136                     print("multiple basenames found, this is probably not what you want!", bone.name, children_basename)
137
138                 break
139         
140         return chain
141
142     @property
143     def _other_bones(self):
144         id_data = self.id_data
145         id_data_type = type(id_data)
146         
147         if id_data_type == bpy_types.Object:
148             bones = id_data.pose.bones
149         elif id_data_type == bpy_types.Armature:
150             bones = id_data.edit_bones
151             if not bones: # not in editmode
152                 bones = id_data.bones
153         
154         return bones
155
156
157 class PoseBone(StructRNA, _GenericBone):
158     pass
159
160
161 class Bone(StructRNA, _GenericBone):
162     pass
163
164
165 class EditBone(StructRNA, _GenericBone):
166     pass
167
168
169 def ord_ind(i1,i2):
170     if i1<i2: return i1,i2
171     return i2,i1
172
173 class Mesh(bpy_types.ID):
174     
175     def from_pydata(self, verts, edges, faces):
176         '''
177         Make a mesh from a list of verts/edges/faces
178         Until we have a nicer way to make geometry, use this.
179         '''
180         self.add_geometry(len(verts), len(edges), len(faces))
181         
182         verts_flat = [f for v in verts for f in v]
183         self.verts.foreach_set("co", verts_flat)
184         del verts_flat
185         
186         edges_flat = [i for e in edges for i in e]
187         self.edges.foreach_set("verts", edges_flat)
188         del edges_flat
189         
190         def treat_face(f):
191             if len(f) == 3:
192                 return f[0], f[1], f[2], 0
193             elif f[3] == 0:
194                 return f[3], f[0], f[1], f[2]
195             return f
196         
197         faces_flat = [v for f in faces for v in treat_face(f)]
198         self.faces.foreach_set("verts_raw", faces_flat)
199         del faces_flat
200
201     @property
202     def edge_keys(self):
203         return [edge_key for face in self.faces for edge_key in face.edge_keys]
204
205     @property
206     def edge_face_count_dict(self):
207         face_edge_keys = [face.edge_keys for face in self.faces]
208         face_edge_count = {}
209         for face_keys in face_edge_keys:
210             for key in face_keys:
211                 try:
212                     face_edge_count[key] += 1
213                 except:
214                     face_edge_count[key] = 1
215
216         return face_edge_count
217
218     @property
219     def edge_face_count(self):
220         edge_face_count_dict = self.edge_face_count_dict
221         return [edge_face_count_dict.get(ed.key, 0) for ed in mesh.edges]
222
223
224 class MeshEdge(StructRNA):
225
226     @property
227     def key(self):
228         return ord_ind(*tuple(self.verts))
229
230
231 class MeshFace(StructRNA):
232
233     @property
234     def edge_keys(self):
235         verts = tuple(self.verts)
236         if len(verts)==3:
237             return ord_ind(verts[0], verts[1]),  ord_ind(verts[1], verts[2]),  ord_ind(verts[2], verts[0])
238
239         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])
240
241
242 import collections
243 class OrderedMeta(type):
244     def __init__(cls, name, bases, attributes):
245         super(OrderedMeta, cls).__init__(name, bases, attributes)
246         cls.order = list(attributes.keys())
247     def __prepare__(name, bases, **kwargs):
248         return collections.OrderedDict()
249
250
251 # Only defined so operators members can be used by accessing self.order
252 class Operator(StructRNA, metaclass=OrderedMeta):
253     pass
254
255 class Macro(StructRNA, metaclass=OrderedMeta):
256     # bpy_types is imported before ops is defined
257     # so we have to do a local import on each run
258     @classmethod
259     def define(self, opname):
260         from _bpy import ops
261         return ops.macro_define(self, opname)
262
263 class Menu(StructRNA):
264     
265     def path_menu(self, searchpaths, operator):
266         layout = self.layout
267         # hard coded to set the operators 'path' to the filename.
268         
269         import os
270
271         def path_to_name(f):
272             ''' Only capitalize all lowercase names, mixed case use them as is.
273             '''
274             f_base = os.path.splitext(f)[0]
275             
276             # string replacements
277             f_base = f_base.replace("_colon_", ":")
278             
279             f_base = f_base.replace("_", " ")
280             
281             if f_base.lower() == f_base:
282                 return ' '.join([w[0].upper() + w[1:] for w in f_base.split()])
283             else:
284                 return f_base
285
286         layout = self.layout
287
288         # collect paths
289         files = []
290         for path in searchpaths:
291             files.extend([(f, os.path.join(path, f)) for f in os.listdir(path)])
292
293         files.sort()
294
295         for f, path in files:
296
297             if f.startswith("."):
298                 continue
299
300             layout.operator(operator, text=path_to_name(f)).path = path
301     
302     def draw_preset(self, context):
303         '''Define these on the subclass
304          - preset_operator
305          - preset_subdir
306         '''
307         import bpy
308         self.path_menu(bpy.utils.preset_paths(self.preset_subdir), self.preset_operator)