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:
authorDoug Hammond <doughammond@hamsterfight.co.uk>2010-10-22 22:55:10 +0400
committerDoug Hammond <doughammond@hamsterfight.co.uk>2010-10-22 22:55:10 +0400
commita77301839d8780f7d0ffed6c050ef2131f84b419 (patch)
tree033624b9fce6a5146408ae79e6d0b2f14e229792
parentdad9423dd605eab60f19f4e76ad7b71ff37be827 (diff)
Add release/scripts/modules/extensions_framework (formerly known as exporter_framework).
-rw-r--r--release/scripts/modules/extensions_framework/__init__.py156
-rw-r--r--release/scripts/modules/extensions_framework/engine.py37
-rw-r--r--release/scripts/modules/extensions_framework/outputs/__init__.py26
-rw-r--r--release/scripts/modules/extensions_framework/outputs/xml_output.py96
-rw-r--r--release/scripts/modules/extensions_framework/plugin.py72
-rw-r--r--release/scripts/modules/extensions_framework/ui.py181
-rw-r--r--release/scripts/modules/extensions_framework/util.py211
-rw-r--r--release/scripts/modules/extensions_framework/validate.py208
8 files changed, 987 insertions, 0 deletions
diff --git a/release/scripts/modules/extensions_framework/__init__.py b/release/scripts/modules/extensions_framework/__init__.py
new file mode 100644
index 00000000000..fdb7ddd706d
--- /dev/null
+++ b/release/scripts/modules/extensions_framework/__init__.py
@@ -0,0 +1,156 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+
+import os, time
+import bpy
+
+#----------------------------------------------------------------------------------------------------------------------
+
+def log(str, popup=False, module_name='EF'):
+ print("[%s %s] %s" % (module_name, time.strftime('%Y-%b-%d %H:%M:%S'), str))
+ if popup:
+ bpy.ops.ef.msg(
+ msg_type='WARNING',
+ msg_text=str
+ )
+
+#----------------------------------------------------------------------------------------------------------------------
+
+from .ui import EF_OT_msg
+
+bpy.types.register(EF_OT_msg)
+ef_path = os.path.realpath( os.path.dirname(__file__) )
+# log('Extensions_Framework detected and loaded from %s'%ef_path)
+
+del EF_OT_msg, os
+
+#----------------------------------------------------------------------------------------------------------------------
+
+class ef(object):
+ '''
+ Extensions Framework base class
+ '''
+
+ added_property_cache = {}
+
+
+def init_properties(obj, props, cache=True):
+ if not obj in ef.added_property_cache.keys():
+ ef.added_property_cache[obj] = []
+
+ for prop in props:
+ if cache and prop['attr'] in ef.added_property_cache[obj]:
+ continue
+ try:
+ if prop['type'] == 'bool':
+ t = bpy.props.BoolProperty
+ a = {k: v for k,v in prop.items() if k in ['name','description','default']}
+ elif prop['type'] == 'collection':
+ t = bpy.props.CollectionProperty
+ a = {k: v for k,v in prop.items() if k in ["ptype", "name", "description"]}
+ a['type'] = a['ptype']
+ del a['ptype']
+ elif prop['type'] == 'enum':
+ t = bpy.props.EnumProperty
+ a = {k: v for k,v in prop.items() if k in ["items", "name", "description", "default"]}
+ elif prop['type'] == 'float':
+ t = bpy.props.FloatProperty
+ a = {k: v for k,v in prop.items() if k in ["name", "description", "min", "max", "soft_min", "soft_max", "default", "precision"]}
+ elif prop['type'] == 'float_vector':
+ t = bpy.props.FloatVectorProperty
+ a = {k: v for k,v in prop.items() if k in ["name", "description", "min", "max", "soft_min", "soft_max", "default", "precision", "size", "subtype"]}
+ elif prop['type'] == 'int':
+ t = bpy.props.IntProperty
+ a = {k: v for k,v in prop.items() if k in ["name", "description", "min", "max", "soft_min", "soft_max", "default"]}
+ elif prop['type'] == 'pointer':
+ t = bpy.props.PointerProperty
+ a = {k: v for k,v in prop.items() if k in ["ptype", "name", "description"]}
+ a['type'] = a['ptype']
+ del a['ptype']
+ elif prop['type'] == 'string':
+ t = bpy.props.StringProperty
+ a = {k: v for k,v in prop.items() if k in ["name", "description", "maxlen", "default", "subtype"]}
+ else:
+ #ef.log('Property type not recognised: %s' % prop['type'])
+ continue
+
+ setattr(obj, prop['attr'], t(**a))
+
+ ef.added_property_cache[obj].append(prop['attr'])
+ #log('Created property %s.%s' % (obj, prop['attr']))
+ except KeyError:
+ continue
+
+class declarative_property_group(bpy.types.IDPropertyGroup):
+
+ controls = [
+ # this list controls the order of property
+ # layout when rendered by a property_group_renderer.
+ # This can be a nested list, where each list
+ # becomes a row in the panel layout.
+ # nesting may be to any depth
+ ]
+
+ # Include some properties in display based on values of others
+ visibility = {
+ # See ef.validate for test syntax
+ }
+
+ # engine-specific properties to create in the scene.
+ # Each item should be a dict of args to pass to a
+ # bpy.types.Scene.<?>Property function, with the exception
+ # of 'type' which is used and stripped by ef
+ properties = [
+ # example:
+ #{
+ # 'type': 'int',
+ # 'attr': 'threads',
+ # 'name': 'Render Threads',
+ # 'description': 'Number of threads to use',
+ # 'default': 1,
+ # 'min': 1,
+ # 'soft_min': 1,
+ # 'max': 64,
+ # 'soft_max': 64
+ #},
+ ]
+
+ def draw_callback(self, context):
+ '''
+ Sub-classes can override this to get a callback
+ when rendered by a property_group_renderer class
+ '''
+
+ pass
+
+ @classmethod
+ def get_exportable_properties(cls):
+ out = []
+ for prop in cls.properties:
+ if 'save_in_preset' in prop.keys() and prop['save_in_preset']:
+ out.append(prop)
+ return out
diff --git a/release/scripts/modules/extensions_framework/engine.py b/release/scripts/modules/extensions_framework/engine.py
new file mode 100644
index 00000000000..448eb715bef
--- /dev/null
+++ b/release/scripts/modules/extensions_framework/engine.py
@@ -0,0 +1,37 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+from .plugin import plugin
+
+class engine_base(plugin):
+ '''
+ Render Engine plugin base class
+ '''
+
+ bl_label = 'Abstract Render Engine Base Class'
+
+ def render(self, scene):
+ pass
diff --git a/release/scripts/modules/extensions_framework/outputs/__init__.py b/release/scripts/modules/extensions_framework/outputs/__init__.py
new file mode 100644
index 00000000000..f05ed25fbad
--- /dev/null
+++ b/release/scripts/modules/extensions_framework/outputs/__init__.py
@@ -0,0 +1,26 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
diff --git a/release/scripts/modules/extensions_framework/outputs/xml_output.py b/release/scripts/modules/extensions_framework/outputs/xml_output.py
new file mode 100644
index 00000000000..4d1af9cc877
--- /dev/null
+++ b/release/scripts/modules/extensions_framework/outputs/xml_output.py
@@ -0,0 +1,96 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+import xml.etree.cElementTree as ET
+import xml.dom.minidom as MD
+
+class xml_output(object):
+
+ format = {}
+
+ def __str__(self):
+ return ET.tostring(self.root)
+
+ def write_pretty(self, file):
+ xml_dom = MD.parseString(ET.tostring(self.root, encoding='utf-8'))
+ xml_dom.writexml(file, addindent=' ', newl='\n', encoding='utf-8')
+
+ def pretty(self):
+ xml_str = MD.parseString(ET.tostring(self.root))
+ return xml_str.toprettyxml()
+
+ # This should be overridden in classes that produce XML conditionally
+ def get_format(self):
+ return self.format
+
+ def compute(self, context):
+ self.context = context
+
+ self.root = ET.Element(self.root_element)
+ self.parse_dict(self.get_format(), self.root)
+ #ET.dump(root)
+
+ return self.root
+
+ def make_subelement(self, elem, name):
+ return ET.SubElement(elem, name)
+
+ format_types = {
+ 'bool': lambda c,x: str(x).lower(),
+ 'collection': lambda c,x: x,
+ 'enum': lambda c,x: x,
+ 'float': lambda c,x: x,
+ 'int': lambda c,x: x,
+ 'pointer': lambda c,x: x,
+ 'string': lambda c,x: x,
+ }
+
+ def parse_dict(self, d, elem):
+ for key in d.keys():
+ # tuple provides multiple child elements
+ if type(d[key]) is tuple:
+ for cd in d[key]:
+ self.parse_dict({key:cd}, elem)
+ continue # don't create empty element for tuple child
+
+ x = ET.SubElement(elem, key)
+
+ # dictionary provides nested elements
+ if type(d[key]) is dict:
+ self.parse_dict(d[key], x)
+
+ # list provides direct value insertion
+ elif type(d[key]) is list:
+ x.text = ' '.join([str(i) for i in d[key]])
+
+ # else look up property
+ else:
+ for p in self.properties:
+ if d[key] == p['attr']:
+ if 'compute' in p.keys():
+ x.text = str(p['compute'](self.context, self))
+ else:
+ x.text = str(self.format_types[p['type']](self.context, getattr(self, d[key])))
diff --git a/release/scripts/modules/extensions_framework/plugin.py b/release/scripts/modules/extensions_framework/plugin.py
new file mode 100644
index 00000000000..b0042aad40d
--- /dev/null
+++ b/release/scripts/modules/extensions_framework/plugin.py
@@ -0,0 +1,72 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+from . import init_properties
+from . import log
+
+import bpy
+
+class plugin(object):
+
+ # List of IDPropertyGroup types to create in the scene
+ property_groups = [
+ # ('bpy.type prototype to attach to. eg. Scene', <declarative_property_group type>)
+ ]
+
+ @classmethod
+ def install(r_class):
+ # create custom property groups
+ for property_group_parent, property_group in r_class.property_groups:
+ call_init = False
+ if property_group_parent is not None:
+ prototype = getattr(bpy.types, property_group_parent)
+ if not hasattr(prototype, property_group.__name__):
+ init_properties(prototype, [{
+ 'type': 'pointer',
+ 'attr': property_group.__name__,
+ 'ptype': property_group,
+ 'name': property_group.__name__,
+ 'description': property_group.__name__
+ }])
+ call_init = True
+ #print('Created IDPropertyGroup %s.%s' % (prototype, property_group.__name__))
+ else:
+ call_init = True
+
+ if call_init:
+ init_properties(property_group, property_group.properties)
+ #print('Initialised IDPropertyGroup %s' % property_group.__name__)
+
+ log('Render Engine "%s" initialised' % r_class.bl_label)
+
+ @classmethod
+ def uninstall(r_class):
+ # unregister property groups in reverse order
+ reverse_property_groups = [p for p in r_class.property_groups]
+ reverse_property_groups.reverse()
+ for property_group_parent, property_group in reverse_property_groups:
+ prototype = getattr(bpy.types, property_group_parent)
+ prototype.RemoveProperty(property_group.__name__)
diff --git a/release/scripts/modules/extensions_framework/ui.py b/release/scripts/modules/extensions_framework/ui.py
new file mode 100644
index 00000000000..5d29ed7245a
--- /dev/null
+++ b/release/scripts/modules/extensions_framework/ui.py
@@ -0,0 +1,181 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+import bpy
+
+from .validate import Visibility
+
+class EF_OT_msg(bpy.types.Operator):
+ bl_idname = 'ef.msg'
+ bl_label = 'Show UI Message'
+ msg_type = bpy.props.StringProperty(default='INFO')
+ msg_text = bpy.props.StringProperty(default='')
+ def execute(self, context):
+ self.report({self.properties.msg_type}, self.properties.msg_text)
+ return {'FINISHED'}
+
+def _get_item_from_context(context, path):
+ if context is not None:
+ for p in path:
+ context = getattr(context, p)
+ return context
+
+class property_group_renderer(object):
+
+ # Choose which custom property groups this panel should draw, and
+ # where to find that property group in the active context
+ display_property_groups = [
+ # ( ('scene',), 'declarative_property_group name')
+ ]
+
+ def draw(self, context):
+ '''
+ Sub-classes should override this if they need to display
+ other (object-related) property groups
+ '''
+ for property_group_path, property_group_name in self.display_property_groups:
+ ctx = _get_item_from_context(context, property_group_path)
+ property_group = getattr(ctx, property_group_name)
+ for p in property_group.controls:
+ self.draw_column(p, self.layout, ctx, context, property_group=property_group)
+ property_group.draw_callback(context)
+
+ @staticmethod
+ def property_reload():
+ '''
+ override this in sub classes to force data refresh upon scene reload
+ '''
+ pass
+
+ def check_visibility(self, lookup_property, context, property_group):
+ vt = Visibility(property_group)
+ if lookup_property in property_group.visibility.keys():
+ if hasattr(property_group, lookup_property):
+ member = getattr(property_group, lookup_property)
+ else:
+ member = None
+ return vt.test_logic(member, property_group.visibility[lookup_property])
+ else:
+ return True
+
+ def draw_column(self, control_list_item, layout, context, supercontext=None, property_group=None):
+ if type(control_list_item) is list:
+ do_split = False
+
+ found_percent = None
+ for sp in control_list_item:
+ if type(sp) is float:
+ found_percent = sp
+ elif type(sp) is list:
+ for ssp in control_list_item:
+ do_split = do_split and self.check_visibility(ssp, context, property_group)
+ else:
+ do_split = do_split or self.check_visibility(sp, context, property_group)
+
+ if do_split:
+ # print('split %s'%p)
+ if found_percent is not None:
+ fp = { 'percentage': found_percent }
+ splt = layout.split(**fp)
+ else:
+ splt = layout.row(True)
+ for sp in [s for s in control_list_item if type(s) in [str, list] ]:
+ col2 = splt.column()
+ self.draw_column(sp, col2, context, supercontext, property_group)
+ #else:
+ # print('dont split %s'%p)
+ else:
+ if self.check_visibility(control_list_item, context, property_group):
+
+ for current_property in property_group.properties:
+ if current_property['attr'] == control_list_item:
+ current_property_keys = current_property.keys()
+ if 'type' in current_property_keys:
+
+ if current_property['type'] in ['int', 'float', 'float_vector', 'enum', 'string']:
+ layout.prop(
+ property_group,
+ control_list_item,
+ text = current_property['name'],
+ expand = current_property['expand'] if 'expand' in current_property_keys else False,
+ slider = current_property['slider'] if 'slider' in current_property_keys else False,
+ toggle = current_property['toggle'] if 'toggle' in current_property_keys else False,
+ icon_only = current_property['icon_only'] if 'icon_only' in current_property_keys else False,
+ event = current_property['event'] if 'event' in current_property_keys else False,
+ full_event = current_property['full_event'] if 'full_event' in current_property_keys else False,
+ emboss = current_property['emboss'] if 'emboss' in current_property_keys else True,
+ )
+ if current_property['type'] in ['bool']:
+ layout.prop(
+ property_group,
+ control_list_item,
+ text = current_property['name'],
+ toggle = current_property['toggle'] if 'toggle' in current_property_keys else False,
+ icon_only = current_property['icon_only'] if 'icon_only' in current_property_keys else False,
+ event = current_property['event'] if 'event' in current_property_keys else False,
+ full_event = current_property['full_event'] if 'full_event' in current_property_keys else False,
+ emboss = current_property['emboss'] if 'emboss' in current_property_keys else True,
+ )
+ elif current_property['type'] in ['operator']:
+ layout.operator(current_property['operator'],
+ text = current_property['text'],
+ icon = current_property['icon']
+ )
+
+ elif current_property['type'] in ['text']:
+ layout.label(
+ text = current_property['name']
+ )
+
+ elif current_property['type'] in ['template_list']:
+ # row.template_list(idblock, "texture_slots", idblock, "active_texture_index", rows=2)
+ layout.template_list(
+ current_property['src'](supercontext, context),
+ current_property['src_attr'],
+ current_property['trg'](supercontext, context),
+ current_property['trg_attr'],
+ rows = 4 if not 'rows' in current_property_keys else current_property['rows'],
+ maxrows = 4 if not 'rows' in current_property_keys else current_property['rows'],
+ type = 'DEFAULT' if not 'list_type' in current_property_keys else current_property['list_type']
+ )
+
+ elif current_property['type'] in ['prop_search']:
+ # split.prop_search(tex, "uv_layer", ob.data, "uv_textures", text="")
+ layout.prop_search(
+ current_property['trg'](supercontext, context),
+ current_property['trg_attr'],
+ current_property['src'](supercontext, context),
+ current_property['src_attr'],
+ text = current_property['name'],
+ )
+ else:
+ layout.prop(property_group, control_list_item)
+
+ # Fire a draw callback if specified
+ if 'draw' in current_property_keys:
+ current_property['draw'](supercontext, context)
+
+ break
diff --git a/release/scripts/modules/extensions_framework/util.py b/release/scripts/modules/extensions_framework/util.py
new file mode 100644
index 00000000000..8bbeaf74ec3
--- /dev/null
+++ b/release/scripts/modules/extensions_framework/util.py
@@ -0,0 +1,211 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+import datetime, os, configparser, threading, tempfile
+
+import bpy
+
+config_paths = bpy.utils.script_paths()
+
+'''
+This path is set at the start of export, so that
+calls to path_relative_to_export() can make all
+exported paths relative to this one
+'''
+export_path = '';
+
+def path_relative_to_export(p):
+ '''
+ Return a path that is relative to the export path
+ '''
+ global export_path
+ p = filesystem_path(p)
+ try:
+ relp = os.path.relpath(p, os.path.dirname(export_path))
+ except ValueError: # path on different drive on windows
+ relp = p
+
+ #print('Resolving rel path %s -> %s' % (p, relp))
+
+ return relp.replace('\\', '/')
+
+def filesystem_path(p):
+ '''
+ Resolve a relative Blender path to a real filesystem path
+ '''
+ if p.startswith('//'):
+ pout = bpy.path.abspath(p)
+ else:
+ pout = os.path.realpath(p)
+
+ #print('Resolving FS path %s -> %s' % (p,pout))
+
+ return pout.replace('\\', '/')
+
+# TODO: - somehow specify TYPES to get/set from config
+
+def find_config_value(module, section, key, default):
+ global config_paths
+ fc = []
+ for p in config_paths:
+ if os.path.exists(p) and os.path.isdir(p) and os.access(p, os.W_OK):
+ fc.append( '/'.join([p, '%s.cfg' % module]))
+
+ if len(fc) < 1:
+ print('Cannot find %s config file path' % module)
+ return default
+
+ cp = configparser.SafeConfigParser()
+
+ cfg_files = cp.read(fc)
+ if len(cfg_files) > 0:
+ try:
+ val = cp.get(section, key)
+ if val == 'true':
+ return True
+ elif val == 'false':
+ return False
+ else:
+ return val
+ except:
+ return default
+ else:
+ return default
+
+def write_config_value(module, section, key, value):
+ global config_paths
+ fc = []
+ for p in config_paths:
+ if os.path.exists(p) and os.path.isdir(p) and os.access(p, os.W_OK):
+ fc.append( '/'.join([p, '%s.cfg' % module]))
+
+ if len(fc) < 1:
+ raise Exception('Cannot find a writable path to store %s config file' % module)
+
+ cp = configparser.SafeConfigParser()
+
+ cfg_files = cp.read(fc)
+
+ if not cp.has_section(section):
+ cp.add_section(section)
+
+ if value == True:
+ cp.set(section, key, 'true')
+ elif value == False:
+ cp.set(section, key, 'false')
+ else:
+ cp.set(section, key, value)
+
+ if len(cfg_files) < 1:
+ cfg_files = fc
+
+ fh=open(cfg_files[0],'w')
+ cp.write(fh)
+ fh.close()
+
+ return True
+
+def scene_filename():
+ '''
+ Construct a safe scene filename
+ '''
+ filename = os.path.splitext(os.path.basename(bpy.data.filepath))[0]
+ if filename == '':
+ filename = 'untitled'
+ return bpy.path.clean_name(filename)
+
+def temp_directory():
+ '''
+ Return the system temp directory
+ '''
+ return tempfile.gettempdir()
+
+def temp_file(ext='tmp'):
+ '''
+ Get a temporary filename with the given extension
+ '''
+ tf, fn = tempfile.mkstemp(suffix='.%s'%ext)
+ os.close(tf)
+ return fn
+
+class TimerThread(threading.Thread):
+ '''
+ Periodically call self.kick()
+ '''
+ STARTUP_DELAY = 0
+ KICK_PERIOD = 8
+
+ active = True
+ timer = None
+
+ LocalStorage = None
+
+ def __init__(self, LocalStorage=dict()):
+ threading.Thread.__init__(self)
+ self.LocalStorage = LocalStorage
+
+ def set_kick_period(self, period):
+ self.KICK_PERIOD = period + self.STARTUP_DELAY
+
+ def stop(self):
+ self.active = False
+ if self.timer is not None:
+ self.timer.cancel()
+
+ def run(self):
+ '''
+ Timed Thread loop
+ '''
+
+ while self.active:
+ self.timer = threading.Timer(self.KICK_PERIOD, self.kick_caller)
+ self.timer.start()
+ if self.timer.isAlive(): self.timer.join()
+
+ def kick_caller(self):
+ if self.STARTUP_DELAY > 0:
+ self.KICK_PERIOD -= self.STARTUP_DELAY
+ self.STARTUP_DELAY = 0
+
+ self.kick()
+
+ def kick(self):
+ '''
+ sub-classes do their work here
+ '''
+ pass
+
+def format_elapsed_time(t):
+ '''
+ Format a duration in seconds as an HH:MM:SS format time
+ '''
+
+ td = datetime.timedelta(seconds=t)
+ min = td.days*1440 + td.seconds/60.0
+ hrs = td.days*24 + td.seconds/3600.0
+
+ return '%i:%02i:%02i' % (hrs, min%60, td.seconds%60)
diff --git a/release/scripts/modules/extensions_framework/validate.py b/release/scripts/modules/extensions_framework/validate.py
new file mode 100644
index 00000000000..b2960fcf01f
--- /dev/null
+++ b/release/scripts/modules/extensions_framework/validate.py
@@ -0,0 +1,208 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+'''
+Pure logic and validation class.
+
+By using a Subject object, and a dict of described logic tests, it
+is possible to arrive at a True or False result for various purposes:
+1. Data validation
+2. UI control visibility
+
+A Subject can be any object whose members are readable with getattr() :
+class Subject(object):
+ a = 0
+ b = 1
+ c = 'foo'
+ d = True
+ e = False
+ f = 8
+ g = 'bar'
+
+
+Tests are described thus:
+
+Use the special list types Logic_AND and Logic_OR to describe combinations
+of values and other members. Use Logic_Operator for numerical comparison.
+
+# With regards to Subject, each of these evaluate to True:
+TESTA = {
+ 'a': 0,
+ 'c': Logic_OR([ 'foo', 'bar' ]),
+ 'd': Logic_AND([True, True]),
+ 'f': Logic_AND([8, {'b': 1}]),
+ 'e': {'b': Logic_Operator({'gte':1, 'lt':3}) },
+ 'g': Logic_OR([ 'baz', Logic_AND([{'b': 1}, {'f': 8}]) ])
+}
+
+# With regards to Subject, each of these evaluate to False:
+TESTB = {
+ 'a': 'foo',
+ 'c': Logic_OR([ 'bar', 'baz' ]),
+ 'd': Logic_AND([ True, 'foo' ]),
+ 'f': Logic_AND([9, {'b': 1}]),
+ 'e': {'b': Logic_Operator({'gte':-10, 'lt': 1}) },
+ 'g': Logic_OR([ 'baz', Logic_AND([{'b':0}, {'f': 8}]) ])
+}
+
+# With regards to Subject, this test is invalid
+TESTC = {
+ 'n': 0
+}
+
+# Tests are executed thus:
+S = Subject()
+L = Logician(S)
+L.execute(TESTA)
+
+'''
+
+class Logic_AND(list):
+ pass
+class Logic_OR(list):
+ pass
+class Logic_Operator(dict):
+ pass
+
+class Logician(object):
+ '''
+ Given a subject and a dict that describes tests to perform on its members,
+ this class will evaluate True or False results for each member/test pair.
+
+ See the examples below for test syntax.
+ '''
+
+ subject = None
+ def __init__(self, subject):
+ self.subject = subject
+
+ def get_member(self, member_name):
+ '''
+ Get a member value from the subject object.
+ Raise exception is subject is None or member not found.
+ '''
+ if self.subject is None:
+ raise Exception('Cannot run tests on a subject which is None')
+
+ return getattr(self.subject, member_name)
+
+ def test_logic(self, member, logic, operator='eq'):
+ '''
+ Find the type of test to run on member, and perform that test
+ '''
+
+ if type(logic) is dict:
+ return self.test_dict(member, logic)
+ elif type(logic) is Logic_AND:
+ return self.test_and(member, logic)
+ elif type(logic) is Logic_OR:
+ return self.test_or(member, logic)
+ elif type(logic) is Logic_Operator:
+ return self.test_operator(member, logic)
+ else:
+ # compare the value, I think using Logic_Operator() here allows completeness in test_operator(),
+ # but I can't put my finger on why for the minute
+ return self.test_operator(member, Logic_Operator({operator: logic}))
+
+ def test_operator(self, member, value):
+ '''
+ execute the operators contained within value and expect that ALL operators are True
+ '''
+
+ # something in this method is incomplete, what if operand is a dict, Logic_AND, Logic_OR or another Logic_Operator ?
+ # do those constructs even make any sense ?
+
+ result = True
+ for operator, operand in value.items():
+ operator = operator.lower().strip()
+ if operator in ['eq', '==']:
+ result &= member==operand
+ if operator in ['not', '!=']:
+ result &= member!=operand
+ if operator in ['lt', '<']:
+ result &= member<operand
+ if operator in ['lte', '<=']:
+ result &= member<=operand
+ if operator in ['gt', '>']:
+ result &= member>operand
+ if operator in ['gte', '>=']:
+ result &= member>=operand
+ if operator in ['and', '&']:
+ result &= member&operand
+ if operator in ['or', '|']:
+ result &= member|operand
+ if operator in ['len']:
+ result &= len(member)==operand
+ # I can think of some more, but they're probably not useful.
+
+ return result
+
+ def test_or(self, member, logic):
+ '''
+ member is a value, logic is a set of values, ANY of which can be True
+ '''
+ result = False
+ for test in logic:
+ result |= self.test_logic(member, test)
+
+ return result
+
+ def test_and(self, member, logic):
+ '''
+ member is a value, logic is a list of values, ALL of which must be True
+ '''
+ result = True
+ for test in logic:
+ result &= self.test_logic(member, test)
+
+ return result
+
+ def test_dict(self, member, logic):
+ '''
+ member is a value, logic is a dict of other members to compare to. All other member tests must be True
+ '''
+ result = True
+ for other_member, test in logic.items():
+ result &= self.test_logic(self.get_member(other_member), test)
+
+ return result
+
+ def execute(self, test):
+ '''
+ subject is an object,
+ test is a dict of {member: test} pairs to perform on subject's members.
+ each key in test is a member of subject.
+ '''
+
+ for member_name, logic in test.items():
+ result = self.test_logic(self.get_member(member_name), logic)
+ print('member %s is %s' % (member_name, result))
+
+# A couple of name aliases
+class Validation(Logician):
+ pass
+class Visibility(Logician):
+ pass