diff --git a/release/scripts/modules/animsys_refactor.py b/release/scripts/modules/animsys_refactor.py new file mode 100755 index 00000000000..8cc91873b0e --- /dev/null +++ b/release/scripts/modules/animsys_refactor.py @@ -0,0 +1,201 @@ +# ##### 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 ##### + +# + +""" +This module has utility functions for renaming +rna values in fcurves and drivers. + +The main function to use is: update_data_paths(...) +""" + +IS_TESTING = False + +class DataPathBuilder(object): + __slots__ = ("data_path", ) + """ Dummy class used to parse fcurve and driver data paths. + """ + def __init__(self, attrs): + self.data_path = attrs + + def __getattr__(self, attr): + str_value = ".%s" % attr + return DataPathBuilder(self.data_path + (str_value, )) + + def __getitem__(self, key): + str_value = '["%s"]' % key + return DataPathBuilder(self.data_path + (str_value, )) + + def resolve(self, real_base, rna_update_from_map=None): + """ Return (attribute, value) pairs. + """ + pairs = [] + base = real_base + for item in self.data_path: + if base is not Ellipsis: + try: + # this only works when running with an old blender + # where the old path will resolve + base = eval("base" + item) + except: + base_new = Ellipsis + # guess the new name + if item.startswith("."): + for item_new in rna_update_from_map.get(item[1:], ()): + try: + print("base." + item_new) + base_new = eval("base." + item_new) + break # found, dont keep looking + except: + pass + + if base_new is Ellipsis: + print("Failed to resolve data path:", self.data_path) + base = base_new + + pairs.append((item, base)) + return pairs + +import bpy + + +def id_iter(): + type_iter = type(bpy.data.objects) + + for attr in dir(bpy.data): + data_iter = getattr(bpy.data, attr, None) + if type(data_iter) == type_iter: + for id_data in data_iter: + if id_data.library is None: + yield id_data + + +def anim_data_actions(anim_data): + actions = [] + actions.append(anim_data.action) + for track in anim_data.nla_tracks: + for strip in track.strips: + actions.append(strip.action) + + # filter out None + return [act for act in actions if act] + + +def classes_recursive(base_type, clss=None): + if clss is None: + clss = [base_type] + else: + clss.append(base_type) + + for base_type_iter in base_type.__bases__: + if base_type_iter is not object: + classes_recursive(base_type_iter, clss) + + return clss + + +def find_path_new(id_data, data_path, rna_update_dict, rna_update_from_map): + # ignore ID props for now + if data_path.startswith("["): + return data_path + + # recursive path fixing, likely will be one in most cases. + data_path_builder = eval("DataPathBuilder(tuple())." + data_path) + data_resolve = data_path_builder.resolve(id_data, rna_update_from_map) + + path_new = [pair[0] for pair in data_resolve] + + # print(data_resolve) + data_base = id_data + + for i, (attr, data) in enumerate(data_resolve): + if data is Ellipsis: + break + + if attr.startswith("."): + # try all classes + for data_base_type in classes_recursive(type(data_base)): + attr_new = rna_update_dict.get(data_base_type.__name__, {}).get(attr[1:]) + if attr_new: + path_new[i] = "." + attr_new + + # set this as the base for further properties + data_base = data + + data_path_new = "".join(path_new)[1:] # skip the first "." + return data_path_new + + +def update_data_paths(rna_update): + ''' rna_update triple [(class_name, from, to), ...] + ''' + + # make a faster lookup dict + rna_update_dict = {} + for ren_class, ren_from, ren_to in rna_update: + rna_update_dict.setdefault(ren_class, {})[ren_from] = ren_to + + rna_update_from_map = {} + for ren_class, ren_from, ren_to in rna_update: + rna_update_from_map.setdefault(ren_from, []).append(ren_to) + + for id_data in id_iter(): + anim_data = getattr(id_data, "animation_data", None) + if anim_data is None: + continue + + for fcurve in anim_data.drivers: + for var in fcurve.driver.variables: + if var.type == 'SINGLE_PROP': + for tar in var.targets: + id_data_other = tar.id + data_path = tar.data_path + + if id_data_other and data_path: + data_path_new = find_path_new(id_data_other, data_path, rna_update_dict, rna_update_from_map) + # print(data_path_new) + if data_path_new != data_path: + if not IS_TESTING: + tar.data_path = data_path_new + print("driver (%s): %s -> %s" % (id_data_other.name, data_path, data_path_new)) + + + + for action in anim_data_actions(anim_data): + for fcu in action.fcurves: + data_path = fcu.data_path + data_path_new = find_path_new(id_data, data_path, rna_update_dict, rna_update_from_map) + # print(data_path_new) + if data_path_new != data_path: + if not IS_TESTING: + fcu.data_path = data_path_new + print("fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new)) + + +if __name__ == "__main__": + + # Example, should be called externally + # (class, from, to) + replace_ls = [ + ('AnimVizMotionPaths', 'frame_after', 'frame_after'), + ('AnimVizMotionPaths', 'frame_before', 'frame_before'), + ('AnimVizOnionSkinning', 'frame_after', 'frame_after'), + ] + + update_data_paths(replace_ls) diff --git a/release/scripts/modules/bpyml.py b/release/scripts/modules/bpyml.py new file mode 100755 index 00000000000..a2ba9ec8006 --- /dev/null +++ b/release/scripts/modules/bpyml.py @@ -0,0 +1,204 @@ +# ##### 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 ##### + +# + +""" +This module translates a python like XML representation into XML +or simple python blender/ui function calls. + + sometag(arg=10) [ + another() + another(key="value") + ] + +# converts into ... + + + + + + +""" + +TAG, ARGS, CHILDREN = range(3) +class ReturnStore(tuple): + def __getitem__(self, key): + + # single item get's + if type(key) is ReturnStore: + key = (key, ) + + if type(key) is tuple: + children = self[CHILDREN] + if children: + raise Exception("Only a single __getitem__ is allowed on the ReturnStore") + else: + children[:] = key + return self + else: + return tuple.__getitem__(self, key) + + +class FunctionStore(object): + def __call__(self, **kwargs): + return ReturnStore((self.__class__.__name__, kwargs, [])) + + +def tag_vars(tags, module=__name__): + return {tag: type(tag, (FunctionStore, ), {"__module__": module})() for tag in tags} + + +def tag_module(mod_name, tags): + import sys + from types import ModuleType + mod = ModuleType(mod_name) + sys.modules[mod_name] = mod + dict_values = tag_vars(tags, mod_name) + mod.__dict__.update(dict_values) + return mod + + +def toxml(py_data, indent=" "): + + if len(py_data) != 1 or type(py_data) != list: + raise Exception("Expected a list with one member") + + def _to_xml(py_item, xml_node=None): + if xml_node is None: + xml_node = newdoc.createElement(py_item[TAG]) + + for key, value in py_item[ARGS].items(): + xml_node.setAttribute(key, str(value)) + + for py_item_child in py_item[CHILDREN]: + xml_node.appendChild(_to_xml(py_item_child)) + + return xml_node + + def _to_xml_iter(xml_parent, data_ls): + for py_item in data_ls: + xml_node = newdoc.createElement(py_item[TAG]) + + + # ok if its empty + _to_xml_iter(xml_node, py_item[CHILDREN]) + + import xml.dom.minidom + impl = xml.dom.minidom.getDOMImplementation() + newdoc = impl.createDocument(None, py_data[0][TAG], None) + + _to_xml(py_data[0], newdoc.documentElement) + + return newdoc.documentElement.toprettyxml(indent=" ") + + +def fromxml(data): + def _fromxml_kwargs(xml_node): + kwargs = {} + for key, value in xml_node.attributes.items(): + kwargs[key] = value + return kwargs + + + def _fromxml(xml_node): + py_item = (xml_node.tagName, _fromxml_kwargs(xml_node), []) + #_fromxml_iter(py_item, xml_node.childNodes) + for xml_node_child in xml_node.childNodes: + if xml_node_child.nodeType not in (xml_node_child.TEXT_NODE, xml_node_child.COMMENT_NODE): + py_item[CHILDREN].append(_fromxml(xml_node_child)) + return py_item + + import xml.dom.minidom + xml_doc = xml.dom.minidom.parseString(data) + return [_fromxml(xml_doc.documentElement)] + + +def topretty_py(py_data, indent=" "): + + if len(py_data) != 1: + raise Exception("Expected a list with one member") + + lines = [] + + def _to_kwargs(kwargs): + return ", ".join([("%s=%s" % (key, repr(value))) for key, value in sorted(kwargs.items())]) + + def _topretty(py_item, indent_ctx, last): + if py_item[CHILDREN]: + lines.append("%s%s(%s) [" % (indent_ctx, py_item[TAG], _to_kwargs(py_item[ARGS]))) + py_item_last = py_item[CHILDREN][-1] + for py_item_child in py_item[CHILDREN]: + _topretty(py_item_child, indent_ctx + indent, (py_item_child is py_item_last)) + lines.append("%s]%s" % (indent_ctx, ("" if last else ","))) + else: + lines.append("%s%s(%s)%s" % (indent_ctx, py_item[TAG], _to_kwargs(py_item[ARGS]), ("" if last else ","))) + + _topretty(py_data[0], "", True) + + return "\n".join(lines) + +if __name__ == "__main__": + # testing code. + + tag_module("bpyml_test", ("ui", "prop", "row", "column", "active", "separator", "split")) + from bpyml_test import * + + draw = [ + ui() [ + split() [ + column() [ + prop(data='context.scene.render', property='use_stamp_time', text='Time'), + prop(data='context.scene.render', property='use_stamp_date', text='Date'), + prop(data='context.scene.render', property='use_stamp_render_time', text='RenderTime'), + prop(data='context.scene.render', property='use_stamp_frame', text='Frame'), + prop(data='context.scene.render', property='use_stamp_scene', text='Scene'), + prop(data='context.scene.render', property='use_stamp_camera', text='Camera'), + prop(data='context.scene.render', property='use_stamp_filename', text='Filename'), + prop(data='context.scene.render', property='use_stamp_marker', text='Marker'), + prop(data='context.scene.render', property='use_stamp_sequencer_strip', text='Seq. Strip') + ], + column() [ + active(expr='context.scene.render.use_stamp'), + prop(data='context.scene.render', property='stamp_foreground', slider=True), + prop(data='context.scene.render', property='stamp_background', slider=True), + separator(), + prop(data='context.scene.render', property='stamp_font_size', text='Font Size') + ] + ], + split(percentage=0.2) [ + prop(data='context.scene.render', property='use_stamp_note', text='Note'), + row() [ + active(expr='context.scene.render.use_stamp_note'), + prop(data='context.scene.render', property='stamp_note_text', text='') + ] + ] + ] + ] + + xml_data = toxml(draw) + print(xml_data) # xml version + + py_data = fromxml(xml_data) + print(py_data) # converted back to py + + xml_data = toxml(py_data) + print(xml_data) # again back to xml + + py_data = fromxml(xml_data) # pretty python version + print(topretty_py(py_data)) diff --git a/release/scripts/modules/bpyml_ui.py b/release/scripts/modules/bpyml_ui.py new file mode 100755 index 00000000000..ad68d1b0d7e --- /dev/null +++ b/release/scripts/modules/bpyml_ui.py @@ -0,0 +1,100 @@ +# ##### 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 ##### + +# + + +import bpy as _bpy +import bpyml +from bpyml import TAG, ARGS, CHILDREN +from types import ModuleType + +_uilayout_rna = _bpy.types.UILayout.bl_rna + +_uilayout_tags = ["ui"] + \ + _uilayout_rna.properties.keys() + \ + _uilayout_rna.functions.keys() + +# these need to be imported directly +# >>> from bpyml_ui.locals import * +locals = bpyml.tag_module("%s.locals" % __name__ , _uilayout_tags) + + +def _parse_rna(prop, value): + if prop.type == 'FLOAT': + value = float(value) + elif prop.type == 'INT': + value = int(value) + elif prop.type == 'BOOLEAN': + if value in (True, False): + pass + else: + if value not in ("True", "False"): + raise Exception("invalid bool value: %s" % value) + value = bool(value == "True") + elif prop.type in ('STRING', 'ENUM'): + pass + elif prop.type == 'POINTER': + value = eval("_bpy." + value) + else: + raise Exception("type not supported %s.%s" % (prop.identifier, prop.type)) + return value + + +def _parse_rna_args(base, py_node): + rna_params = base.bl_rna.functions[py_node[TAG]].parameters + args = {} + for key, value in py_node[ARGS].items(): + args[key] = _parse_rna(rna_params[key], value) + return args + + +def _call_recursive(context, base, py_node): + prop = base.bl_rna.properties.get(py_node[TAG]) + if py_node[TAG] in base.bl_rna.properties: + value = py_node[ARGS].get("expr") + if value: + value = eval(value, {"context": _bpy.context}) + setattr(base, py_node[TAG], value) + else: + value = py_node[ARGS]['value'] # have to have this + setattr(base, name, value) + else: + args = _parse_rna_args(base, py_node) + func_new = getattr(base, py_node[TAG]) + base_new = func_new(**args) # call blender func + if base_new is not None: + for py_node_child in py_node[CHILDREN]: + _call_recursive(context, base_new, py_node_child) + + +class BPyML_BaseUI(): + ''' + This is a mix-in class that defines a draw function + which checks for draw_data + ''' + + def draw(self, context): + layout = self.layout + for py_node in self.draw_data[CHILDREN]: + _call_recursive(context, layout, py_node) + + def draw_header(self, context): + layout = self.layout + for py_node in self.draw_header_data[CHILDREN]: + _call_recursive(context, layout, py_node)