Experemental XML UI, define panels/menus/headers which load at startup like python scripts.

- 2 panels implimented in properties_render_test.xml (Render Dimensions and Stamp)
- only enabled in debug mode.
- poll() functions are not supported yet.
- as stated above experemental, we'll see if this is at all useful, remove if not.
- XML could be replaced with JSON or YAML.
This commit is contained in:
Campbell Barton 2010-08-07 16:21:15 +00:00
parent 2b3c8bdc27
commit 057aac553b
4 changed files with 266 additions and 4 deletions

@ -30,6 +30,8 @@ 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
@ -52,6 +54,35 @@ 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):
"""
@ -79,6 +110,10 @@ 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)

@ -0,0 +1,151 @@
# ##### 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

@ -0,0 +1,79 @@
<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>

@ -922,12 +922,11 @@ class USERPREF_PT_addons(bpy.types.Panel):
return list(USERPREF_PT_addons._addons_fake_modules.values())
else:
# never run this!, before it used ast
# never run this!, before it used 'ast' module
pass
'''
# note, this still gets added to _bpy_types.TypeMap
import bpy_types as _bpy_types
_bpy_types._register_override = True
# sys.path.insert(0, None)
for path in paths:
@ -937,8 +936,6 @@ class USERPREF_PT_addons(bpy.types.Panel):
if bpy.app.debug:
print("Addon Script Load Time %.4f" % (time.time() - t_main))
_bpy_types._register_override = False
# del sys.path[0]
return modules
'''