2011-09-20 21:22:19 +00:00
|
|
|
# $Id$
|
2011-09-20 17:44:45 +00:00
|
|
|
# ***** BEGIN GPL LICENSE BLOCK *****
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or
|
|
|
|
# modify it under the terms of the GNU General Public License
|
|
|
|
# as published by the Free Software Foundation; either version 2
|
|
|
|
# of the License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
#
|
|
|
|
# ***** END GPL LICENSE BLOCK *****
|
|
|
|
|
2011-10-05 03:39:22 +00:00
|
|
|
# <pep8-80 compliant>
|
2011-09-20 17:44:45 +00:00
|
|
|
|
|
|
|
# Write out messages.txt from blender
|
|
|
|
|
|
|
|
# Execite:
|
|
|
|
# blender --background --python po/update_msg.py
|
|
|
|
|
|
|
|
import os
|
|
|
|
|
2011-09-29 06:34:58 +00:00
|
|
|
CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
|
2011-09-20 17:44:45 +00:00
|
|
|
SOURCE_DIR = os.path.normpath(os.path.abspath(os.path.join(CURRENT_DIR, "..")))
|
|
|
|
|
|
|
|
FILE_NAME_MESSAGES = os.path.join(CURRENT_DIR, "messages.txt")
|
2011-09-29 17:15:58 +00:00
|
|
|
COMMENT_PREFIX = "#~ "
|
2011-09-20 17:44:45 +00:00
|
|
|
|
|
|
|
|
2011-09-21 12:23:23 +00:00
|
|
|
def dump_messages_rna(messages):
|
2011-09-20 17:44:45 +00:00
|
|
|
import bpy
|
2011-09-21 13:16:42 +00:00
|
|
|
|
2011-10-05 03:39:22 +00:00
|
|
|
def classBlackList():
|
|
|
|
blacklist_rna_class = [
|
|
|
|
# core classes
|
|
|
|
"Context", "Event", "Function", "UILayout",
|
|
|
|
"BlendData",
|
|
|
|
# registerable classes
|
|
|
|
"Panel", "Menu", "Header", "RenderEngine",
|
2011-10-07 08:57:08 +00:00
|
|
|
"Operator", "OperatorMacro", "Macro",
|
|
|
|
"KeyingSetInfo", "UnknownType",
|
2011-10-05 03:39:22 +00:00
|
|
|
# window classes
|
|
|
|
"WindowManager", "Window"
|
|
|
|
]
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Collect internal operators
|
|
|
|
|
|
|
|
# extend with all internal operators
|
|
|
|
# note that this uses internal api introspection functions
|
|
|
|
# all possible operator names
|
|
|
|
op_names = list(sorted(set(
|
|
|
|
[cls.bl_rna.identifier for cls in
|
|
|
|
bpy.types.OperatorProperties.__subclasses__()] +
|
|
|
|
[cls.bl_rna.identifier for cls in
|
|
|
|
bpy.types.Operator.__subclasses__()] +
|
|
|
|
[cls.bl_rna.identifier for cls in
|
|
|
|
bpy.types.OperatorMacro.__subclasses__()]
|
|
|
|
)))
|
|
|
|
|
|
|
|
get_inatance = __import__("_bpy").ops.get_instance
|
|
|
|
path_resolve = type(bpy.context).__base__.path_resolve
|
|
|
|
for idname in op_names:
|
|
|
|
op = get_inatance(idname)
|
|
|
|
if 'INTERNAL' in path_resolve(op, "bl_options"):
|
|
|
|
blacklist_rna_class.append(idname)
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Collect builtin classes we dont need to doc
|
|
|
|
blacklist_rna_class.append("Property")
|
|
|
|
blacklist_rna_class.extend(
|
|
|
|
[cls.__name__ for cls in
|
|
|
|
bpy.types.Property.__subclasses__()])
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Collect classes which are attached to collections, these are api
|
|
|
|
# access only.
|
|
|
|
collection_props = set()
|
|
|
|
for cls_id in dir(bpy.types):
|
|
|
|
cls = getattr(bpy.types, cls_id)
|
|
|
|
for prop in cls.bl_rna.properties:
|
|
|
|
if prop.type == 'COLLECTION':
|
|
|
|
prop_cls = prop.srna
|
|
|
|
if prop_cls is not None:
|
|
|
|
collection_props.add(prop_cls.identifier)
|
|
|
|
blacklist_rna_class.extend(sorted(collection_props))
|
|
|
|
|
|
|
|
return blacklist_rna_class
|
|
|
|
|
|
|
|
blacklist_rna_class = classBlackList()
|
|
|
|
|
|
|
|
def filterRNA(bl_rna):
|
|
|
|
id = bl_rna.identifier
|
|
|
|
if id in blacklist_rna_class:
|
|
|
|
print(" skipping", id)
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2011-09-20 17:44:45 +00:00
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Function definitions
|
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
def walkProperties(bl_rna):
|
2011-09-20 17:44:45 +00:00
|
|
|
import bpy
|
2011-09-29 17:15:58 +00:00
|
|
|
|
|
|
|
# get our parents properties not to export them multiple times
|
|
|
|
bl_rna_base = bl_rna.base
|
|
|
|
if bl_rna_base:
|
|
|
|
bl_rna_base_props = bl_rna_base.properties.values()
|
|
|
|
else:
|
|
|
|
bl_rna_base_props = ()
|
|
|
|
|
|
|
|
for prop in bl_rna.properties:
|
|
|
|
# only write this property is our parent hasn't got it.
|
|
|
|
if prop in bl_rna_base_props:
|
|
|
|
continue
|
|
|
|
if prop.identifier == "rna_type":
|
|
|
|
continue
|
|
|
|
|
|
|
|
msgsrc = "bpy.types.%s.%s" % (bl_rna.identifier, prop.identifier)
|
2011-10-09 12:41:42 +00:00
|
|
|
if prop.name and prop.name != prop.identifier:
|
|
|
|
messages.setdefault(prop.name, []).append(msgsrc)
|
|
|
|
if prop.description:
|
|
|
|
messages.setdefault(prop.description, []).append(msgsrc)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
|
|
|
if isinstance(prop, bpy.types.EnumProperty):
|
|
|
|
for item in prop.enum_items:
|
2011-09-29 17:15:58 +00:00
|
|
|
msgsrc = "bpy.types.%s.%s, '%s'" % (bl_rna.identifier,
|
|
|
|
prop.identifier,
|
|
|
|
item.identifier,
|
|
|
|
)
|
2011-10-09 12:41:42 +00:00
|
|
|
# Here identifier and name can be the same!
|
2011-10-15 05:01:47 +00:00
|
|
|
if item.name: # and item.name != item.identifier:
|
|
|
|
messages.setdefault(item.name,
|
|
|
|
[]).append(msgsrc)
|
2011-10-09 12:41:42 +00:00
|
|
|
if item.description:
|
2011-10-15 05:01:47 +00:00
|
|
|
messages.setdefault(item.description,
|
|
|
|
[]).append(msgsrc)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
2011-09-21 12:23:23 +00:00
|
|
|
def walkRNA(bl_rna):
|
2011-10-05 03:39:22 +00:00
|
|
|
|
|
|
|
if filterRNA(bl_rna):
|
|
|
|
return
|
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
msgsrc = "bpy.types.%s" % bl_rna.identifier
|
|
|
|
|
2011-09-20 17:44:45 +00:00
|
|
|
if bl_rna.name and bl_rna.name != bl_rna.identifier:
|
2011-09-29 17:15:58 +00:00
|
|
|
messages.setdefault(bl_rna.name, []).append(msgsrc)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
|
|
|
if bl_rna.description:
|
2011-09-29 17:15:58 +00:00
|
|
|
messages.setdefault(bl_rna.description, []).append(msgsrc)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
2011-09-30 09:25:53 +00:00
|
|
|
if hasattr(bl_rna, 'bl_label') and bl_rna.bl_label:
|
|
|
|
messages.setdefault(bl_rna.bl_label, []).append(msgsrc)
|
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
walkProperties(bl_rna)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
2011-09-21 12:23:23 +00:00
|
|
|
def walkClass(cls):
|
|
|
|
walkRNA(cls.bl_rna)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
def walk_keymap_hierarchy(hier, msgsrc_prev):
|
2011-09-20 17:44:45 +00:00
|
|
|
for lvl in hier:
|
2011-09-29 17:15:58 +00:00
|
|
|
msgsrc = "%s.%s" % (msgsrc_prev, lvl[1])
|
|
|
|
messages.setdefault(lvl[0], []).append(msgsrc)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
|
|
|
if lvl[3]:
|
2011-09-29 17:15:58 +00:00
|
|
|
walk_keymap_hierarchy(lvl[3], msgsrc)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Dump Messages
|
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
def full_class_id(cls):
|
|
|
|
""" gives us 'ID.Lamp.AreaLamp' which is best for sorting.
|
|
|
|
"""
|
|
|
|
cls_id = ""
|
|
|
|
bl_rna = cls.bl_rna
|
|
|
|
while bl_rna:
|
|
|
|
cls_id = "%s.%s" % (bl_rna.identifier, cls_id)
|
|
|
|
bl_rna = bl_rna.base
|
|
|
|
return cls_id
|
|
|
|
|
|
|
|
cls_list = type(bpy.context).__base__.__subclasses__()
|
|
|
|
cls_list.sort(key=full_class_id)
|
|
|
|
for cls in cls_list:
|
2011-09-21 12:23:23 +00:00
|
|
|
walkClass(cls)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
cls_list = bpy.types.Space.__subclasses__()
|
|
|
|
cls_list.sort(key=full_class_id)
|
|
|
|
for cls in cls_list:
|
2011-09-21 12:23:23 +00:00
|
|
|
walkClass(cls)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
cls_list = bpy.types.Operator.__subclasses__()
|
|
|
|
cls_list.sort(key=full_class_id)
|
|
|
|
for cls in cls_list:
|
2011-09-21 12:23:23 +00:00
|
|
|
walkClass(cls)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
2011-09-30 09:25:53 +00:00
|
|
|
cls_list = bpy.types.OperatorProperties.__subclasses__()
|
|
|
|
cls_list.sort(key=full_class_id)
|
|
|
|
for cls in cls_list:
|
|
|
|
walkClass(cls)
|
2011-09-20 17:44:45 +00:00
|
|
|
|
2011-09-30 09:25:53 +00:00
|
|
|
cls_list = bpy.types.Menu.__subclasses__()
|
|
|
|
cls_list.sort(key=full_class_id)
|
|
|
|
for cls in cls_list:
|
|
|
|
walkClass(cls)
|
2011-09-21 13:16:42 +00:00
|
|
|
|
2011-09-30 09:25:53 +00:00
|
|
|
from bpy_extras.keyconfig_utils import KM_HIERARCHY
|
2011-09-20 17:44:45 +00:00
|
|
|
|
2011-09-30 09:25:53 +00:00
|
|
|
walk_keymap_hierarchy(KM_HIERARCHY, "KM_HIERARCHY")
|
2011-09-20 17:44:45 +00:00
|
|
|
|
|
|
|
|
2011-09-21 13:16:42 +00:00
|
|
|
def dump_messages_pytext(messages):
|
|
|
|
""" dumps text inlined in the python user interface: eg.
|
|
|
|
|
|
|
|
layout.prop("someprop", text="My Name")
|
|
|
|
"""
|
|
|
|
import ast
|
|
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Gather function names
|
|
|
|
|
|
|
|
import bpy
|
|
|
|
# key: func_id
|
|
|
|
# val: [(arg_kw, arg_pos), (arg_kw, arg_pos), ...]
|
|
|
|
func_translate_args = {}
|
|
|
|
|
|
|
|
# so far only 'text' keywords, but we may want others translated later
|
|
|
|
translate_kw = ("text", )
|
|
|
|
|
|
|
|
for func_id, func in bpy.types.UILayout.bl_rna.functions.items():
|
|
|
|
# check it has a 'text' argument
|
|
|
|
for (arg_pos, (arg_kw, arg)) in enumerate(func.parameters.items()):
|
|
|
|
if ((arg_kw in translate_kw) and
|
|
|
|
(arg.is_output == False) and
|
|
|
|
(arg.type == 'STRING')):
|
|
|
|
|
|
|
|
func_translate_args.setdefault(func_id, []).append((arg_kw,
|
|
|
|
arg_pos))
|
|
|
|
# print(func_translate_args)
|
|
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Function definitions
|
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
def extract_strings(fp_rel, node_container):
|
2011-09-21 13:16:42 +00:00
|
|
|
""" Recursively get strings, needed incase we have "Blah" + "Blah",
|
|
|
|
passed as an argument in that case it wont evaluate to a string.
|
|
|
|
"""
|
2011-09-29 17:15:58 +00:00
|
|
|
|
2011-09-21 13:16:42 +00:00
|
|
|
for node in ast.walk(node_container):
|
|
|
|
if type(node) == ast.Str:
|
|
|
|
eval_str = ast.literal_eval(node)
|
|
|
|
if eval_str:
|
2011-09-29 17:15:58 +00:00
|
|
|
# print("%s:%d: %s" % (fp, node.lineno, eval_str))
|
|
|
|
msgsrc = "%s:%s" % (fp_rel, node.lineno)
|
|
|
|
messages.setdefault(eval_str, []).append(msgsrc)
|
2011-09-21 13:16:42 +00:00
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
def extract_strings_from_file(fp):
|
|
|
|
filedata = open(fp, 'r', encoding="utf8")
|
|
|
|
root_node = ast.parse(filedata.read(), fp, 'exec')
|
2011-09-21 13:16:42 +00:00
|
|
|
filedata.close()
|
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
fp_rel = os.path.relpath(fp, SOURCE_DIR)
|
|
|
|
|
2011-09-21 13:16:42 +00:00
|
|
|
for node in ast.walk(root_node):
|
|
|
|
if type(node) == ast.Call:
|
|
|
|
# print("found function at")
|
2011-09-29 17:15:58 +00:00
|
|
|
# print("%s:%d" % (fp, node.lineno))
|
2011-09-21 13:16:42 +00:00
|
|
|
|
|
|
|
# lambda's
|
|
|
|
if type(node.func) == ast.Name:
|
|
|
|
continue
|
|
|
|
|
|
|
|
# getattr(self, con.type)(context, box, con)
|
|
|
|
if not hasattr(node.func, "attr"):
|
|
|
|
continue
|
|
|
|
|
|
|
|
translate_args = func_translate_args.get(node.func.attr, ())
|
|
|
|
|
|
|
|
# do nothing if not found
|
|
|
|
for arg_kw, arg_pos in translate_args:
|
|
|
|
if arg_pos < len(node.args):
|
2011-09-29 17:15:58 +00:00
|
|
|
extract_strings(fp_rel, node.args[arg_pos])
|
2011-09-21 13:16:42 +00:00
|
|
|
else:
|
|
|
|
for kw in node.keywords:
|
|
|
|
if kw.arg == arg_kw:
|
2011-09-29 17:15:58 +00:00
|
|
|
extract_strings(fp_rel, kw.value)
|
2011-09-21 13:16:42 +00:00
|
|
|
|
|
|
|
# -------------------------------------------------------------------------
|
|
|
|
# Dump Messages
|
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
mod_dir = os.path.join(SOURCE_DIR,
|
|
|
|
"release",
|
|
|
|
"scripts",
|
|
|
|
"startup",
|
|
|
|
"bl_ui")
|
2011-09-21 13:16:42 +00:00
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
files = [os.path.join(mod_dir, fn)
|
|
|
|
for fn in sorted(os.listdir(mod_dir))
|
|
|
|
if not fn.startswith("_")
|
|
|
|
if fn.endswith("py")
|
2011-09-21 13:16:42 +00:00
|
|
|
]
|
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
for fp in files:
|
|
|
|
extract_strings_from_file(fp)
|
2011-09-21 13:16:42 +00:00
|
|
|
|
|
|
|
|
2011-09-21 12:23:23 +00:00
|
|
|
def dump_messages():
|
2011-09-29 17:15:58 +00:00
|
|
|
|
|
|
|
def filter_message(msg):
|
|
|
|
|
|
|
|
# check for strings like ": %d"
|
|
|
|
msg_test = msg
|
|
|
|
for ignore in ("%d", "%s", "%r", # string formatting
|
|
|
|
"*", ".", "(", ")", "-", "/", "\\", "+", ":", "#", "%"
|
|
|
|
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
|
|
|
|
"x", # used on its own eg: 100x200
|
|
|
|
"X", "Y", "Z", # used alone. no need to include
|
|
|
|
):
|
|
|
|
msg_test = msg_test.replace(ignore, "")
|
|
|
|
msg_test = msg_test.strip()
|
|
|
|
if not msg_test:
|
|
|
|
# print("Skipping: '%s'" % msg)
|
|
|
|
return True
|
|
|
|
|
|
|
|
# we could filter out different strings here
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
if 1:
|
|
|
|
import collections
|
|
|
|
messages = collections.OrderedDict()
|
|
|
|
else:
|
|
|
|
messages = {}
|
|
|
|
|
|
|
|
messages[""] = []
|
2011-09-21 12:23:23 +00:00
|
|
|
|
2011-09-21 13:16:42 +00:00
|
|
|
# get strings from RNA
|
2011-09-21 12:23:23 +00:00
|
|
|
dump_messages_rna(messages)
|
|
|
|
|
2011-09-21 13:16:42 +00:00
|
|
|
# get strings from UI layout definitions text="..." args
|
|
|
|
dump_messages_pytext(messages)
|
|
|
|
|
2011-09-29 17:15:58 +00:00
|
|
|
del messages[""]
|
2011-09-20 17:44:45 +00:00
|
|
|
|
2011-09-21 12:23:23 +00:00
|
|
|
message_file = open(FILE_NAME_MESSAGES, 'w', encoding="utf8")
|
2011-09-29 17:15:58 +00:00
|
|
|
# message_file.writelines("\n".join(sorted(messages)))
|
|
|
|
|
|
|
|
for key, value in messages.items():
|
|
|
|
|
|
|
|
# filter out junk values
|
|
|
|
if filter_message(key):
|
|
|
|
continue
|
|
|
|
|
|
|
|
for msgsrc in value:
|
|
|
|
message_file.write("%s%s\n" % (COMMENT_PREFIX, msgsrc))
|
|
|
|
message_file.write("%s\n" % key)
|
|
|
|
|
2011-09-21 12:23:23 +00:00
|
|
|
message_file.close()
|
|
|
|
|
|
|
|
print("Written %d messages to: %r" % (len(messages), FILE_NAME_MESSAGES))
|
2011-09-20 17:44:45 +00:00
|
|
|
|
2011-09-21 13:16:42 +00:00
|
|
|
|
2011-09-20 17:44:45 +00:00
|
|
|
def main():
|
|
|
|
|
|
|
|
try:
|
|
|
|
import bpy
|
|
|
|
except ImportError:
|
|
|
|
print("This script must run from inside blender")
|
|
|
|
return
|
|
|
|
|
|
|
|
dump_messages()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
print("\n\n *** Running %r *** \n" % __file__)
|
|
|
|
main()
|