Fix crash in outliner on cursor hover
[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 def get_py_class_from_rna(rna_type):
86     """ Gets the Python type for a class which isn't necessarily added to ``bpy.types``.
87     """
88     identifier = rna_type.identifier
89     py_class = getattr(bpy.types, identifier, None)
90     if py_class is not None:
91         return py_class
92
93     def subclasses_recurse(cls):
94         for c in cls.__subclasses__():
95             # is_registered
96             if "bl_rna" in cls.__dict__:
97                 yield c
98             yield from subclasses_recurse(c)
99
100     while py_class is None:
101         base = rna_type.base
102         if base is None:
103             raise Exception("can't find type")
104         py_class_base = getattr(bpy.types, base.identifier, None)
105         if py_class_base is not None:
106             for cls in subclasses_recurse(py_class_base):
107                 if cls.bl_rna.identifier == identifier:
108                     return cls
109
110
111 class InfoStructRNA:
112     __slots__ = (
113         "bl_rna",
114         "identifier",
115         "name",
116         "description",
117         "base",
118         "nested",
119         "full_path",
120         "functions",
121         "children",
122         "references",
123         "properties",
124         "py_class",
125         "module_name",
126     )
127
128     global_lookup = {}
129
130     def __init__(self, rna_type):
131         self.bl_rna = rna_type
132
133         self.identifier = rna_type.identifier
134         self.name = rna_type.name
135         self.description = rna_type.description.strip()
136
137         # set later
138         self.base = None
139         self.nested = None
140         self.full_path = ""
141
142         self.functions = []
143         self.children = []
144         self.references = []
145         self.properties = []
146
147         self.py_class = get_py_class_from_rna(self.bl_rna)
148         self.module_name = (
149             self.py_class.__module__
150             if (self.py_class and not hasattr(bpy.types, self.identifier)) else
151             "bpy.types"
152         )
153         if self.module_name == "bpy_types":
154             self.module_name = "bpy.types"
155
156     def build(self):
157         rna_type = self.bl_rna
158         parent_id = self.identifier
159         self.properties[:] = [GetInfoPropertyRNA(rna_prop, parent_id)
160                               for rna_prop in get_direct_properties(rna_type) if rna_prop.identifier != "rna_type"]
161         self.functions[:] = [GetInfoFunctionRNA(rna_prop, parent_id)
162                              for rna_prop in get_direct_functions(rna_type)]
163
164     def get_bases(self):
165         bases = []
166         item = self
167
168         while item:
169             item = item.base
170             if item:
171                 bases.append(item)
172
173         return bases
174
175     def get_nested_properties(self, ls=None):
176         if not ls:
177             ls = self.properties[:]
178
179         if self.nested:
180             self.nested.get_nested_properties(ls)
181
182         return ls
183
184     def _get_py_visible_attrs(self):
185         attrs = []
186         py_class = self.py_class
187
188         for attr_str in dir(py_class):
189             if attr_str.startswith("_"):
190                 continue
191             attrs.append((attr_str, getattr(py_class, attr_str)))
192         return attrs
193
194     def get_py_properties(self):
195         properties = []
196         for identifier, attr in self._get_py_visible_attrs():
197             if type(attr) is property:
198                 properties.append((identifier, attr))
199         return properties
200
201     def get_py_functions(self):
202         import types
203         functions = []
204         for identifier, attr in self._get_py_visible_attrs():
205             # methods may be python wrappers to C functions
206             attr_func = getattr(attr, "__func__", attr)
207             if type(attr_func) in {types.FunctionType, types.MethodType}:
208                 functions.append((identifier, attr))
209         return functions
210
211     def get_py_c_functions(self):
212         import types
213         functions = []
214         for identifier, attr in self._get_py_visible_attrs():
215             # methods may be python wrappers to C functions
216             attr_func = getattr(attr, "__func__", attr)
217             if type(attr_func) in {types.BuiltinMethodType, types.BuiltinFunctionType}:
218                 functions.append((identifier, attr))
219         return functions
220
221     def __str__(self):
222
223         txt = ""
224         txt += self.identifier
225         if self.base:
226             txt += "(%s)" % self.base.identifier
227         txt += ": " + self.description + "\n"
228
229         for prop in self.properties:
230             txt += prop.__repr__() + "\n"
231
232         for func in self.functions:
233             txt += func.__repr__() + "\n"
234
235         return txt
236
237
238 class InfoPropertyRNA:
239     __slots__ = (
240         "bl_prop",
241         "srna",
242         "identifier",
243         "name",
244         "description",
245         "default_str",
246         "default",
247         "enum_items",
248         "min",
249         "max",
250         "array_length",
251         "array_dimensions",
252         "collection_type",
253         "type",
254         "fixed_type",
255         "is_argument_optional",
256         "is_enum_flag",
257         "is_required",
258         "is_readonly",
259         "is_never_none",
260     )
261     global_lookup = {}
262
263     def __init__(self, rna_prop):
264         self.bl_prop = rna_prop
265         self.identifier = rna_prop.identifier
266         self.name = rna_prop.name
267         self.description = rna_prop.description.strip()
268         self.default_str = "<UNKNOWN>"
269
270     def build(self):
271         rna_prop = self.bl_prop
272
273         self.enum_items = []
274         self.min = getattr(rna_prop, "hard_min", -1)
275         self.max = getattr(rna_prop, "hard_max", -1)
276         self.array_length = getattr(rna_prop, "array_length", 0)
277         self.array_dimensions = getattr(rna_prop, "array_dimensions", ())[:]
278         self.collection_type = GetInfoStructRNA(rna_prop.srna)
279         self.is_required = rna_prop.is_required
280         self.is_readonly = rna_prop.is_readonly
281         self.is_never_none = rna_prop.is_never_none
282         self.is_argument_optional = rna_prop.is_argument_optional
283
284         self.type = rna_prop.type.lower()
285         fixed_type = getattr(rna_prop, "fixed_type", "")
286         if fixed_type:
287             self.fixed_type = GetInfoStructRNA(fixed_type)  # valid for pointer/collections
288         else:
289             self.fixed_type = None
290
291         if self.type == "enum":
292             self.enum_items[:] = [(item.identifier, item.name, item.description) for item in rna_prop.enum_items]
293             self.is_enum_flag = rna_prop.is_enum_flag
294         else:
295             self.is_enum_flag = False
296
297         self.default_str = ""  # fallback
298
299         if self.array_length:
300             self.default = tuple(getattr(rna_prop, "default_array", ()))
301             if self.array_dimensions[1] != 0:  # Multi-dimensional array, convert default flat one accordingly.
302                 self.default_str = tuple(float_as_string(v) if self.type == "float" else str(v) for v in self.default)
303                 for dim in self.array_dimensions[::-1]:
304                     if dim != 0:
305                         self.default = tuple(zip(*((iter(self.default),) * dim)))
306                         self.default_str = tuple(
307                             "(%s)" % ", ".join(s for s in b) for b in zip(*((iter(self.default_str),) * dim))
308                         )
309                 self.default_str = self.default_str[0]
310         elif self.type == "enum" and self.is_enum_flag:
311             self.default = getattr(rna_prop, "default_flag", set())
312         else:
313             self.default = getattr(rna_prop, "default", None)
314
315         if self.type == "pointer":
316             # pointer has no default, just set as None
317             self.default = None
318             self.default_str = "None"
319         elif self.type == "string":
320             self.default_str = "\"%s\"" % self.default
321         elif self.type == "enum":
322             if self.is_enum_flag:
323                 # self.default_str = "%r" % self.default  # repr or set()
324                 self.default_str = "{%s}" % repr(list(sorted(self.default)))[1:-1]
325             else:
326                 self.default_str = "'%s'" % self.default
327         elif self.array_length:
328             if self.array_dimensions[1] == 0:  # single dimension array, we already took care of multi-dimensions ones.
329                 # special case for floats
330                 if self.type == "float" and len(self.default) > 0:
331                     self.default_str = "(%s)" % ", ".join(float_as_string(f) for f in self.default)
332                 else:
333                     self.default_str = str(self.default)
334         else:
335             if self.type == "float":
336                 self.default_str = float_as_string(self.default)
337             else:
338                 self.default_str = str(self.default)
339
340         self.srna = GetInfoStructRNA(rna_prop.srna)  # valid for pointer/collections
341
342     def get_arg_default(self, force=True):
343         default = self.default_str
344         if default and (force or self.is_required is False):
345             return "%s=%s" % (self.identifier, default)
346         return self.identifier
347
348     def get_type_description(self, as_ret=False, as_arg=False, class_fmt="%s", collection_id="Collection"):
349         type_str = ""
350         if self.fixed_type is None:
351             type_str += self.type
352             if self.array_length:
353                 if self.array_dimensions[1] != 0:
354                     type_str += " multi-dimensional array of %s items" % (
355                         " * ".join(str(d) for d in self.array_dimensions if d != 0)
356                     )
357                 else:
358                     type_str += " array of %d items" % (self.array_length)
359
360             if self.type in {"float", "int"}:
361                 type_str += " in [%s, %s]" % (range_str(self.min), range_str(self.max))
362             elif self.type == "enum":
363                 if self.is_enum_flag:
364                     type_str += " set in {%s}" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
365                 else:
366                     type_str += " in [%s]" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
367
368             if not (as_arg or as_ret):
369                 # write default property, ignore function args for this
370                 if self.type != "pointer":
371                     if self.default_str:
372                         type_str += ", default %s" % self.default_str
373
374         else:
375             if self.type == "collection":
376                 if self.collection_type:
377                     collection_str = (class_fmt % self.collection_type.identifier) + (" %s of " % collection_id)
378                 else:
379                     collection_str = "%s of " % collection_id
380             else:
381                 collection_str = ""
382
383             type_str += collection_str + (class_fmt % self.fixed_type.identifier)
384
385         # setup qualifiers for this value.
386         type_info = []
387         if as_ret:
388             pass
389         elif as_arg:
390             if not self.is_required:
391                 type_info.append("optional")
392             if self.is_argument_optional:
393                 type_info.append("optional argument")
394         else:  # readonly is only useful for self's, not args
395             if self.is_readonly:
396                 type_info.append("readonly")
397
398         if self.is_never_none:
399             type_info.append("never None")
400
401         if type_info:
402             type_str += (", (%s)" % ", ".join(type_info))
403
404         return type_str
405
406     def __str__(self):
407         txt = ""
408         txt += " * " + self.identifier + ": " + self.description
409
410         return txt
411
412
413 class InfoFunctionRNA:
414     __slots__ = (
415         "bl_func",
416         "identifier",
417         "description",
418         "args",
419         "return_values",
420         "is_classmethod",
421     )
422     global_lookup = {}
423
424     def __init__(self, rna_func):
425         self.bl_func = rna_func
426         self.identifier = rna_func.identifier
427         # self.name = rna_func.name # functions have no name!
428         self.description = rna_func.description.strip()
429         self.is_classmethod = not rna_func.use_self
430
431         self.args = []
432         self.return_values = ()
433
434     def build(self):
435         rna_func = self.bl_func
436         parent_id = rna_func
437         self.return_values = []
438
439         for rna_prop in rna_func.parameters.values():
440             prop = GetInfoPropertyRNA(rna_prop, parent_id)
441             if rna_prop.is_output:
442                 self.return_values.append(prop)
443             else:
444                 self.args.append(prop)
445
446         self.return_values = tuple(self.return_values)
447
448     def __str__(self):
449         txt = ''
450         txt += ' * ' + self.identifier + '('
451
452         for arg in self.args:
453             txt += arg.identifier + ', '
454         txt += '): ' + self.description
455         return txt
456
457
458 class InfoOperatorRNA:
459     __slots__ = (
460         "bl_op",
461         "identifier",
462         "name",
463         "module_name",
464         "func_name",
465         "description",
466         "args",
467     )
468     global_lookup = {}
469
470     def __init__(self, rna_op):
471         self.bl_op = rna_op
472         self.identifier = rna_op.identifier
473
474         mod, name = self.identifier.split("_OT_", 1)
475         self.module_name = mod.lower()
476         self.func_name = name
477
478         # self.name = rna_func.name # functions have no name!
479         self.description = rna_op.description.strip()
480
481         self.args = []
482
483     def build(self):
484         rna_op = self.bl_op
485         parent_id = self.identifier
486         for rna_id, rna_prop in rna_op.properties.items():
487             if rna_id == "rna_type":
488                 continue
489
490             prop = GetInfoPropertyRNA(rna_prop, parent_id)
491             self.args.append(prop)
492
493     def get_location(self):
494         try:
495             op_class = getattr(bpy.types, self.identifier)
496         except AttributeError:
497             # defined in C.
498             return None, None
499         op_func = getattr(op_class, "execute", None)
500         if op_func is None:
501             op_func = getattr(op_class, "invoke", None)
502         if op_func is None:
503             op_func = getattr(op_class, "poll", None)
504
505         if op_func:
506             op_code = op_func.__code__
507             source_path = op_code.co_filename
508
509             # clear the prefix
510             for p in script_paths:
511                 source_path = source_path.split(p)[-1]
512
513             if source_path[0] in "/\\":
514                 source_path = source_path[1:]
515
516             return source_path, op_code.co_firstlineno
517         else:
518             return None, None
519
520
521 def _GetInfoRNA(bl_rna, cls, parent_id=""):
522
523     if bl_rna is None:
524         return None
525
526     key = parent_id, bl_rna.identifier
527     try:
528         return cls.global_lookup[key]
529     except KeyError:
530         instance = cls.global_lookup[key] = cls(bl_rna)
531         return instance
532
533
534 def GetInfoStructRNA(bl_rna):
535     return _GetInfoRNA(bl_rna, InfoStructRNA)
536
537
538 def GetInfoPropertyRNA(bl_rna, parent_id):
539     return _GetInfoRNA(bl_rna, InfoPropertyRNA, parent_id)
540
541
542 def GetInfoFunctionRNA(bl_rna, parent_id):
543     return _GetInfoRNA(bl_rna, InfoFunctionRNA, parent_id)
544
545
546 def GetInfoOperatorRNA(bl_rna):
547     return _GetInfoRNA(bl_rna, InfoOperatorRNA)
548
549
550 def BuildRNAInfo():
551
552     # needed on successive calls to prevent stale data access
553     for cls in (InfoStructRNA, InfoFunctionRNA, InfoOperatorRNA, InfoPropertyRNA):
554         cls.global_lookup.clear()
555     del cls
556
557     # Use for faster lookups
558     # use rna_struct.identifier as the key for each dict
559     rna_struct_dict = {}  # store identifier:rna lookups
560     rna_full_path_dict = {}  # store the result of full_rna_struct_path(rna_struct)
561     rna_children_dict = {}  # store all rna_structs nested from here
562     rna_references_dict = {}  # store a list of rna path strings that reference this type
563     # rna_functions_dict = {}  # store all functions directly in this type (not inherited)
564
565     def full_rna_struct_path(rna_struct):
566         """
567         Needed when referencing one struct from another
568         """
569         nested = rna_struct.nested
570         if nested:
571             return "%s.%s" % (full_rna_struct_path(nested), rna_struct.identifier)
572         else:
573             return rna_struct.identifier
574
575     # def write_func(rna_func, ident):
576     def base_id(rna_struct):
577         try:
578             return rna_struct.base.identifier
579         except:
580             return ""  # invalid id
581
582     #structs = [(base_id(rna_struct), rna_struct.identifier, rna_struct) for rna_struct in bpy.doc.structs.values()]
583     '''
584     structs = []
585     for rna_struct in bpy.doc.structs.values():
586         structs.append( (base_id(rna_struct), rna_struct.identifier, rna_struct) )
587     '''
588     structs = []
589
590     def _bpy_types_iterator():
591         names_unique = set()
592         rna_type_list = []
593         for rna_type_name in dir(bpy.types):
594             names_unique.add(rna_type_name)
595             rna_type = getattr(bpy.types, rna_type_name)
596             rna_struct = getattr(rna_type, "bl_rna", None)
597             if rna_struct is not None:
598                 rna_type_list.append(rna_type)
599                 yield (rna_type_name, rna_struct)
600             else:
601                 print("Ignoring", rna_type_name)
602
603         # Now, there are some sub-classes in add-ons we also want to include.
604         # Cycles for e.g. these are referenced from the Scene, but not part of
605         # bpy.types module.
606         # Include all sub-classes we didn't already get from 'bpy.types'.
607         i = 0
608         while i < len(rna_type_list):
609             rna_type = rna_type_list[i]
610             for rna_sub_type in rna_type.__subclasses__():
611                 rna_sub_struct = getattr(rna_sub_type, "bl_rna", None)
612                 if rna_sub_struct is not None:
613                     rna_sub_type_name = rna_sub_struct.identifier
614                     if rna_sub_type_name not in names_unique:
615                         names_unique.add(rna_sub_type_name)
616                         rna_type_list.append(rna_sub_type)
617                         # The bl_idname may not match the class name in the file.
618                         # Always use the 'bl_idname' because using the Python
619                         # class name causes confusion - having two names for the same thing.
620                         # Since having two names for the same thing is trickier to support
621                         # without a significant benefit.
622                         yield (rna_sub_type_name, rna_sub_struct)
623             i += 1
624
625     for (rna_type_name, rna_struct) in _bpy_types_iterator():
626         # if not rna_type_name.startswith('__'):
627
628         identifier = rna_struct.identifier
629
630         if not rna_id_ignore(identifier):
631             structs.append((base_id(rna_struct), identifier, rna_struct))
632
633             # Simple lookup
634             rna_struct_dict[identifier] = rna_struct
635
636             # Store full rna path 'GameObjectSettings' -> 'Object.GameObjectSettings'
637             rna_full_path_dict[identifier] = full_rna_struct_path(rna_struct)
638
639             # Store a list of functions, remove inherited later
640             # NOT USED YET
641             ## rna_functions_dict[identifier] = get_direct_functions(rna_struct)
642
643             # fill in these later
644             rna_children_dict[identifier] = []
645             rna_references_dict[identifier] = []
646
647     del _bpy_types_iterator
648
649     structs.sort()  # not needed but speeds up sort below, setting items without an inheritance first
650
651     # Arrange so classes are always defined in the correct order
652     deps_ok = False
653     while deps_ok is False:
654         deps_ok = True
655         rna_done = set()
656
657         for i, (rna_base, identifier, rna_struct) in enumerate(structs):
658
659             rna_done.add(identifier)
660
661             if rna_base and rna_base not in rna_done:
662                 deps_ok = False
663                 data = structs.pop(i)
664                 ok = False
665                 while i < len(structs):
666                     if structs[i][1] == rna_base:
667                         structs.insert(i + 1, data)  # insert after the item we depend on.
668                         ok = True
669                         break
670                     i += 1
671
672                 if not ok:
673                     print('Dependancy "%s" could not be found for "%s"' % (identifier, rna_base))
674
675                 break
676
677     # Done ordering structs
678
679     # precalculate vars to avoid a lot of looping
680     for (rna_base, identifier, rna_struct) in structs:
681
682         # rna_struct_path = full_rna_struct_path(rna_struct)
683         rna_struct_path = rna_full_path_dict[identifier]
684
685         for rna_prop in get_direct_properties(rna_struct):
686             rna_prop_identifier = rna_prop.identifier
687
688             if rna_prop_identifier == 'RNA' or rna_id_ignore(rna_prop_identifier):
689                 continue
690
691             for rna_prop_ptr in (getattr(rna_prop, "fixed_type", None), getattr(rna_prop, "srna", None)):
692                 # Does this property point to me?
693                 if rna_prop_ptr and rna_prop_ptr.identifier in rna_references_dict:
694                     rna_references_dict[rna_prop_ptr.identifier].append(
695                         "%s.%s" % (rna_struct_path, rna_prop_identifier))
696
697         for rna_func in get_direct_functions(rna_struct):
698             for rna_prop_identifier, rna_prop in rna_func.parameters.items():
699
700                 if rna_prop_identifier == 'RNA' or rna_id_ignore(rna_prop_identifier):
701                     continue
702
703                 rna_prop_ptr = getattr(rna_prop, "fixed_type", None)
704
705                 # Does this property point to me?
706                 if rna_prop_ptr and rna_prop_ptr.identifier in rna_references_dict:
707                     rna_references_dict[rna_prop_ptr.identifier].append(
708                         "%s.%s" % (rna_struct_path, rna_func.identifier))
709
710         # Store nested children
711         nested = rna_struct.nested
712         if nested:
713             rna_children_dict[nested.identifier].append(rna_struct)
714
715     # Sort the refs, just reads nicer
716     for rna_refs in rna_references_dict.values():
717         rna_refs.sort()
718
719     info_structs = []
720     for (rna_base, identifier, rna_struct) in structs:
721         # if rna_struct.nested:
722         #     continue
723
724         #write_struct(rna_struct, '')
725         info_struct = GetInfoStructRNA(rna_struct)
726         if rna_base:
727             info_struct.base = GetInfoStructRNA(rna_struct_dict[rna_base])
728         info_struct.nested = GetInfoStructRNA(rna_struct.nested)
729         info_struct.children[:] = rna_children_dict[identifier]
730         info_struct.references[:] = rna_references_dict[identifier]
731         info_struct.full_path = rna_full_path_dict[identifier]
732
733         info_structs.append(info_struct)
734
735     for rna_info_prop in InfoPropertyRNA.global_lookup.values():
736         rna_info_prop.build()
737
738     for rna_info_prop in InfoFunctionRNA.global_lookup.values():
739         rna_info_prop.build()
740
741     done_keys = set()
742     new_keys = set(InfoStructRNA.global_lookup.keys())
743     while new_keys:
744         for rna_key in new_keys:
745             rna_info = InfoStructRNA.global_lookup[rna_key]
746             rna_info.build()
747             for prop in rna_info.properties:
748                 prop.build()
749             for func in rna_info.functions:
750                 func.build()
751                 for prop in func.args:
752                     prop.build()
753                 for prop in func.return_values:
754                     prop.build()
755         done_keys |= new_keys
756         new_keys = set(InfoStructRNA.global_lookup.keys()) - done_keys
757
758     # there are too many invalid defaults, unless we intend to fix, leave this off
759     if 0:
760         for rna_info in InfoStructRNA.global_lookup.values():
761             for prop in rna_info.properties:
762                 # ERROR CHECK
763                 default = prop.default
764                 if type(default) in {float, int}:
765                     if default < prop.min or default > prop.max:
766                         print("\t %s.%s, %s not in [%s - %s]" %
767                               (rna_info.identifier, prop.identifier, default, prop.min, prop.max))
768
769     # now for operators
770     op_mods = dir(bpy.ops)
771
772     for op_mod_name in sorted(op_mods):
773         if op_mod_name.startswith('__'):
774             continue
775
776         op_mod = getattr(bpy.ops, op_mod_name)
777         operators = dir(op_mod)
778         for op in sorted(operators):
779             try:
780                 rna_prop = getattr(op_mod, op).get_rna_type()
781             except AttributeError:
782                 rna_prop = None
783             except TypeError:
784                 rna_prop = None
785
786             if rna_prop:
787                 GetInfoOperatorRNA(rna_prop)
788
789     for rna_info in InfoOperatorRNA.global_lookup.values():
790         rna_info.build()
791         for rna_prop in rna_info.args:
792             rna_prop.build()
793
794     # for rna_info in InfoStructRNA.global_lookup.values():
795     #     print(rna_info)
796     return (
797         InfoStructRNA.global_lookup,
798         InfoFunctionRNA.global_lookup,
799         InfoOperatorRNA.global_lookup,
800         InfoPropertyRNA.global_lookup,
801     )
802
803
804 def main():
805     struct = BuildRNAInfo()[0]
806     data = []
807     for _struct_id, v in sorted(struct.items()):
808         struct_id_str = v.identifier  # "".join(sid for sid in struct_id if struct_id)
809
810         for base in v.get_bases():
811             struct_id_str = base.identifier + "|" + struct_id_str
812
813         props = [(prop.identifier, prop) for prop in v.properties]
814         for _prop_id, prop in sorted(props):
815             # if prop.type == "boolean":
816             #     continue
817             prop_type = prop.type
818             if prop.array_length > 0:
819                 prop_type += "[%d]" % prop.array_length
820
821             data.append(
822                 "%s.%s -> %s:    %s%s    %s" %
823                 (struct_id_str, prop.identifier, prop.identifier, prop_type,
824                  ", (read-only)" if prop.is_readonly else "", prop.description))
825         data.sort()
826
827     if bpy.app.background:
828         import sys
829         sys.stderr.write("\n".join(data))
830         sys.stderr.write("\n\nEOF\n")
831     else:
832         text = bpy.data.texts.new(name="api.py")
833         text.from_string("\n".join(data))
834
835
836 if __name__ == "__main__":
837     main()