Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCampbell Barton <ideasman42@gmail.com>2010-08-11 19:11:30 +0400
committerCampbell Barton <ideasman42@gmail.com>2010-08-11 19:11:30 +0400
commitab8ccaa7098f6fee887f2a249fddada7864cd6c5 (patch)
tree672613464ff57264e76ca71cafa910cd26c8730a /release
parent2a72eb8c287bedd35908ace4f19b06e806592845 (diff)
python declarative UI
- remove XML testing feature - add 2 modules: bpyml - generic, bpyml_ui - blender spesific. nothing uses these now. ==bpyml_ui module== defines BPyML_BaseUI and its draw() function which uses the bpyml member of the class instance self.draw_data & self.draw_header_data. This way declarative ui is opt-in and easy to use by using BPyML_BaseUI as a mix-in class. ==bpyml module== 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 ... <sometag arg="10"> <another/> <another key="value" /> </sometag>
Diffstat (limited to 'release')
-rw-r--r--release/scripts/modules/bpy/utils.py35
-rw-r--r--release/scripts/modules/bpy_xml_ui.py151
-rw-r--r--release/scripts/modules/bpyml.py204
-rw-r--r--release/scripts/modules/bpyml_ui.py100
-rw-r--r--release/scripts/ui/properties_render_test.xml79
5 files changed, 304 insertions, 265 deletions
diff --git a/release/scripts/modules/bpy/utils.py b/release/scripts/modules/bpy/utils.py
index c7ee23264e8..6c1c669d1f2 100644
--- a/release/scripts/modules/bpy/utils.py
+++ b/release/scripts/modules/bpy/utils.py
@@ -30,8 +30,6 @@ import sys as _sys
from _bpy import blend_paths
from _bpy import script_paths as _bpy_script_paths
-_TEST_XML = _bpy.app.debug
-
def _test_import(module_name, loaded_modules):
import traceback
import time
@@ -54,35 +52,6 @@ def _test_import(module_name, loaded_modules):
loaded_modules.add(mod.__name__) # should match mod.__name__ too
return mod
-if _TEST_XML:
- # TEST CODE
- def _test_import_xml(path, f, loaded_modules):
- import bpy_xml_ui
- import traceback
-
- f_full = _os.path.join(path, f)
- _bpy_types._register_immediate = True
- try:
- classes = bpy_xml_ui.load_xml(f_full)
- except:
- traceback.print_exc()
- classes = []
- _bpy_types._register_immediate = False
-
- if classes:
- mod_name = f.split(".")[0]
-
- # fake module
- mod = type(traceback)(mod_name)
- mod.__file__ = f_full
- for cls in classes:
- setattr(mod, cls.__name__, cls)
-
- loaded_modules.add(mod_name)
- _sys.modules[mod_name] = mod
- mod.register = lambda: None # quiet errors
- return mod
-
def modules_from_path(path, loaded_modules):
"""
@@ -110,10 +79,6 @@ def modules_from_path(path, loaded_modules):
else:
mod = None
- if _TEST_XML:
- if mod is None and f.endswith(".xml"):
- mod = _test_import_xml(path, f, loaded_modules)
-
if mod:
modules.append(mod)
diff --git a/release/scripts/modules/bpy_xml_ui.py b/release/scripts/modules/bpy_xml_ui.py
deleted file mode 100644
index 18eec9c4ef4..00000000000
--- a/release/scripts/modules/bpy_xml_ui.py
+++ /dev/null
@@ -1,151 +0,0 @@
-# ##### 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 #####
-
-# <pep8 compliant>
-
-"""
-This module translates XML into blender/ui function calls.
-"""
-
-import xml.dom.minidom
-import bpy as _bpy
-
-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 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_args(base, xml_node):
- args = {}
- rna_params = base.bl_rna.functions[xml_node.tagName].parameters
- for key, value in xml_node.attributes.items():
- args[key] = parse_rna(rna_params[key], value)
- return args
-
-def ui_xml(base, xml_node):
- name = xml_node.tagName
- prop = base.bl_rna.properties.get(name)
- if name in base.bl_rna.properties:
- attr = xml_node.attributes.get("expr")
- if attr:
- value = attr.value
- value = eval(value, {"context": _bpy.context})
- setattr(base, name, value)
- else:
- attr = xml_node.attributes['value']
- value = attr.value
- value = parse_rna(prop, value)
- setattr(base, name, value)
- else:
- func_new = getattr(base, name)
- kw_args = parse_args(base, xml_node)
- base_new = func_new(**kw_args) # call blender func
- if xml_node.hasChildNodes():
- ui_xml_list(base_new, xml_node.childNodes)
-
-def ui_xml_list(base, xml_nodes):
- import bpy
- for node in xml_nodes:
- if node.nodeType not in (node.TEXT_NODE, node.COMMENT_NODE):
- ui_xml(base, node)
- bpy.N = node
-
-def test(layout):
- uixml = xml.dom.minidom.parseString(open("/mnt/test/blender-svn/blender/release/scripts/ui/test.xml", 'r').read())
- panel = uixml.getElementsByTagName('panel')[0]
- ui_xml_list(layout, panel.childNodes)
-
-def load_xml(filepath):
- classes = []
- fn = open(filepath, 'r')
- data = fn.read()
- uixml = xml.dom.minidom.parseString(data).getElementsByTagName("ui")[0]
- fn.close()
-
- def draw_xml(self, context):
- node = self._xml_node.getElementsByTagName("draw")[0]
- ui_xml_list(self.layout, node.childNodes)
-
- def draw_header_xml(self, context):
- node = self._xml_node.getElementsByTagName("draw_header")[0]
- ui_xml_list(self.layout, node.childNodes)
-
- for node in uixml.childNodes:
- if node.nodeType not in (node.TEXT_NODE, node.COMMENT_NODE):
- name = node.tagName
- class_name = node.attributes["identifier"].value
-
- if name == "panel":
- class_dict = {
- "bl_label": node.attributes["label"].value,
- "bl_region_type": node.attributes["region_type"].value,
- "bl_space_type": node.attributes["space_type"].value,
- "bl_context": node.attributes["context"].value,
- "bl_default_closed": ((node.attributes["default_closed"].value == "true") if "default_closed" in node.attributes else False),
-
- "draw": draw_xml,
- "_xml_node": node
- }
-
- if node.getElementsByTagName("draw_header"):
- class_dict["draw_header"] = draw_header_xml
-
- # will register instantly
- class_new = type(class_name, (_bpy.types.Panel,), class_dict)
-
- elif name == "menu":
- class_dict = {
- "bl_label": node.attributes["label"].value,
-
- "draw": draw_xml,
- "_xml_node": node
- }
-
- # will register instantly
- class_new = type(class_name, (_bpy.types.Menu,), class_dict)
-
- elif name == "header":
- class_dict = {
- "bl_label": node.attributes["label"].value,
- "bl_space_type": node.attributes["space_type"].value,
-
- "draw": draw_xml,
- "_xml_node": node
- }
-
- # will register instantly
- class_new = type(class_name, (_bpy.types.Header,), class_dict)
- else:
- raise Exception("invalid id found '%s': expected a value in ('header', 'panel', 'menu)'" % name)
-
- classes.append(class_new)
-
-
- return classes
diff --git a/release/scripts/modules/bpyml.py b/release/scripts/modules/bpyml.py
new file mode 100644
index 00000000000..f30a4b8d9ac
--- /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 #####
+
+# <pep8 compliant>
+
+"""
+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 ...
+
+ <sometag arg="10">
+ <another/>
+ <another key="value" />
+ </sometag>
+
+"""
+
+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='stamp_time', text='Time'),
+ prop(data='context.scene.render', property='stamp_date', text='Date'),
+ prop(data='context.scene.render', property='stamp_render_time', text='RenderTime'),
+ prop(data='context.scene.render', property='stamp_frame', text='Frame'),
+ prop(data='context.scene.render', property='stamp_scene', text='Scene'),
+ prop(data='context.scene.render', property='stamp_camera', text='Camera'),
+ prop(data='context.scene.render', property='stamp_filename', text='Filename'),
+ prop(data='context.scene.render', property='stamp_marker', text='Marker'),
+ prop(data='context.scene.render', property='stamp_sequencer_strip', text='Seq. Strip')
+ ],
+ column() [
+ active(expr='context.scene.render.render_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='stamp_note', text='Note'),
+ row() [
+ active(expr='context.scene.render.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 100644
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 #####
+
+# <pep8 compliant>
+
+
+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)
diff --git a/release/scripts/ui/properties_render_test.xml b/release/scripts/ui/properties_render_test.xml
deleted file mode 100644
index f8a77e37e21..00000000000
--- a/release/scripts/ui/properties_render_test.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-<ui>
- <panel identifier="RENDER_PT_stamp_test" label="Stamp (XML)" space_type="PROPERTIES" region_type="WINDOW" context="render" default_closed="true">
- <draw_header>
- <prop data="context.scene.render" property="render_stamp" text=""/>
- </draw_header>
-
- <draw>
- <split>
- <column>
- <prop data="context.scene.render" property="stamp_time" text="Time"/>
- <prop data="context.scene.render" property="stamp_date" text="Date"/>
- <prop data="context.scene.render" property="stamp_render_time" text="RenderTime"/>
- <prop data="context.scene.render" property="stamp_frame" text="Frame"/>
- <prop data="context.scene.render" property="stamp_scene" text="Scene"/>
- <prop data="context.scene.render" property="stamp_camera" text="Camera"/>
- <prop data="context.scene.render" property="stamp_filename" text="Filename"/>
- <prop data="context.scene.render" property="stamp_marker" text="Marker"/>
- <prop data="context.scene.render" property="stamp_sequencer_strip" text="Seq. Strip"/>
- </column>
- <column>
- <active expr="context.scene.render.render_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"/>
- </column>
- </split>
- <split percentage="0.2">
- <prop data="context.scene.render" property="stamp_note" text="Note"/>
- <row>
- <active expr="context.scene.render.stamp_note"/>
- <prop data="context.scene.render" property="stamp_note_text" text=""/>
- </row>
- </split>
- </draw>
- </panel>
-
- <panel identifier="RENDER_PT_dimensions_test" label="Dimensions (XML)" space_type="PROPERTIES" region_type="WINDOW" context="render">
- <draw>
- <row align="true">
- <menu menu="RENDER_MT_presets"/>
- <operator operator="render.preset_add" text="" icon="ZOOMIN"/>
- </row>
- <split>
- <column>
- <column align="true">
- <label text="Resolution:"/>
- <prop data="context.scene.render" property="resolution_x" text="X"/>
- <prop data="context.scene.render" property="resolution_y" text="Y"/>
- <prop data="context.scene.render" property="resolution_percentage" text=""/>
-
- <label text="Aspect Ratio:"/>
- <prop data="context.scene.render" property="pixel_aspect_x" text="X"/>
- <prop data="context.scene.render" property="pixel_aspect_y" text="Y"/>
- </column>
- <row>
- <prop data="context.scene.render" property="use_border" text="Border"/>
- <row>
- <active expr="context.scene.render.use_border"/>
- <prop data="context.scene.render" property="crop_to_border" text="Crop"/>
- </row>
- </row>
- </column>
- <column>
- <column align="true">
- <label text="Frame Range:"/>
- <prop data="context.scene" property="frame_start" text="Start"/>
- <prop data="context.scene" property="frame_end" text="End"/>
- <prop data="context.scene" property="frame_step" text="Step"/>
-
- <label text="Frame Rate:"/>
- <prop data="context.scene.render" property="fps"/>
- <prop data="context.scene.render" property="fps_base" text="/"/>
- </column>
- </column>
- </split>
- </draw>
- </panel>
-</ui>