svn merge -r40197:40311 ^/trunk/blender
[blender.git] / release / scripts / modules / rna_info.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 # classes for extracting info from blenders internal classes
22
23 import bpy
24
25 # use to strip python paths
26 script_paths = bpy.utils.script_paths()
27
28 _FAKE_STRUCT_SUBCLASS = True
29
30
31 def _get_direct_attr(rna_type, attr):
32     props = getattr(rna_type, attr)
33     base = rna_type.base
34
35     if not base:
36         return [prop for prop in props]
37     else:
38         props_base = getattr(base, attr).values()
39         return [prop for prop in props if prop not in props_base]
40
41
42 def get_direct_properties(rna_type):
43     return _get_direct_attr(rna_type, "properties")
44
45
46 def get_direct_functions(rna_type):
47     return _get_direct_attr(rna_type, "functions")
48
49
50 def rna_id_ignore(rna_id):
51     if rna_id == "rna_type":
52         return True
53
54     if "_OT_" in rna_id:
55         return True
56     if "_MT_" in rna_id:
57         return True
58     if "_PT_" in rna_id:
59         return True
60     if "_HT_" in rna_id:
61         return True
62     if "_KSI_" in rna_id:
63         return True
64     return False
65
66
67 def range_str(val):
68     if val < -10000000:
69         return '-inf'
70     elif val > 10000000:
71         return 'inf'
72     elif type(val) == float:
73         return '%g' % val
74     else:
75         return str(val)
76
77
78 def float_as_string(f):
79     val_str = "%g" % f
80     if '.' not in val_str and '-' not in val_str:  # value could be 1e-05
81         val_str += '.0'
82     return val_str
83
84
85 class InfoStructRNA:
86     global_lookup = {}
87
88     def __init__(self, rna_type):
89         self.bl_rna = rna_type
90
91         self.identifier = rna_type.identifier
92         self.name = rna_type.name
93         self.description = rna_type.description.strip()
94
95         # set later
96         self.base = None
97         self.nested = None
98         self.full_path = ""
99
100         self.functions = []
101         self.children = []
102         self.references = []
103         self.properties = []
104
105     def build(self):
106         rna_type = self.bl_rna
107         parent_id = self.identifier
108         self.properties[:] = [GetInfoPropertyRNA(rna_prop, parent_id) for rna_prop in get_direct_properties(rna_type) if rna_prop.identifier != "rna_type"]
109         self.functions[:] = [GetInfoFunctionRNA(rna_prop, parent_id) for rna_prop in get_direct_functions(rna_type)]
110
111     def get_bases(self):
112         bases = []
113         item = self
114
115         while item:
116             item = item.base
117             if item:
118                 bases.append(item)
119
120         return bases
121
122     def get_nested_properties(self, ls=None):
123         if not ls:
124             ls = self.properties[:]
125
126         if self.nested:
127             self.nested.get_nested_properties(ls)
128
129         return ls
130
131     def _get_py_visible_attrs(self):
132         attrs = []
133         py_class = getattr(bpy.types, self.identifier)
134         for attr_str in dir(py_class):
135             if attr_str.startswith("_"):
136                 continue
137             attrs.append((attr_str, getattr(py_class, attr_str)))
138         return attrs
139
140     def get_py_properties(self):
141         properties = []
142         for identifier, attr in self._get_py_visible_attrs():
143             if type(attr) is property:
144                 properties.append((identifier, attr))
145         return properties
146
147     def get_py_functions(self):
148         import types
149         functions = []
150         for identifier, attr in self._get_py_visible_attrs():
151             if type(attr) in {types.FunctionType, types.MethodType}:
152                 functions.append((identifier, attr))
153         return functions
154
155     def get_py_c_functions(self):
156         import types
157         functions = []
158         for identifier, attr in self._get_py_visible_attrs():
159             if type(attr) in {types.BuiltinMethodType, types.BuiltinFunctionType}:
160                 functions.append((identifier, attr))
161         return functions
162
163     def __str__(self):
164
165         txt = ""
166         txt += self.identifier
167         if self.base:
168             txt += "(%s)" % self.base.identifier
169         txt += ": " + self.description + "\n"
170
171         for prop in self.properties:
172             txt += prop.__repr__() + "\n"
173
174         for func in self.functions:
175             txt += func.__repr__() + "\n"
176
177         return txt
178
179
180 class InfoPropertyRNA:
181     global_lookup = {}
182
183     def __init__(self, rna_prop):
184         self.bl_prop = rna_prop
185         self.identifier = rna_prop.identifier
186         self.name = rna_prop.name
187         self.description = rna_prop.description.strip()
188         self.default_str = "<UNKNOWN>"
189
190     def build(self):
191         rna_prop = self.bl_prop
192
193         self.enum_items = []
194         self.min = getattr(rna_prop, "hard_min", -1)
195         self.max = getattr(rna_prop, "hard_max", -1)
196         self.array_length = getattr(rna_prop, "array_length", 0)
197         self.collection_type = GetInfoStructRNA(rna_prop.srna)
198         self.is_required = rna_prop.is_required
199         self.is_readonly = rna_prop.is_readonly
200         self.is_never_none = rna_prop.is_never_none
201
202         self.type = rna_prop.type.lower()
203         fixed_type = getattr(rna_prop, "fixed_type", "")
204         if fixed_type:
205             self.fixed_type = GetInfoStructRNA(fixed_type)  # valid for pointer/collections
206         else:
207             self.fixed_type = None
208
209         if self.type == "enum":
210             self.enum_items[:] = [(item.identifier, item.name, item.description) for item in rna_prop.enum_items]
211             self.is_enum_flag = rna_prop.is_enum_flag
212         else:
213             self.is_enum_flag = False
214
215         if self.array_length:
216             self.default = tuple(getattr(rna_prop, "default_array", ()))
217         elif self.type == "enum" and self.is_enum_flag:
218             self.default = getattr(rna_prop, "default_flag", set())
219         else:
220             self.default = getattr(rna_prop, "default", None)
221         self.default_str = ""  # fallback
222
223         if self.type == "pointer":
224             # pointer has no default, just set as None
225             self.default = None
226             self.default_str = "None"
227         elif self.type == "string":
228             self.default_str = "\"%s\"" % self.default
229         elif self.type == "enum":
230             if self.is_enum_flag:
231                 self.default_str = "%r" % self.default  # repr or set()
232             else:
233                 self.default_str = "'%s'" % self.default
234         elif self.array_length:
235             self.default_str = ''
236             # special case for floats
237             if len(self.default) > 0:
238                 if self.type == "float":
239                     self.default_str = "(%s)" % ", ".join(float_as_string(f) for f in self.default)
240             if not self.default_str:
241                 self.default_str = str(self.default)
242         else:
243             if self.type == "float":
244                 self.default_str = float_as_string(self.default)
245             else:
246                 self.default_str = str(self.default)
247
248         self.srna = GetInfoStructRNA(rna_prop.srna)  # valid for pointer/collections
249
250     def get_arg_default(self, force=True):
251         default = self.default_str
252         if default and (force or self.is_required == False):
253             return "%s=%s" % (self.identifier, default)
254         return self.identifier
255
256     def get_type_description(self, as_ret=False, as_arg=False, class_fmt="%s", collection_id="Collection"):
257         type_str = ""
258         if self.fixed_type is None:
259             type_str += self.type
260             if self.array_length:
261                 type_str += " array of %d items" % (self.array_length)
262
263             if self.type in {"float", "int"}:
264                 type_str += " in [%s, %s]" % (range_str(self.min), range_str(self.max))
265             elif self.type == "enum":
266                 if self.is_enum_flag:
267                     type_str += " set in {%s}" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
268                 else:
269                     type_str += " in [%s]" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
270
271             if not (as_arg or as_ret):
272                 # write default property, ignore function args for this
273                 if self.type != "pointer":
274                     if self.default_str:
275                         type_str += ", default %s" % self.default_str
276
277         else:
278             if self.type == "collection":
279                 if self.collection_type:
280                     collection_str = (class_fmt % self.collection_type.identifier) + (" %s of " % collection_id)
281                 else:
282                     collection_str = "%s of " % collection_id
283             else:
284                 collection_str = ""
285
286             type_str += collection_str + (class_fmt % self.fixed_type.identifier)
287
288         # setup qualifiers for this value.
289         type_info = []
290         if as_ret:
291             pass
292         elif as_arg:
293             if not self.is_required:
294                 type_info.append("optional")
295         else:  # readonly is only useful for selfs, not args
296             if self.is_readonly:
297                 type_info.append("readonly")
298
299         if self.is_never_none:
300             type_info.append("never None")
301
302         if type_info:
303             type_str += (", (%s)" % ", ".join(type_info))
304
305         return type_str
306
307     def __str__(self):
308         txt = ''
309         txt += ' * ' + self.identifier + ': ' + self.description
310
311         return txt
312
313
314 class InfoFunctionRNA:
315     global_lookup = {}
316
317     def __init__(self, rna_func):
318         self.bl_func = rna_func
319         self.identifier = rna_func.identifier
320         # self.name = rna_func.name # functions have no name!
321         self.description = rna_func.description.strip()
322         self.is_classmethod = not rna_func.use_self
323
324         self.args = []
325         self.return_values = ()
326
327     def build(self):
328         rna_func = self.bl_func
329         parent_id = rna_func
330         self.return_values = []
331
332         for rna_prop in rna_func.parameters.values():
333             prop = GetInfoPropertyRNA(rna_prop, parent_id)
334             if rna_prop.is_output:
335                 self.return_values.append(prop)
336             else:
337                 self.args.append(prop)
338
339         self.return_values = tuple(self.return_values)
340
341     def __str__(self):
342         txt = ''
343         txt += ' * ' + self.identifier + '('
344
345         for arg in self.args:
346             txt += arg.identifier + ', '
347         txt += '): ' + self.description
348         return txt
349
350
351 class InfoOperatorRNA:
352     global_lookup = {}
353
354     def __init__(self, rna_op):
355         self.bl_op = rna_op
356         self.identifier = rna_op.identifier
357
358         mod, name = self.identifier.split("_OT_", 1)
359         self.module_name = mod.lower()
360         self.func_name = name
361
362         # self.name = rna_func.name # functions have no name!
363         self.description = rna_op.description.strip()
364
365         self.args = []
366
367     def build(self):
368         rna_op = self.bl_op
369         parent_id = self.identifier
370         for rna_id, rna_prop in rna_op.properties.items():
371             if rna_id == "rna_type":
372                 continue
373
374             prop = GetInfoPropertyRNA(rna_prop, parent_id)
375             self.args.append(prop)
376
377     def get_location(self):
378         op_class = getattr(bpy.types, self.identifier)
379         op_func = getattr(op_class, "execute", None)
380         if op_func is None:
381             op_func = getattr(op_class, "invoke", None)
382         if op_func is None:
383             op_func = getattr(op_class, "poll", None)
384
385         if op_func:
386             op_code = op_func.__code__
387             source_path = op_code.co_filename
388
389             # clear the prefix
390             for p in script_paths:
391                 source_path = source_path.split(p)[-1]
392
393             if source_path[0] in "/\\":
394                 source_path = source_path[1:]
395
396             return source_path, op_code.co_firstlineno
397         else:
398             return None, None
399
400
401 def _GetInfoRNA(bl_rna, cls, parent_id=''):
402
403     if bl_rna is None:
404         return None
405
406     key = parent_id, bl_rna.identifier
407     try:
408         return cls.global_lookup[key]
409     except KeyError:
410         instance = cls.global_lookup[key] = cls(bl_rna)
411         return instance
412
413
414 def GetInfoStructRNA(bl_rna):
415     return _GetInfoRNA(bl_rna, InfoStructRNA)
416
417
418 def GetInfoPropertyRNA(bl_rna, parent_id):
419     return _GetInfoRNA(bl_rna, InfoPropertyRNA, parent_id)
420
421
422 def GetInfoFunctionRNA(bl_rna, parent_id):
423     return _GetInfoRNA(bl_rna, InfoFunctionRNA, parent_id)
424
425
426 def GetInfoOperatorRNA(bl_rna):
427     return _GetInfoRNA(bl_rna, InfoOperatorRNA)
428
429
430 def BuildRNAInfo():
431     # Use for faster lookups
432     # use rna_struct.identifier as the key for each dict
433     rna_struct_dict = {}  # store identifier:rna lookups
434     rna_full_path_dict = {}      # store the result of full_rna_struct_path(rna_struct)
435     rna_children_dict = {}  # store all rna_structs nested from here
436     rna_references_dict = {}  # store a list of rna path strings that reference this type
437     # rna_functions_dict = {}  # store all functions directly in this type (not inherited)
438
439     def full_rna_struct_path(rna_struct):
440         '''
441         Needed when referencing one struct from another
442         '''
443         nested = rna_struct.nested
444         if nested:
445             return "%s.%s" % (full_rna_struct_path(nested), rna_struct.identifier)
446         else:
447             return rna_struct.identifier
448
449     # def write_func(rna_func, ident):
450     def base_id(rna_struct):
451         try:
452             return rna_struct.base.identifier
453         except:
454             return ""  # invalid id
455
456     #structs = [(base_id(rna_struct), rna_struct.identifier, rna_struct) for rna_struct in bpy.doc.structs.values()]
457     '''
458     structs = []
459     for rna_struct in bpy.doc.structs.values():
460         structs.append( (base_id(rna_struct), rna_struct.identifier, rna_struct) )
461     '''
462     structs = []
463     for rna_type_name in dir(bpy.types):
464         rna_type = getattr(bpy.types, rna_type_name)
465
466         rna_struct = getattr(rna_type, "bl_rna", None)
467
468         if rna_struct:
469             #if not rna_type_name.startswith('__'):
470
471             identifier = rna_struct.identifier
472
473             if not rna_id_ignore(identifier):
474                 structs.append((base_id(rna_struct), identifier, rna_struct))
475
476                 # Simple lookup
477                 rna_struct_dict[identifier] = rna_struct
478
479                 # Store full rna path 'GameObjectSettings' -> 'Object.GameObjectSettings'
480                 rna_full_path_dict[identifier] = full_rna_struct_path(rna_struct)
481
482                 # Store a list of functions, remove inherited later
483                 # NOT USED YET
484                 ## rna_functions_dict[identifier] = get_direct_functions(rna_struct)
485
486                 # fill in these later
487                 rna_children_dict[identifier] = []
488                 rna_references_dict[identifier] = []
489         else:
490             print("Ignoring", rna_type_name)
491
492     structs.sort()  # not needed but speeds up sort below, setting items without an inheritance first
493
494     # Arrange so classes are always defined in the correct order
495     deps_ok = False
496     while deps_ok == False:
497         deps_ok = True
498         rna_done = set()
499
500         for i, (rna_base, identifier, rna_struct) in enumerate(structs):
501
502             rna_done.add(identifier)
503
504             if rna_base and rna_base not in rna_done:
505                 deps_ok = False
506                 data = structs.pop(i)
507                 ok = False
508                 while i < len(structs):
509                     if structs[i][1] == rna_base:
510                         structs.insert(i + 1, data)  # insert after the item we depend on.
511                         ok = True
512                         break
513                     i += 1
514
515                 if not ok:
516                     print('Dependancy "%s" could not be found for "%s"' % (identifier, rna_base))
517
518                 break
519
520     # Done ordering structs
521
522     # precalc vars to avoid a lot of looping
523     for (rna_base, identifier, rna_struct) in structs:
524
525         # rna_struct_path = full_rna_struct_path(rna_struct)
526         rna_struct_path = rna_full_path_dict[identifier]
527
528         for rna_prop in get_direct_properties(rna_struct):
529             rna_prop_identifier = rna_prop.identifier
530
531             if rna_prop_identifier == 'RNA' or rna_id_ignore(rna_prop_identifier):
532                 continue
533
534             for rna_prop_ptr in (getattr(rna_prop, "fixed_type", None), getattr(rna_prop, "srna", None)):
535                 # Does this property point to me?
536                 if rna_prop_ptr:
537                     rna_references_dict[rna_prop_ptr.identifier].append("%s.%s" % (rna_struct_path, rna_prop_identifier))
538
539         for rna_func in get_direct_functions(rna_struct):
540             for rna_prop_identifier, rna_prop in rna_func.parameters.items():
541
542                 if rna_prop_identifier == 'RNA' or rna_id_ignore(rna_prop_identifier):
543                     continue
544
545                 rna_prop_ptr = getattr(rna_prop, "fixed_type", None)
546
547                 # Does this property point to me?
548                 if rna_prop_ptr:
549                     rna_references_dict[rna_prop_ptr.identifier].append("%s.%s" % (rna_struct_path, rna_func.identifier))
550
551         # Store nested children
552         nested = rna_struct.nested
553         if nested:
554             rna_children_dict[nested.identifier].append(rna_struct)
555
556     # Sort the refs, just reads nicer
557     for rna_refs in rna_references_dict.values():
558         rna_refs.sort()
559
560     info_structs = []
561     for (rna_base, identifier, rna_struct) in structs:
562         #if rna_struct.nested:
563         #    continue
564
565         #write_struct(rna_struct, '')
566         info_struct = GetInfoStructRNA(rna_struct)
567         if rna_base:
568             info_struct.base = GetInfoStructRNA(rna_struct_dict[rna_base])
569         info_struct.nested = GetInfoStructRNA(rna_struct.nested)
570         info_struct.children[:] = rna_children_dict[identifier]
571         info_struct.references[:] = rna_references_dict[identifier]
572         info_struct.full_path = rna_full_path_dict[identifier]
573
574         info_structs.append(info_struct)
575
576     for rna_info_prop in InfoPropertyRNA.global_lookup.values():
577         rna_info_prop.build()
578
579     for rna_info_prop in InfoFunctionRNA.global_lookup.values():
580         rna_info_prop.build()
581
582     for rna_info in InfoStructRNA.global_lookup.values():
583         rna_info.build()
584         for prop in rna_info.properties:
585             prop.build()
586         for func in rna_info.functions:
587             func.build()
588             for prop in func.args:
589                 prop.build()
590             for prop in func.return_values:
591                 prop.build()
592
593     if 1:
594         for rna_info in InfoStructRNA.global_lookup.values():
595             for prop in rna_info.properties:
596                 # ERROR CHECK
597                 default = prop.default
598                 if type(default) in {float, int}:
599                     if default < prop.min or default > prop.max:
600                         print("\t %s.%s, %s not in [%s - %s]" % (rna_info.identifier, prop.identifier, default, prop.min, prop.max))
601
602     # now for operators
603     op_mods = dir(bpy.ops)
604
605     for op_mod_name in sorted(op_mods):
606         if op_mod_name.startswith('__'):
607             continue
608
609         op_mod = getattr(bpy.ops, op_mod_name)
610         operators = dir(op_mod)
611         for op in sorted(operators):
612             try:
613                 rna_prop = getattr(op_mod, op).get_rna()
614             except AttributeError:
615                 rna_prop = None
616             except TypeError:
617                 rna_prop = None
618
619             if rna_prop:
620                 GetInfoOperatorRNA(rna_prop.bl_rna)
621
622     for rna_info in InfoOperatorRNA.global_lookup.values():
623         rna_info.build()
624         for rna_prop in rna_info.args:
625             rna_prop.build()
626
627     #for rna_info in InfoStructRNA.global_lookup.values():
628     #    print(rna_info)
629     return InfoStructRNA.global_lookup, InfoFunctionRNA.global_lookup, InfoOperatorRNA.global_lookup, InfoPropertyRNA.global_lookup
630
631
632 if __name__ == "__main__":
633     import rna_info
634     struct = rna_info.BuildRNAInfo()[0]
635     data = []
636     for struct_id, v in sorted(struct.items()):
637         struct_id_str = v.identifier  # "".join(sid for sid in struct_id if struct_id)
638
639         for base in v.get_bases():
640             struct_id_str = base.identifier + "|" + struct_id_str
641
642         props = [(prop.identifier, prop) for prop in v.properties]
643         for prop_id, prop in sorted(props):
644             # if prop.type == 'boolean':
645             #     continue
646             prop_type = prop.type
647             if prop.array_length > 0:
648                 prop_type += "[%d]" % prop.array_length
649
650             data.append("%s.%s -> %s:    %s%s    %s" % (struct_id_str, prop.identifier, prop.identifier, prop_type, ", (read-only)" if prop.is_readonly else "", prop.description))
651         data.sort()
652
653     if bpy.app.background:
654         import sys
655         sys.stderr.write("\n".join(data))
656         sys.stderr.write("\n\nEOF\n")
657     else:
658         text = bpy.data.texts.new(name="api.py")
659         text.from_string(data)