diff --git a/modules/extensions_framework/__init__.py b/modules/extensions_framework/__init__.py
index df815eb3cb7a2671dffd1803f8c14cc8cf154ed2..af909b45ad13ad0bee7254d52a8fea364fd2dd4e 100644
--- a/modules/extensions_framework/__init__.py
+++ b/modules/extensions_framework/__init__.py
@@ -33,339 +33,339 @@ bpy.utils.register_class(EF_OT_msg)
 del EF_OT_msg
 
 def log(str, popup=False, module_name='EF'):
-	"""Print a message to the console, prefixed with the module_name
-	and the current time. If the popup flag is True, the message will
-	be raised in the UI as a warning using the operator bpy.ops.ef.msg.
-	
-	"""
-	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
-		)
+    """Print a message to the console, prefixed with the module_name
+    and the current time. If the popup flag is True, the message will
+    be raised in the UI as a warning using the operator bpy.ops.ef.msg.
+
+    """
+    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
+        )
 
 
 added_property_cache = {}
 
 def init_properties(obj, props, cache=True):
-	"""Initialise custom properties in the given object or type.
-	The props list is described in the declarative_property_group
-	class definition. If the cache flag is False, this function
-	will attempt to redefine properties even if they have already been
-	added.
-	
-	"""
-	
-	if not obj in added_property_cache.keys():
-		added_property_cache[obj] = []
-	
-	for prop in props:
-		try:
-			if cache and prop['attr'] in added_property_cache[obj]:
-				continue
-			
-			if prop['type'] == 'bool':
-				t = bpy.props.BoolProperty
-				a = {k: v for k,v in prop.items() if k in ["name",
-					"description","default","options","subtype","update"]}
-			elif prop['type'] == 'bool_vector':
-				t = bpy.props.BoolVectorProperty
-				a = {k: v for k,v in prop.items() if k in ["name",
-					"description","default","options","subtype","size",
-					"update"]}
-			elif prop['type'] == 'collection':
-				t = bpy.props.CollectionProperty
-				a = {k: v for k,v in prop.items() if k in ["ptype","name",
-					"description","default","options"]}
-				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","options","update"]}
-			elif prop['type'] == 'float':
-				t = bpy.props.FloatProperty
-				a = {k: v for k,v in prop.items() if k in ["name",
-					"description","default","min","max","soft_min","soft_max",
-					"step","precision","options","subtype","unit","update"]}
-			elif prop['type'] == 'float_vector':
-				t = bpy.props.FloatVectorProperty
-				a = {k: v for k,v in prop.items() if k in ["name",
-					"description","default","min","max","soft_min","soft_max",
-					"step","precision","options","subtype","size","update"]}
-			elif prop['type'] == 'int':
-				t = bpy.props.IntProperty
-				a = {k: v for k,v in prop.items() if k in ["name",
-					"description","default","min","max","soft_min","soft_max",
-					"step","options","subtype","update"]}
-			elif prop['type'] == 'int_vector':
-				t = bpy.props.IntVectorProperty
-				a = {k: v for k,v in prop.items() if k in ["name",
-					"description","default","min","max","soft_min","soft_max",
-					"options","subtype","size","update"]}
-			elif prop['type'] == 'pointer':
-				t = bpy.props.PointerProperty
-				a = {k: v for k,v in prop.items() if k in ["ptype", "name",
-					"description","options","update"]}
-				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","default","maxlen","options","subtype",
-					"update"]}
-			else:
-				continue
-			
-			setattr(obj, prop['attr'], t(**a))
-			
-			added_property_cache[obj].append(prop['attr'])
-		except KeyError:
-			# Silently skip invalid entries in props
-			continue
+    """Initialise custom properties in the given object or type.
+    The props list is described in the declarative_property_group
+    class definition. If the cache flag is False, this function
+    will attempt to redefine properties even if they have already been
+    added.
+
+    """
+
+    if not obj in added_property_cache.keys():
+        added_property_cache[obj] = []
+
+    for prop in props:
+        try:
+            if cache and prop['attr'] in added_property_cache[obj]:
+                continue
+
+            if prop['type'] == 'bool':
+                t = bpy.props.BoolProperty
+                a = {k: v for k,v in prop.items() if k in ["name",
+                    "description","default","options","subtype","update"]}
+            elif prop['type'] == 'bool_vector':
+                t = bpy.props.BoolVectorProperty
+                a = {k: v for k,v in prop.items() if k in ["name",
+                    "description","default","options","subtype","size",
+                    "update"]}
+            elif prop['type'] == 'collection':
+                t = bpy.props.CollectionProperty
+                a = {k: v for k,v in prop.items() if k in ["ptype","name",
+                    "description","default","options"]}
+                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","options","update"]}
+            elif prop['type'] == 'float':
+                t = bpy.props.FloatProperty
+                a = {k: v for k,v in prop.items() if k in ["name",
+                    "description","default","min","max","soft_min","soft_max",
+                    "step","precision","options","subtype","unit","update"]}
+            elif prop['type'] == 'float_vector':
+                t = bpy.props.FloatVectorProperty
+                a = {k: v for k,v in prop.items() if k in ["name",
+                    "description","default","min","max","soft_min","soft_max",
+                    "step","precision","options","subtype","size","update"]}
+            elif prop['type'] == 'int':
+                t = bpy.props.IntProperty
+                a = {k: v for k,v in prop.items() if k in ["name",
+                    "description","default","min","max","soft_min","soft_max",
+                    "step","options","subtype","update"]}
+            elif prop['type'] == 'int_vector':
+                t = bpy.props.IntVectorProperty
+                a = {k: v for k,v in prop.items() if k in ["name",
+                    "description","default","min","max","soft_min","soft_max",
+                    "options","subtype","size","update"]}
+            elif prop['type'] == 'pointer':
+                t = bpy.props.PointerProperty
+                a = {k: v for k,v in prop.items() if k in ["ptype", "name",
+                    "description","options","update"]}
+                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","default","maxlen","options","subtype",
+                    "update"]}
+            else:
+                continue
+
+            setattr(obj, prop['attr'], t(**a))
+
+            added_property_cache[obj].append(prop['attr'])
+        except KeyError:
+            # Silently skip invalid entries in props
+            continue
 
 class declarative_property_group(bpy.types.PropertyGroup):
-	"""A declarative_property_group describes a set of logically
-	related properties, using a declarative style to list each
-	property type, name, values, and other relevant information.
-	The information provided for each property depends on the
-	property's type.
-	
-	The properties list attribute in this class describes the
-	properties present in this group.
-	
-	Some additional information about the properties in this group
-	can be specified, so that a UI can be generated to display them.
-	To that end, the controls list attribute and the visibility dict
-	attribute are present here, to be read and interpreted by a
-	property_group_renderer object.
-	See extensions_framework.ui.property_group_renderer.
-	
-	"""
-	
-	ef_initialised = False
-	
-	"""This property tells extensions_framework which bpy.type(s)
-	to attach this PropertyGroup to. If left as an empty list,
-	it will not be attached to any type, but its properties will
-	still be initialised. The type(s) given in the list should be
-	a string, such as 'Scene'.
-	
-	"""
-	ef_attach_to = []
-	
-	@classmethod
-	def initialise_properties(cls):
-		"""This is a function that should be called on
-		sub-classes of declarative_property_group in order
-		to ensure that they are initialised when the addon
-		is loaded.
-		the init_properties is called without caching here,
-		as it is assumed that any addon calling this function
-		will also call ef_remove_properties when it is
-		unregistered.
-		
-		"""
-		
-		if not cls.ef_initialised:
-			for property_group_parent in cls.ef_attach_to:
-				if property_group_parent is not None:
-					prototype = getattr(bpy.types, property_group_parent)
-					if not hasattr(prototype, cls.__name__):
-						init_properties(prototype, [{
-							'type': 'pointer',
-							'attr': cls.__name__,
-							'ptype': cls,
-							'name': cls.__name__,
-							'description': cls.__name__
-						}], cache=False)
-			
-			init_properties(cls, cls.properties, cache=False)
-			cls.ef_initialised = True
-		
-		return cls
-	
-	@classmethod
-	def register_initialise_properties(cls):
-		"""As ef_initialise_properties, but also registers the
-		class with RNA. Note that this isn't a great idea
-		because it's non-trivial to unregister the class, unless
-		you keep track of it yourself.
-		"""
-		
-		bpy.utils.register_class(cls)
-		cls.initialise_properties()
-		return cls
-	
-	@classmethod
-	def remove_properties(cls):
-		"""This is a function that should be called on
-		sub-classes of declarative_property_group in order
-		to ensure that they are un-initialised when the addon
-		is unloaded.
-		
-		"""
-		
-		if cls.ef_initialised:
-			prototype = getattr(bpy.types, cls.__name__)
-			for prop in cls.properties:
-				if hasattr(prototype, prop['attr']):
-					delattr(prototype, prop['attr'])
-			
-			for property_group_parent in cls.ef_attach_to:
-				if property_group_parent is not None:
-					prototype = getattr(bpy.types, property_group_parent)
-					if hasattr(prototype, cls.__name__):
-						delattr(prototype, cls.__name__)
-			
-			cls.ef_initialised = False
-		
-		return cls
-	
-	
-	"""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.
-	
-	"""
-	controls = []
-	
-	"""The visibility dict controls the visibility of properties based on
-	the value of other properties. See extensions_framework.validate
-	for test syntax.
-	
-	"""
-	visibility = {}
-	
-	"""The enabled dict controls the enabled state of properties based on
-	the value of other properties. See extensions_framework.validate
-	for test syntax.
-	
-	"""
-	enabled = {}
-	
-	"""The alert dict controls the alert state of properties based on
-	the value of other properties. See extensions_framework.validate
-	for test syntax.
-	
-	"""
-	alert = {}
-	
-	"""The properties list describes each property to be created. Each
-	item should be a dict of args to pass to a
-	bpy.props.<?>Property function, with the exception of 'type'
-	which is used and stripped by extensions_framework in order to
-	determine which Property creation function to call.
-	
-	Example item:
-	{
-		'type': 'int',								# bpy.props.IntProperty
-		'attr': 'threads',							# bpy.types.<type>.threads
-		'name': 'Render Threads',					# Rendered next to the UI
-		'description': 'Number of threads to use',	# Tooltip text in the UI
-		'default': 1,
-		'min': 1,
-		'soft_min': 1,
-		'max': 64,
-		'soft_max': 64
-	}
-	
-	"""
-	properties = []
-	
-	def draw_callback(self, context):
-		"""Sub-classes can override this to get a callback when
-		rendering is completed by a property_group_renderer sub-class.
-		
-		"""
-		
-		pass
-	
-	@classmethod
-	def get_exportable_properties(cls):
-		"""Return a list of properties which have the 'save_in_preset' key
-		set to True, and hence should be saved into preset files.
-		
-		"""
-		
-		out = []
-		for prop in cls.properties:
-			if 'save_in_preset' in prop.keys() and prop['save_in_preset']:
-				out.append(prop)
-		return out
-	
-	def reset(self):
-		"""Reset all properties in this group to the default value,
-		if specified"""
-		for prop in self.properties:
-			pk = prop.keys()
-			if 'attr' in pk and 'default' in pk and hasattr(self, prop['attr']):
-				setattr(self, prop['attr'], prop['default'])
+    """A declarative_property_group describes a set of logically
+    related properties, using a declarative style to list each
+    property type, name, values, and other relevant information.
+    The information provided for each property depends on the
+    property's type.
+
+    The properties list attribute in this class describes the
+    properties present in this group.
+
+    Some additional information about the properties in this group
+    can be specified, so that a UI can be generated to display them.
+    To that end, the controls list attribute and the visibility dict
+    attribute are present here, to be read and interpreted by a
+    property_group_renderer object.
+    See extensions_framework.ui.property_group_renderer.
+
+    """
+
+    ef_initialised = False
+
+    """This property tells extensions_framework which bpy.type(s)
+    to attach this PropertyGroup to. If left as an empty list,
+    it will not be attached to any type, but its properties will
+    still be initialised. The type(s) given in the list should be
+    a string, such as 'Scene'.
+
+    """
+    ef_attach_to = []
+
+    @classmethod
+    def initialise_properties(cls):
+        """This is a function that should be called on
+        sub-classes of declarative_property_group in order
+        to ensure that they are initialised when the addon
+        is loaded.
+        the init_properties is called without caching here,
+        as it is assumed that any addon calling this function
+        will also call ef_remove_properties when it is
+        unregistered.
+
+        """
+
+        if not cls.ef_initialised:
+            for property_group_parent in cls.ef_attach_to:
+                if property_group_parent is not None:
+                    prototype = getattr(bpy.types, property_group_parent)
+                    if not hasattr(prototype, cls.__name__):
+                        init_properties(prototype, [{
+                            'type': 'pointer',
+                            'attr': cls.__name__,
+                            'ptype': cls,
+                            'name': cls.__name__,
+                            'description': cls.__name__
+                        }], cache=False)
+
+            init_properties(cls, cls.properties, cache=False)
+            cls.ef_initialised = True
+
+        return cls
+
+    @classmethod
+    def register_initialise_properties(cls):
+        """As ef_initialise_properties, but also registers the
+        class with RNA. Note that this isn't a great idea
+        because it's non-trivial to unregister the class, unless
+        you keep track of it yourself.
+        """
+
+        bpy.utils.register_class(cls)
+        cls.initialise_properties()
+        return cls
+
+    @classmethod
+    def remove_properties(cls):
+        """This is a function that should be called on
+        sub-classes of declarative_property_group in order
+        to ensure that they are un-initialised when the addon
+        is unloaded.
+
+        """
+
+        if cls.ef_initialised:
+            prototype = getattr(bpy.types, cls.__name__)
+            for prop in cls.properties:
+                if hasattr(prototype, prop['attr']):
+                    delattr(prototype, prop['attr'])
+
+            for property_group_parent in cls.ef_attach_to:
+                if property_group_parent is not None:
+                    prototype = getattr(bpy.types, property_group_parent)
+                    if hasattr(prototype, cls.__name__):
+                        delattr(prototype, cls.__name__)
+
+            cls.ef_initialised = False
+
+        return cls
+
+
+    """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.
+
+    """
+    controls = []
+
+    """The visibility dict controls the visibility of properties based on
+    the value of other properties. See extensions_framework.validate
+    for test syntax.
+
+    """
+    visibility = {}
+
+    """The enabled dict controls the enabled state of properties based on
+    the value of other properties. See extensions_framework.validate
+    for test syntax.
+
+    """
+    enabled = {}
+
+    """The alert dict controls the alert state of properties based on
+    the value of other properties. See extensions_framework.validate
+    for test syntax.
+
+    """
+    alert = {}
+
+    """The properties list describes each property to be created. Each
+    item should be a dict of args to pass to a
+    bpy.props.<?>Property function, with the exception of 'type'
+    which is used and stripped by extensions_framework in order to
+    determine which Property creation function to call.
+
+    Example item:
+    {
+        'type': 'int',                              # bpy.props.IntProperty
+        'attr': 'threads',                          # bpy.types.<type>.threads
+        'name': 'Render Threads',                   # Rendered next to the UI
+        'description': 'Number of threads to use',  # Tooltip text in the UI
+        'default': 1,
+        'min': 1,
+        'soft_min': 1,
+        'max': 64,
+        'soft_max': 64
+    }
+
+    """
+    properties = []
+
+    def draw_callback(self, context):
+        """Sub-classes can override this to get a callback when
+        rendering is completed by a property_group_renderer sub-class.
+
+        """
+
+        pass
+
+    @classmethod
+    def get_exportable_properties(cls):
+        """Return a list of properties which have the 'save_in_preset' key
+        set to True, and hence should be saved into preset files.
+
+        """
+
+        out = []
+        for prop in cls.properties:
+            if 'save_in_preset' in prop.keys() and prop['save_in_preset']:
+                out.append(prop)
+        return out
+
+    def reset(self):
+        """Reset all properties in this group to the default value,
+        if specified"""
+        for prop in self.properties:
+            pk = prop.keys()
+            if 'attr' in pk and 'default' in pk and hasattr(self, prop['attr']):
+                setattr(self, prop['attr'], prop['default'])
 
 class Addon(object):
-	"""A list of classes registered by this addon"""
-	static_addon_count = 0
-	
-	addon_serial = 0
-	addon_classes = None
-	bl_info = None
-	
-	BL_VERSION = None
-	BL_IDNAME = None
-	
-	def __init__(self, bl_info=None):
-		self.addon_classes = []
-		self.bl_info = bl_info
-		
-		# Keep a count in case we have to give this addon an anonymous name
-		self.addon_serial = Addon.static_addon_count
-		Addon.static_addon_count += 1
-		
-		if self.bl_info:
-			self.BL_VERSION = '.'.join(['%s'%v for v in self.bl_info['version']]).lower()
-			self.BL_IDNAME = self.bl_info['name'].lower() + '-' + self.BL_VERSION
-		else:
-			# construct anonymous name
-			self.BL_VERSION = '0'
-			self.BL_IDNAME = 'Addon-%03d'%self.addon_serial
-	
-	def addon_register_class(self, cls):
-		"""This method is designed to be used as a decorator on RNA-registerable
-		classes defined by the addon. By using this decorator, this class will
-		keep track of classes registered by this addon so that they can be
-		unregistered later in the correct order.
-		
-		"""
-		self.addon_classes.append(cls)
-		return cls
-	
-	def register(self):
-		"""This is the register function that should be exposed in the addon's
-		__init__.
-		
-		"""
-		for cls in self.addon_classes:
-			bpy.utils.register_class(cls)
-			if hasattr(cls, 'ef_attach_to'): cls.initialise_properties()
-	
-	def unregister(self):
-		"""This is the unregister function that should be exposed in the addon's
-		__init__.
-		
-		"""
-		for cls in self.addon_classes[::-1]:	# unregister in reverse order
-			if hasattr(cls, 'ef_attach_to'): cls.remove_properties()
-			bpy.utils.unregister_class(cls)
-	
-	def init_functions(self):
-		"""Returns references to the three functions that this addon needs
-		for successful class registration management. In the addon's __init__
-		you would use like this:
-		
-		addon_register_class, register, unregister = Addon().init_functions()
-		
-		"""
-		
-		return self.register, self.unregister
+    """A list of classes registered by this addon"""
+    static_addon_count = 0
+
+    addon_serial = 0
+    addon_classes = None
+    bl_info = None
+
+    BL_VERSION = None
+    BL_IDNAME = None
+
+    def __init__(self, bl_info=None):
+        self.addon_classes = []
+        self.bl_info = bl_info
+
+        # Keep a count in case we have to give this addon an anonymous name
+        self.addon_serial = Addon.static_addon_count
+        Addon.static_addon_count += 1
+
+        if self.bl_info:
+            self.BL_VERSION = '.'.join(['%s'%v for v in self.bl_info['version']]).lower()
+            self.BL_IDNAME = self.bl_info['name'].lower() + '-' + self.BL_VERSION
+        else:
+            # construct anonymous name
+            self.BL_VERSION = '0'
+            self.BL_IDNAME = 'Addon-%03d'%self.addon_serial
+
+    def addon_register_class(self, cls):
+        """This method is designed to be used as a decorator on RNA-registerable
+        classes defined by the addon. By using this decorator, this class will
+        keep track of classes registered by this addon so that they can be
+        unregistered later in the correct order.
+
+        """
+        self.addon_classes.append(cls)
+        return cls
+
+    def register(self):
+        """This is the register function that should be exposed in the addon's
+        __init__.
+
+        """
+        for cls in self.addon_classes:
+            bpy.utils.register_class(cls)
+            if hasattr(cls, 'ef_attach_to'): cls.initialise_properties()
+
+    def unregister(self):
+        """This is the unregister function that should be exposed in the addon's
+        __init__.
+
+        """
+        for cls in self.addon_classes[::-1]:    # unregister in reverse order
+            if hasattr(cls, 'ef_attach_to'): cls.remove_properties()
+            bpy.utils.unregister_class(cls)
+
+    def init_functions(self):
+        """Returns references to the three functions that this addon needs
+        for successful class registration management. In the addon's __init__
+        you would use like this:
+
+        addon_register_class, register, unregister = Addon().init_functions()
+
+        """
+
+        return self.register, self.unregister
diff --git a/modules/extensions_framework/ui.py b/modules/extensions_framework/ui.py
index 4f24d873471e5d663b9c72f3cf0876171592f654..944309111ec19e0504d8fad363a4d8543362a41e 100644
--- a/modules/extensions_framework/ui.py
+++ b/modules/extensions_framework/ui.py
@@ -29,309 +29,309 @@ import bpy
 from extensions_framework.validate import Logician
 
 class EF_OT_msg(bpy.types.Operator):
-	"""An operator to show simple messages in the UI"""
-	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'}
+    """An operator to show simple messages in the UI"""
+    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):
-	"""Utility to get an object when the path to it is known:
-	_get_item_from_context(context, ['a','b','c']) returns
-	context.a.b.c
-	No error checking is performed other than checking that context
-	is not None. Exceptions caused by invalid path should be caught in
-	the calling code.
-	
-	"""
-	
-	if context is not None:
-		for p in path:
-			context = getattr(context, p)
-	return context
+    """Utility to get an object when the path to it is known:
+    _get_item_from_context(context, ['a','b','c']) returns
+    context.a.b.c
+    No error checking is performed other than checking that context
+    is not None. Exceptions caused by invalid path should be caught in
+    the calling code.
+
+    """
+
+    if context is not None:
+        for p in path:
+            context = getattr(context, p)
+    return context
 
 class property_group_renderer(bpy.types.Panel):
-	"""Mix-in class for sub-classes of bpy.types.Panel. This class
-	will provide the draw() method which implements drawing one or
-	more property groups derived from
-	extensions_framework.declarative_propery_group.
-	The display_property_groups list attribute describes which
-	declarative_property_groups should be drawn in the Panel, and
-	how to extract those groups from the context passed to draw().
-	
-	"""
-	
-	"""The display_property_groups list attribute specifies which
-	custom declarative_property_groups this panel should draw, and
-	where to find that property group in the active context.
-	Example item:
-		( ('scene',), 'myaddon_property_group')
-	In this case, this renderer will look for properties in
-	context.scene.myaddon_property_group to draw in the Panel.
-	
-	"""
-	display_property_groups = []
-	
-	def draw(self, context):
-		"""Sub-classes should override this if they need to display
-		other (object-related) property groups. super().draw(context)
-		can be a useful call in those cases.
-		
-		"""
-		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)
-	
-	def check_visibility(self, lookup_property, property_group):
-		"""Determine if the lookup_property should be drawn in the Panel"""
-		vt = Logician(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 check_enabled(self, lookup_property, property_group):
-		"""Determine if the lookup_property should be enabled in the Panel"""
-		et = Logician(property_group)
-		if lookup_property in property_group.enabled.keys():
-			if hasattr(property_group, lookup_property):
-				member = getattr(property_group, lookup_property)
-			else:
-				member = None
-			return et.test_logic(member,
-				property_group.enabled[lookup_property])
-		else:
-			return True
-	
-	def check_alert(self, lookup_property, property_group):
-		"""Determine if the lookup_property should be in an alert state in the Panel"""
-		et = Logician(property_group)
-		if lookup_property in property_group.alert.keys():
-			if hasattr(property_group, lookup_property):
-				member = getattr(property_group, lookup_property)
-			else:
-				member = None
-			return et.test_logic(member,
-				property_group.alert[lookup_property])
-		else:
-			return False
-	
-	def is_real_property(self, lookup_property, property_group):
-		for prop in property_group.properties:
-			if prop['attr'] == lookup_property:
-				return prop['type'] not in ['text', 'prop_search']
-		
-		return False
-	
-	def draw_column(self, control_list_item, layout, context,
-					supercontext=None, property_group=None):
-		"""Draw a column's worth of UI controls in this Panel"""
-		if type(control_list_item) is list:
-			draw_row = 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 [s for s in sp if self.is_real_property(s, property_group)]:
-						draw_row = draw_row or self.check_visibility(ssp,
-							property_group)
-				else:
-					draw_row = draw_row or self.check_visibility(sp,
-						property_group)
-			
-			next_items = [s for s in control_list_item if type(s) in [str, list]]
-			if draw_row and len(next_items) > 0:
-				if found_percent is not None:
-					splt = layout.split(percentage=found_percent)
-				else:
-					splt = layout.row(True)
-				for sp in next_items:
-					col2 = splt.column()
-					self.draw_column(sp, col2, context, supercontext,
-						property_group)
-		else:
-			if self.check_visibility(control_list_item, property_group):
-				
-				for current_property in property_group.properties:
-					if current_property['attr'] == control_list_item:
-						current_property_keys = current_property.keys() 
-						
-						sub_layout_created = False
-						if not self.check_enabled(control_list_item, property_group):
-							last_layout = layout
-							sub_layout_created = True
-							
-							layout = layout.row()
-							layout.enabled = False
-						
-						if self.check_alert(control_list_item, property_group):
-							if not sub_layout_created:
-								last_layout = layout
-								sub_layout_created = True
-							layout = layout.row()
-							layout.alert = True
-						
-						if 'type' in current_property_keys:
-							if current_property['type'] in ['int', 'float',
-								'float_vector', '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 ['enum']:
-								if 'use_menu' in current_property_keys and \
-									current_property['use_menu']:
-									layout.prop_menu_enum(
-										property_group,
-										control_list_item,
-										text = current_property['name']
-									)
-								else:
-									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']:
-								args = {}
-								for optional_arg in ('text', 'icon'):
-									if optional_arg in current_property_keys:
-										args.update({
-											optional_arg: current_property[optional_arg],
-										})
-								layout.operator( current_property['operator'], **args )
-							
-							elif current_property['type'] in ['menu']:
-								args = {}
-								for optional_arg in ('text', 'icon'):
-									if optional_arg in current_property_keys:
-										args.update({
-											optional_arg: current_property[optional_arg],
-										})
-								layout.menu(current_property['menu'], **args)
-							
-							elif current_property['type'] in ['text']:
-								layout.label(
-									text = current_property['name']
-								)
-								
-							elif current_property['type'] in ['template_list']:
-								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']:
-								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'],
-								)
-							
-							elif current_property['type'] in ['ef_callback']:
-								getattr(self, current_property['method'])(supercontext)
-						else:
-							layout.prop(property_group, control_list_item)
-						
-						if sub_layout_created:
-							layout = last_layout
-						
-						# Fire a draw callback if specified
-						if 'draw' in current_property_keys:
-							current_property['draw'](supercontext, context)
-						
-						break
+    """Mix-in class for sub-classes of bpy.types.Panel. This class
+    will provide the draw() method which implements drawing one or
+    more property groups derived from
+    extensions_framework.declarative_propery_group.
+    The display_property_groups list attribute describes which
+    declarative_property_groups should be drawn in the Panel, and
+    how to extract those groups from the context passed to draw().
+
+    """
+
+    """The display_property_groups list attribute specifies which
+    custom declarative_property_groups this panel should draw, and
+    where to find that property group in the active context.
+    Example item:
+        ( ('scene',), 'myaddon_property_group')
+    In this case, this renderer will look for properties in
+    context.scene.myaddon_property_group to draw in the Panel.
+
+    """
+    display_property_groups = []
+
+    def draw(self, context):
+        """Sub-classes should override this if they need to display
+        other (object-related) property groups. super().draw(context)
+        can be a useful call in those cases.
+
+        """
+        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)
+
+    def check_visibility(self, lookup_property, property_group):
+        """Determine if the lookup_property should be drawn in the Panel"""
+        vt = Logician(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 check_enabled(self, lookup_property, property_group):
+        """Determine if the lookup_property should be enabled in the Panel"""
+        et = Logician(property_group)
+        if lookup_property in property_group.enabled.keys():
+            if hasattr(property_group, lookup_property):
+                member = getattr(property_group, lookup_property)
+            else:
+                member = None
+            return et.test_logic(member,
+                property_group.enabled[lookup_property])
+        else:
+            return True
+
+    def check_alert(self, lookup_property, property_group):
+        """Determine if the lookup_property should be in an alert state in the Panel"""
+        et = Logician(property_group)
+        if lookup_property in property_group.alert.keys():
+            if hasattr(property_group, lookup_property):
+                member = getattr(property_group, lookup_property)
+            else:
+                member = None
+            return et.test_logic(member,
+                property_group.alert[lookup_property])
+        else:
+            return False
+
+    def is_real_property(self, lookup_property, property_group):
+        for prop in property_group.properties:
+            if prop['attr'] == lookup_property:
+                return prop['type'] not in ['text', 'prop_search']
+
+        return False
+
+    def draw_column(self, control_list_item, layout, context,
+                    supercontext=None, property_group=None):
+        """Draw a column's worth of UI controls in this Panel"""
+        if type(control_list_item) is list:
+            draw_row = 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 [s for s in sp if self.is_real_property(s, property_group)]:
+                        draw_row = draw_row or self.check_visibility(ssp,
+                            property_group)
+                else:
+                    draw_row = draw_row or self.check_visibility(sp,
+                        property_group)
+
+            next_items = [s for s in control_list_item if type(s) in [str, list]]
+            if draw_row and len(next_items) > 0:
+                if found_percent is not None:
+                    splt = layout.split(percentage=found_percent)
+                else:
+                    splt = layout.row(True)
+                for sp in next_items:
+                    col2 = splt.column()
+                    self.draw_column(sp, col2, context, supercontext,
+                        property_group)
+        else:
+            if self.check_visibility(control_list_item, property_group):
+
+                for current_property in property_group.properties:
+                    if current_property['attr'] == control_list_item:
+                        current_property_keys = current_property.keys()
+
+                        sub_layout_created = False
+                        if not self.check_enabled(control_list_item, property_group):
+                            last_layout = layout
+                            sub_layout_created = True
+
+                            layout = layout.row()
+                            layout.enabled = False
+
+                        if self.check_alert(control_list_item, property_group):
+                            if not sub_layout_created:
+                                last_layout = layout
+                                sub_layout_created = True
+                            layout = layout.row()
+                            layout.alert = True
+
+                        if 'type' in current_property_keys:
+                            if current_property['type'] in ['int', 'float',
+                                'float_vector', '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 ['enum']:
+                                if 'use_menu' in current_property_keys and \
+                                    current_property['use_menu']:
+                                    layout.prop_menu_enum(
+                                        property_group,
+                                        control_list_item,
+                                        text = current_property['name']
+                                    )
+                                else:
+                                    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']:
+                                args = {}
+                                for optional_arg in ('text', 'icon'):
+                                    if optional_arg in current_property_keys:
+                                        args.update({
+                                            optional_arg: current_property[optional_arg],
+                                        })
+                                layout.operator( current_property['operator'], **args )
+
+                            elif current_property['type'] in ['menu']:
+                                args = {}
+                                for optional_arg in ('text', 'icon'):
+                                    if optional_arg in current_property_keys:
+                                        args.update({
+                                            optional_arg: current_property[optional_arg],
+                                        })
+                                layout.menu(current_property['menu'], **args)
+
+                            elif current_property['type'] in ['text']:
+                                layout.label(
+                                    text = current_property['name']
+                                )
+
+                            elif current_property['type'] in ['template_list']:
+                                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']:
+                                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'],
+                                )
+
+                            elif current_property['type'] in ['ef_callback']:
+                                getattr(self, current_property['method'])(supercontext)
+                        else:
+                            layout.prop(property_group, control_list_item)
+
+                        if sub_layout_created:
+                            layout = last_layout
+
+                        # Fire a draw callback if specified
+                        if 'draw' in current_property_keys:
+                            current_property['draw'](supercontext, context)
+
+                        break
diff --git a/modules/extensions_framework/util.py b/modules/extensions_framework/util.py
index b48c702676887f2839b26f2b0c85fc78d6844ae7..dd71e55af633ad0f96e55aad1237373474c9171d 100644
--- a/modules/extensions_framework/util.py
+++ b/modules/extensions_framework/util.py
@@ -49,222 +49,222 @@ 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)
-	ep = os.path.dirname(export_path)
-	
-	if os.sys.platform[:3] == "win":
-		# Prevent an error whereby python thinks C: and c: are different drives
-		if p[1] == ':': p = p[0].lower() + p[1:]
-		if ep[1] == ':': ep = ep[0].lower() + ep[1:]
-	
-	try:
-		relp = os.path.relpath(p, ep)
-	except ValueError: # path on different drive on windows
-		relp = p
-	
-	return relp.replace('\\', '/')
+    """Return a path that is relative to the export path"""
+    global export_path
+    p = filesystem_path(p)
+    ep = os.path.dirname(export_path)
+
+    if os.sys.platform[:3] == "win":
+        # Prevent an error whereby python thinks C: and c: are different drives
+        if p[1] == ':': p = p[0].lower() + p[1:]
+        if ep[1] == ':': ep = ep[0].lower() + ep[1:]
+
+    try:
+        relp = os.path.relpath(p, ep)
+    except ValueError: # path on different drive on windows
+        relp = p
+
+    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)
-	
-	return pout.replace('\\', '/')
+    """Resolve a relative Blender path to a real filesystem path"""
+    if p.startswith('//'):
+        pout = bpy.path.abspath(p)
+    else:
+        pout = os.path.realpath(p)
+
+    return pout.replace('\\', '/')
 
 # TODO: - somehow specify TYPES to get/set from config
 
 def find_config_value(module, section, key, default):
-	"""Attempt to find the configuration value specified by string key
-	in the specified section of module's configuration file. If it is
-	not found, return 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
+    """Attempt to find the configuration value specified by string key
+    in the specified section of module's configuration file. If it is
+    not found, return 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):
-	"""Attempt to write the configuration value specified by string key
-	in the specified section of module's configuration file.
-	
-	"""
-	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
+    """Attempt to write the configuration value specified by string key
+    in the specified section of module's configuration file.
+
+    """
+    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, using 'untitled' instead of ''"""
-	filename = os.path.splitext(os.path.basename(bpy.data.filepath))[0]
-	if filename == '':
-		filename = 'untitled'
-	return bpy.path.clean_name(filename)
+    """Construct a safe scene filename, using 'untitled' instead of ''"""
+    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()
+    """Return the system temp directory"""
+    return tempfile.gettempdir()
 
 def temp_file(ext='tmp'):
-	"""Get a temporary filename with the given extension. This function
-	will actually attempt to create the file."""
-	tf, fn = tempfile.mkstemp(suffix='.%s'%ext)
-	os.close(tf)
-	return fn
+    """Get a temporary filename with the given extension. This function
+    will actually attempt to create the file."""
+    tf, fn = tempfile.mkstemp(suffix='.%s'%ext)
+    os.close(tf)
+    return fn
 
 class TimerThread(threading.Thread):
-	"""Periodically call self.kick(). The period of time in seconds
-	between calling is given by self.KICK_PERIOD, and the first call
-	may be delayed by setting self.STARTUP_DELAY, also in seconds.
-	self.kick() will continue to be called at regular intervals until
-	self.stop() is called. Since this is a thread, calling self.join()
-	may be wise after calling self.stop() if self.kick() is performing
-	a task necessary for the continuation of the program.
-	The object that creates this TimerThread may pass into it data
-	needed during self.kick() as a dict LocalStorage in __init__().
-	
-	"""
-	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):
-		"""Adjust the KICK_PERIOD between __init__() and start()"""
-		self.KICK_PERIOD = period + self.STARTUP_DELAY
-	
-	def stop(self):
-		"""Stop this timer. This method does not join()"""
-		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):
-		"""Intermediary between the kick-wait-loop and kick to allow
-		adjustment of the first KICK_PERIOD by STARTUP_DELAY
-		
-		"""
-		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
+    """Periodically call self.kick(). The period of time in seconds
+    between calling is given by self.KICK_PERIOD, and the first call
+    may be delayed by setting self.STARTUP_DELAY, also in seconds.
+    self.kick() will continue to be called at regular intervals until
+    self.stop() is called. Since this is a thread, calling self.join()
+    may be wise after calling self.stop() if self.kick() is performing
+    a task necessary for the continuation of the program.
+    The object that creates this TimerThread may pass into it data
+    needed during self.kick() as a dict LocalStorage in __init__().
+
+    """
+    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):
+        """Adjust the KICK_PERIOD between __init__() and start()"""
+        self.KICK_PERIOD = period + self.STARTUP_DELAY
+
+    def stop(self):
+        """Stop this timer. This method does not join()"""
+        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):
+        """Intermediary between the kick-wait-loop and kick to allow
+        adjustment of the first KICK_PERIOD by STARTUP_DELAY
+
+        """
+        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)
+    """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)
 
 def getSequenceTexturePath(it, f):
-	import bpy.path
-	import os.path
-	import string
-	fd = it.image_user.frame_duration
-	fs = it.image_user.frame_start
-	fo = it.image_user.frame_offset
-	cyclic = it.image_user.use_cyclic
-	ext = os.path.splitext(it.image.filepath)[-1]
-	fb = bpy.path.display_name_from_filepath(it.image.filepath)
-	dn = os.path.dirname(it.image.filepath)
-	rf = fb[::-1]
-	nl = 0
-	for i in range (len(fb)):
-		if rf[i] in string.digits:
-			nl += 1
-		else:
-			break
-	head = fb[:len(fb)-nl]
-	fnum = f
-	if fs != 1:
-		if f != fs:
-			fnum -= (fs-1)
-		elif f == fs:
-			fnum = 1
-	if fnum <= 0:
-		if cyclic:
-			fnum = fd - abs(fnum) % fd
-		else:
-			fnum = 1
-	elif fnum > fd:
-		if cyclic:
-			fnum = fnum % fd
-		else:
-			fnum = fd
-	fnum += fo
-	return dn + "/" + head + str(fnum).rjust(nl, "0") + ext
+    import bpy.path
+    import os.path
+    import string
+    fd = it.image_user.frame_duration
+    fs = it.image_user.frame_start
+    fo = it.image_user.frame_offset
+    cyclic = it.image_user.use_cyclic
+    ext = os.path.splitext(it.image.filepath)[-1]
+    fb = bpy.path.display_name_from_filepath(it.image.filepath)
+    dn = os.path.dirname(it.image.filepath)
+    rf = fb[::-1]
+    nl = 0
+    for i in range (len(fb)):
+        if rf[i] in string.digits:
+            nl += 1
+        else:
+            break
+    head = fb[:len(fb)-nl]
+    fnum = f
+    if fs != 1:
+        if f != fs:
+            fnum -= (fs-1)
+        elif f == fs:
+            fnum = 1
+    if fnum <= 0:
+        if cyclic:
+            fnum = fd - abs(fnum) % fd
+        else:
+            fnum = 1
+    elif fnum > fd:
+        if cyclic:
+            fnum = fnum % fd
+        else:
+            fnum = fd
+    fnum += fo
+    return dn + "/" + head + str(fnum).rjust(nl, "0") + ext
diff --git a/modules/extensions_framework/validate.py b/modules/extensions_framework/validate.py
index d9cee8fd807152860f6c01977e92c328d4621376..5b20552ba593d63ac10aa0dcef4cce25b468fcea 100644
--- a/modules/extensions_framework/validate.py
+++ b/modules/extensions_framework/validate.py
@@ -34,13 +34,13 @@ is possible to arrive at a True or False result for various purposes:
 
 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'
+    a = 0
+    b = 1
+    c = 'foo'
+    d = True
+    e = False
+    f = 8
+    g = 'bar'
 
 
 Tests are described thus:
@@ -51,27 +51,27 @@ 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}]) ])
+    '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}]) ])
+    '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
+    'n': 0
 }
 
 Tests are executed thus:
@@ -82,132 +82,132 @@ L.execute(TESTA)
 """
 
 class Logic_AND(list):
-	pass
+    pass
 class Logic_OR(list):
-	pass
+    pass
 class Logic_Operator(dict):
-	pass
+    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
-		if 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. Wach 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			
+    """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
+        if 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. Wach 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
+    pass
 class Visibility(Logician):
-	pass
+    pass