forked from bartvdbraak/blender
Moved extensions_framework into addons/modules
This commit is contained in:
parent
1d1c8bb21b
commit
2b772739fb
@ -1,195 +0,0 @@
|
||||
# -*- 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 time
|
||||
|
||||
import bpy
|
||||
|
||||
from extensions_framework.ui import EF_OT_msg
|
||||
|
||||
bpy.types.register(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
|
||||
)
|
||||
|
||||
|
||||
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']}
|
||||
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:
|
||||
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.IDPropertyGroup):
|
||||
"""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.
|
||||
|
||||
"""
|
||||
|
||||
"""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 display of properties based on
|
||||
the value of other properties. See extensions_framework.validate
|
||||
for test syntax.
|
||||
|
||||
"""
|
||||
visibility = {}
|
||||
|
||||
"""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
|
@ -1,39 +0,0 @@
|
||||
# -*- 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 extensions_framework.plugin import plugin
|
||||
|
||||
class engine_base(plugin):
|
||||
"""Render Engine plugin base class
|
||||
|
||||
TODO: Remove, this class hasn't grown to be useful
|
||||
|
||||
"""
|
||||
|
||||
bl_label = 'Abstract Render Engine Base Class'
|
||||
|
||||
def render(self, scene):
|
||||
pass
|
@ -1,26 +0,0 @@
|
||||
# -*- 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 *****
|
||||
#
|
@ -1,116 +0,0 @@
|
||||
# -*- 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):
|
||||
"""This class serves to describe an XML output, it uses
|
||||
cElementTree and minidom to construct and format the XML
|
||||
data.
|
||||
|
||||
"""
|
||||
|
||||
"""The format dict describes the XML structure that this class
|
||||
should generate, and which properties should be used to fill
|
||||
the XML data structure
|
||||
|
||||
"""
|
||||
format = {}
|
||||
|
||||
def __str__(self):
|
||||
return ET.tostring(self.root)
|
||||
|
||||
def write_pretty(self, file):
|
||||
"""Write a formatted XML string to 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):
|
||||
"""Return a formatted XML string"""
|
||||
xml_str = MD.parseString(ET.tostring(self.root))
|
||||
return xml_str.toprettyxml()
|
||||
|
||||
def get_format(self):
|
||||
"""This should be overridden in classes that produce XML
|
||||
conditionally
|
||||
|
||||
"""
|
||||
return self.format
|
||||
|
||||
def compute(self, context):
|
||||
"""Compute the XML output from the input format"""
|
||||
self.context = context
|
||||
|
||||
self.root = ET.Element(self.root_element)
|
||||
self.parse_dict(self.get_format(), self.root)
|
||||
|
||||
return self.root
|
||||
|
||||
"""Formatting functions for various data types"""
|
||||
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):
|
||||
"""Parse the values in the format dict and collect the
|
||||
formatted data into XML structure starting at self.root
|
||||
|
||||
"""
|
||||
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]))
|
||||
)
|
@ -1,94 +0,0 @@
|
||||
# -*- 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 extensions_framework import init_properties
|
||||
from extensions_framework import log
|
||||
|
||||
class plugin(object):
|
||||
"""Base class for plugins which wish to make use of utilities
|
||||
provided in extensions_framework. Using the property_groups
|
||||
attribute and the install() and uninstall() methods, a large number
|
||||
of custom scene properties can be easily defined, displayed and
|
||||
managed.
|
||||
|
||||
TODO: Rename, 'extension' would be more appropriate than 'plugin'
|
||||
|
||||
"""
|
||||
|
||||
"""The property_groups defines a list of declarative_property_group
|
||||
types to create in specified types during the initialisation of the
|
||||
plugin.
|
||||
Item format:
|
||||
('bpy.type prototype to attach to', <declarative_property_group>)
|
||||
|
||||
Example item:
|
||||
('Scene', myaddon_property_group)
|
||||
In this example, a new property group will be attached to
|
||||
bpy.types.Scene and all of the properties described in that group
|
||||
will be added to it.
|
||||
See extensions_framework.declarative_property_group.
|
||||
|
||||
"""
|
||||
property_groups = []
|
||||
|
||||
@classmethod
|
||||
def install(r_class):
|
||||
"""Initialise this plugin. So far, all this does is to create
|
||||
custom property groups specified in the property_groups
|
||||
attribute.
|
||||
|
||||
"""
|
||||
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
|
||||
else:
|
||||
call_init = True
|
||||
|
||||
if call_init:
|
||||
init_properties(property_group, property_group.properties)
|
||||
|
||||
log('Extension "%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__)
|
@ -1,253 +0,0 @@
|
||||
# -*- 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 extensions_framework.validate import Visibility
|
||||
|
||||
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'}
|
||||
|
||||
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
|
||||
|
||||
class property_group_renderer(object):
|
||||
"""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 = 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
|
||||
|
||||
# tab_level = 0
|
||||
|
||||
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):
|
||||
# self.tab_level += 1
|
||||
"""Draw a column's worth of UI controls in this Panel"""
|
||||
if type(control_list_item) is list:
|
||||
draw_row = False
|
||||
|
||||
found_percent = None
|
||||
# print('\t'*self.tab_level, '--', property_group, '--')
|
||||
for sp in control_list_item:
|
||||
# print('\t'*self.tab_level, sp)
|
||||
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)
|
||||
# print('\t'*self.tab_level, 'List: ', draw_row)
|
||||
else:
|
||||
draw_row = draw_row or self.check_visibility(sp,
|
||||
property_group)
|
||||
# print('\t'*self.tab_level, 'Single: ', draw_row)
|
||||
# print('\t'*self.tab_level, '-->', draw_row)
|
||||
# print('\t'*self.tab_level, '--', 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()
|
||||
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']:
|
||||
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'],
|
||||
)
|
||||
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
|
||||
# self.tab_level -= 1
|
@ -1,223 +0,0 @@
|
||||
# -*- 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 configparser
|
||||
import datetime
|
||||
import os
|
||||
import tempfile
|
||||
import threading
|
||||
|
||||
import bpy
|
||||
|
||||
"""List of possibly appropriate paths to load/save addon config from/to"""
|
||||
config_paths = []
|
||||
if bpy.utils.user_resource('CONFIG') != "": config_paths.append(bpy.utils.user_resource('CONFIG'))
|
||||
if bpy.utils.user_resource('SCRIPTS') != "": config_paths.append(bpy.utils.user_resource('SCRIPTS'))
|
||||
for pth in bpy.utils.script_paths():
|
||||
if pth != "": config_paths.append(pth)
|
||||
|
||||
"""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
|
||||
|
||||
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('\\', '/')
|
||||
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
def temp_directory():
|
||||
"""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
|
||||
|
||||
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
|
||||
|
||||
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)
|
@ -1,213 +0,0 @@
|
||||
# -*- 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
|
||||
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
|
||||
class Visibility(Logician):
|
||||
pass
|
Loading…
Reference in New Issue
Block a user