svn merge ^/trunk/blender -r41150:41175
[blender.git] / release / scripts / modules / bpyml.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 """
22 This module translates a python like XML representation into XML
23 or simple python blender/ui function calls.
24
25     sometag(arg=10) [
26         another()
27         another(key="value")
28     ]
29
30 # converts into ...
31
32     <sometag arg="10">
33         <another/>
34         <another key="value" />
35     </sometag>
36
37 """
38
39 TAG, ARGS, CHILDREN = range(3)
40
41
42 class ReturnStore(tuple):
43     def __getitem__(self, key):
44
45         # single item get's
46         if type(key) is ReturnStore:
47             key = (key, )
48
49         if type(key) is tuple:
50             children = self[CHILDREN]
51             if children:
52                 raise Exception("Only a single __getitem__ is allowed on the ReturnStore")
53             else:
54                 children[:] = key
55             return self
56         else:
57             return tuple.__getitem__(self, key)
58
59
60 class FunctionStore(object):
61     def __call__(self, **kwargs):
62         return ReturnStore((self.__class__.__name__, kwargs, []))
63
64
65 def tag_vars(tags, module=__name__):
66     return {tag: type(tag, (FunctionStore, ), {"__module__": module})() for tag in tags}
67
68
69 def tag_module(mod_name, tags):
70     import sys
71     from types import ModuleType
72     mod = ModuleType(mod_name)
73     sys.modules[mod_name] = mod
74     dict_values = tag_vars(tags, mod_name)
75     mod.__dict__.update(dict_values)
76     return mod
77
78
79 def toxml(py_data, indent="    "):
80
81     if len(py_data) != 1 or type(py_data) != list:
82         raise Exception("Expected a list with one member")
83
84     def _to_xml(py_item, xml_node=None):
85         if xml_node is None:
86             xml_node = newdoc.createElement(py_item[TAG])
87
88         for key, value in py_item[ARGS].items():
89             xml_node.setAttribute(key, str(value))
90
91         for py_item_child in py_item[CHILDREN]:
92             xml_node.appendChild(_to_xml(py_item_child))
93
94         return xml_node
95
96     def _to_xml_iter(xml_parent, data_ls):
97         for py_item in data_ls:
98             xml_node = newdoc.createElement(py_item[TAG])
99
100             # ok if its empty
101             _to_xml_iter(xml_node, py_item[CHILDREN])
102
103     import xml.dom.minidom
104     impl = xml.dom.minidom.getDOMImplementation()
105     newdoc = impl.createDocument(None, py_data[0][TAG], None)
106
107     _to_xml(py_data[0], newdoc.documentElement)
108
109     return newdoc.documentElement.toprettyxml(indent="  ")
110
111
112 def fromxml(data):
113     def _fromxml_kwargs(xml_node):
114         kwargs = {}
115         for key, value in xml_node.attributes.items():
116             kwargs[key] = value
117         return kwargs
118
119     def _fromxml(xml_node):
120         py_item = (xml_node.tagName, _fromxml_kwargs(xml_node), [])
121         #_fromxml_iter(py_item, xml_node.childNodes)
122         for xml_node_child in xml_node.childNodes:
123             if xml_node_child.nodeType not in {xml_node_child.TEXT_NODE, xml_node_child.COMMENT_NODE}:
124                 py_item[CHILDREN].append(_fromxml(xml_node_child))
125         return py_item
126
127     import xml.dom.minidom
128     xml_doc = xml.dom.minidom.parseString(data)
129     return [_fromxml(xml_doc.documentElement)]
130
131
132 def topretty_py(py_data, indent="    "):
133
134     if len(py_data) != 1:
135         raise Exception("Expected a list with one member")
136
137     lines = []
138
139     def _to_kwargs(kwargs):
140         return ", ".join([("%s=%s" % (key, repr(value))) for key, value in sorted(kwargs.items())])
141
142     def _topretty(py_item, indent_ctx, last):
143         if py_item[CHILDREN]:
144             lines.append("%s%s(%s) [" % (indent_ctx, py_item[TAG], _to_kwargs(py_item[ARGS])))
145             py_item_last = py_item[CHILDREN][-1]
146             for py_item_child in py_item[CHILDREN]:
147                 _topretty(py_item_child, indent_ctx + indent, (py_item_child is py_item_last))
148             lines.append("%s]%s" % (indent_ctx, ("" if last else ",")))
149         else:
150             lines.append("%s%s(%s)%s" % (indent_ctx, py_item[TAG], _to_kwargs(py_item[ARGS]), ("" if last else ",")))
151
152     _topretty(py_data[0], "", True)
153
154     return "\n".join(lines)
155
156 if __name__ == "__main__":
157     # testing code.
158
159     tag_module("bpyml_test", ("ui", "prop", "row", "column", "active", "separator", "split"))
160     from bpyml_test import *
161
162     draw = [
163          ui()[
164             split()[
165                 column()[
166                     prop(data='context.scene.render', property='use_stamp_time', text='Time'),
167                     prop(data='context.scene.render', property='use_stamp_date', text='Date'),
168                     prop(data='context.scene.render', property='use_stamp_render_time', text='RenderTime'),
169                     prop(data='context.scene.render', property='use_stamp_frame', text='Frame'),
170                     prop(data='context.scene.render', property='use_stamp_scene', text='Scene'),
171                     prop(data='context.scene.render', property='use_stamp_camera', text='Camera'),
172                     prop(data='context.scene.render', property='use_stamp_filename', text='Filename'),
173                     prop(data='context.scene.render', property='use_stamp_marker', text='Marker'),
174                     prop(data='context.scene.render', property='use_stamp_sequencer_strip', text='Seq. Strip')
175                 ],
176                 column()[
177                     active(expr='context.scene.render.use_stamp'),
178                     prop(data='context.scene.render', property='stamp_foreground', slider=True),
179                     prop(data='context.scene.render', property='stamp_background', slider=True),
180                     separator(),
181                     prop(data='context.scene.render', property='stamp_font_size', text='Font Size')
182                 ]
183             ],
184             split(percentage=0.2)[
185                 prop(data='context.scene.render', property='use_stamp_note', text='Note'),
186                 row()[
187                     active(expr='context.scene.render.use_stamp_note'),
188                     prop(data='context.scene.render', property='stamp_note_text', text='')
189                 ]
190             ]
191         ]
192     ]
193
194     xml_data = toxml(draw)
195     print(xml_data)  # xml version
196
197     py_data = fromxml(xml_data)
198     print(py_data)  # converted back to py
199
200     xml_data = toxml(py_data)
201     print(xml_data)  # again back to xml
202
203     py_data = fromxml(xml_data)  # pretty python version
204     print(topretty_py(py_data))