Added prop_factory module to create dynamic Blender property groups.
This commit is contained in:
parent
5028f84e28
commit
ad97d29f51
@ -1,169 +0,0 @@
|
|||||||
# blender_context.py
|
|
||||||
"""
|
|
||||||
Contextual metadata acquired from internal values in a Blender file.
|
|
||||||
|
|
||||||
This module must be invoked from within Blender to work, as it relies on the bpy Blender API
|
|
||||||
module and the currently-open Blender file's data graph in order to work.
|
|
||||||
|
|
||||||
It collects data about scenes, objects, groups, and other datablocks in the Blender file,
|
|
||||||
as well as data encoded in text blocks in different formats. Overall file data is incorporated
|
|
||||||
into a PropertyGroup attached to the "WindowManager" object identified as 'WinMan' (normally,
|
|
||||||
it appears there is only ever one of these in a Blender file, but if there is more than one, this
|
|
||||||
is the one that will be used).
|
|
||||||
"""
|
|
||||||
|
|
||||||
import io
|
|
||||||
import bpy, bpy.app, bpy.props, bpy.utils
|
|
||||||
from bpy.app.handlers import persistent
|
|
||||||
from accumulate import UnionList, RecursiveDict
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
def EnumFromList(schema, listname):
|
|
||||||
return [(e, e.capitalize(), e.capitalize()) for e in schema[listname]]
|
|
||||||
|
|
||||||
prop_types = {
|
|
||||||
'string':{
|
|
||||||
'property': bpy.props.StringProperty,
|
|
||||||
'keywords': { 'name', 'description', 'default', 'maxlen', 'options', 'subtype'},
|
|
||||||
'translate': {
|
|
||||||
'desc': ('description', None)}},
|
|
||||||
'enum': {
|
|
||||||
'property': bpy.props.EnumProperty,
|
|
||||||
'keywords': { 'items', 'name', 'description', 'default', 'options'},
|
|
||||||
'translate': {
|
|
||||||
'desc': ('description', None),
|
|
||||||
'items_from': ('items', EnumFromList)}},
|
|
||||||
'int': {
|
|
||||||
'property': bpy.props.IntProperty,
|
|
||||||
'keywords': { 'name', 'description', 'default', 'min', 'max', 'soft_min', 'soft_max',
|
|
||||||
'step', 'options', 'subtype'},
|
|
||||||
'translate': {
|
|
||||||
'desc': ('description', None)}},
|
|
||||||
'float': {
|
|
||||||
'property': bpy.props.FloatProperty,
|
|
||||||
'keywords': { 'name', 'description', 'default', 'min', 'max', 'soft_min', 'soft_max',
|
|
||||||
'step', 'options', 'subtype', 'precision', 'unit'},
|
|
||||||
'translate': {
|
|
||||||
'desc': ('description', None)}},
|
|
||||||
'bool': {
|
|
||||||
'property': bpy.props.BoolProperty,
|
|
||||||
'keywords': { 'name', 'description', 'default', 'options', 'subtype'},
|
|
||||||
'translate': {
|
|
||||||
'desc': ('description', None)}}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AbxMeta(bpy.types.PropertyGroup):
|
|
||||||
"""
|
|
||||||
Metadata property group factory for attachment to Blender object types.
|
|
||||||
Definitions come from a YAML source (or default defined below).
|
|
||||||
"""
|
|
||||||
default_schema = yaml.safe_load(io.StringIO("""\
|
|
||||||
---
|
|
||||||
blender:
|
|
||||||
- id: project
|
|
||||||
type: string
|
|
||||||
level: project
|
|
||||||
name: Project Name
|
|
||||||
desc: Name of the project
|
|
||||||
maxlen: 32
|
|
||||||
|
|
||||||
- id: project_title
|
|
||||||
type: string
|
|
||||||
level: project
|
|
||||||
name: Project Title
|
|
||||||
desc: Full title for the project
|
|
||||||
maxlen: 64
|
|
||||||
|
|
||||||
- id: project_description
|
|
||||||
type: string
|
|
||||||
level: project
|
|
||||||
name: Project Description
|
|
||||||
desc: Brief description of the project
|
|
||||||
maxlen: 128
|
|
||||||
|
|
||||||
- id: project_url
|
|
||||||
type: list string
|
|
||||||
level: project
|
|
||||||
name: Project URL
|
|
||||||
desc: URL for Project home page, or comma-separated list of Project URLs
|
|
||||||
|
|
||||||
- id: level
|
|
||||||
type: enum
|
|
||||||
items_from: levels
|
|
||||||
name: Level
|
|
||||||
desc: Level of the file in the project hierarchy
|
|
||||||
|
|
||||||
levels:
|
|
||||||
- project
|
|
||||||
- series
|
|
||||||
- episode
|
|
||||||
- seq
|
|
||||||
- subseq
|
|
||||||
- camera
|
|
||||||
- shot
|
|
||||||
- element
|
|
||||||
- frame
|
|
||||||
|
|
||||||
hierarchies:
|
|
||||||
- library
|
|
||||||
- episodes
|
|
||||||
"""))
|
|
||||||
|
|
||||||
def __new__(cls, schema=default_schema):
|
|
||||||
class CustomPropertyGroup(bpy.types.PropertyGroup):
|
|
||||||
pass
|
|
||||||
for definition in schema['blender']:
|
|
||||||
# Translate and filter parameters
|
|
||||||
try:
|
|
||||||
propmap = prop_types[definition['type']]
|
|
||||||
except KeyError:
|
|
||||||
# If no 'type' specified or 'type' not found, default to string:
|
|
||||||
propmap = prop_types['string']
|
|
||||||
|
|
||||||
filtered = {}
|
|
||||||
for param in definition:
|
|
||||||
if 'translate' in propmap and param in propmap['translate']:
|
|
||||||
filter = propmap['translate'][param][1]
|
|
||||||
if callable(filter):
|
|
||||||
# Filtered translation
|
|
||||||
filtered[propmap['translate'][param][0]] = filter(schema, definition[param])
|
|
||||||
else:
|
|
||||||
# Simple translation
|
|
||||||
filtered[propmap['translate'][param][0]] = definition[param]
|
|
||||||
|
|
||||||
# Create the Blender Property object
|
|
||||||
kwargs = dict((key,filtered[key]) for key in propmap['keywords'] if key in filtered)
|
|
||||||
setattr(CustomPropertyGroup, definition['id'], propmap['property'](**kwargs))
|
|
||||||
|
|
||||||
bpy.utils.register_class(CustomPropertyGroup)
|
|
||||||
return(CustomPropertyGroup)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BlenderContext(RecursiveDict):
|
|
||||||
"""
|
|
||||||
Dictionary accumulating data from sources within the currently-open Blender file.
|
|
||||||
"""
|
|
||||||
filepath = ''
|
|
||||||
defaults = {}
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.clear()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def update(cls):
|
|
||||||
try:
|
|
||||||
cls.file_metadata = bpy.data.window_managers['WinMan'].metadata
|
|
||||||
except AttributeError:
|
|
||||||
bpy.data.window_managers['WinMan'].new(FileMeta())
|
|
||||||
|
|
||||||
|
|
||||||
def clear(self):
|
|
||||||
for key in self:
|
|
||||||
del self[key]
|
|
||||||
self.update(self.defaults)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1042,7 +1042,7 @@ class FileContext(NameContext):
|
|||||||
self.namepath_segment = [d['code'] for d in self.provided_data['project_unit']]
|
self.namepath_segment = [d['code'] for d in self.provided_data['project_unit']]
|
||||||
self.code = self.namepath[-1]
|
self.code = self.namepath[-1]
|
||||||
except:
|
except:
|
||||||
print("Errors finding Name Path (is there a 'project_schema' or 'project_unit' defined?")
|
print("Can't find Name Path. Missing <project>.yaml file?")
|
||||||
pass
|
pass
|
||||||
# print("\n(899) filename = ", self.filename)
|
# print("\n(899) filename = ", self.filename)
|
||||||
# if 'project_schema' in self.provided_data:
|
# if 'project_schema' in self.provided_data:
|
||||||
|
98
abx/prop_factory.py
Normal file
98
abx/prop_factory.py
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# blender_context.py
|
||||||
|
"""
|
||||||
|
Contextual metadata acquired from internal values in a Blender file.
|
||||||
|
|
||||||
|
This module must be invoked from within Blender to work, as it relies on the bpy Blender API
|
||||||
|
module and the currently-open Blender file's data graph in order to work.
|
||||||
|
|
||||||
|
It collects data about scenes, objects, groups, and other datablocks in the Blender file,
|
||||||
|
as well as data encoded in text blocks in different formats. Overall file data is incorporated
|
||||||
|
into a PropertyGroup attached to the "WindowManager" object identified as 'WinMan' (normally,
|
||||||
|
it appears there is only ever one of these in a Blender file, but if there is more than one, this
|
||||||
|
is the one that will be used).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import io
|
||||||
|
import bpy, bpy.app, bpy.props, bpy.utils
|
||||||
|
from bpy.app.handlers import persistent
|
||||||
|
from .accumulate import UnionList, RecursiveDict
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
def EnumFromList(schema, listname):
|
||||||
|
return [(e, e.capitalize(), e.capitalize()) for e in schema[listname]]
|
||||||
|
|
||||||
|
class PropertyGroupFactory(bpy.types.PropertyGroup):
|
||||||
|
"""
|
||||||
|
Metadata property group factory for attachment to Blender object types.
|
||||||
|
Definitions come from a YAML source (or default defined below).
|
||||||
|
"""
|
||||||
|
# These values mirror the Blender documentation for the bpy.props types:
|
||||||
|
prop_types = {
|
||||||
|
'str':{
|
||||||
|
'property': bpy.props.StringProperty,
|
||||||
|
'keywords': { 'name', 'description', 'default', 'maxlen',
|
||||||
|
'options', 'subtype'},
|
||||||
|
'translate': {
|
||||||
|
'desc': ('description', None)}},
|
||||||
|
'enum': {
|
||||||
|
'property': bpy.props.EnumProperty,
|
||||||
|
'keywords': { 'items', 'name', 'description', 'default', 'options'},
|
||||||
|
'translate': {
|
||||||
|
'desc': ('description', None),
|
||||||
|
'items_from': ('items', EnumFromList)}},
|
||||||
|
'int': {
|
||||||
|
'property': bpy.props.IntProperty,
|
||||||
|
'keywords': { 'name', 'description', 'default', 'min', 'max',
|
||||||
|
'soft_min', 'soft_max', 'step', 'options', 'subtype'},
|
||||||
|
'translate': {
|
||||||
|
'desc': ('description', None)}},
|
||||||
|
'float': {
|
||||||
|
'property': bpy.props.FloatProperty,
|
||||||
|
'keywords': { 'name', 'description', 'default', 'min', 'max',
|
||||||
|
'soft_min', 'soft_max', 'step', 'options',
|
||||||
|
'subtype', 'precision', 'unit'},
|
||||||
|
'translate': {
|
||||||
|
'desc': ('description', None)}},
|
||||||
|
'bool': {
|
||||||
|
'property': bpy.props.BoolProperty,
|
||||||
|
'keywords': { 'name', 'description', 'default', 'options', 'subtype'},
|
||||||
|
'translate': {
|
||||||
|
'desc': ('description', None)}}
|
||||||
|
}
|
||||||
|
|
||||||
|
def __new__(cls, name, schema):
|
||||||
|
class CustomPropertyGroup(bpy.types.PropertyGroup):
|
||||||
|
pass
|
||||||
|
for definition in schema[name]:
|
||||||
|
# Translate and filter parameters
|
||||||
|
try:
|
||||||
|
propmap = cls.prop_types[definition['type']]
|
||||||
|
except KeyError:
|
||||||
|
# If no 'type' specified or 'type' not found, default to string:
|
||||||
|
propmap = cls.prop_types['str']
|
||||||
|
|
||||||
|
filtered = {}
|
||||||
|
for param in definition:
|
||||||
|
if 'translate' in propmap and param in propmap['translate']:
|
||||||
|
translator = propmap['translate'][param][1]
|
||||||
|
if callable(translator):
|
||||||
|
# Filtered translation
|
||||||
|
filtered[propmap['translate'][param][0]] = translator(schema, definition[param])
|
||||||
|
else:
|
||||||
|
# Simple translation
|
||||||
|
filtered[propmap['translate'][param][0]] = definition[param]
|
||||||
|
else:
|
||||||
|
filtered[param] = definition[param]
|
||||||
|
|
||||||
|
# Create the Blender Property object
|
||||||
|
kwargs = dict((key,filtered[key]) for key in propmap['keywords'] if key in filtered)
|
||||||
|
setattr(CustomPropertyGroup, definition['code'], propmap['property'](**kwargs))
|
||||||
|
|
||||||
|
bpy.utils.register_class(CustomPropertyGroup)
|
||||||
|
return(CustomPropertyGroup)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Binary file not shown.
Binary file not shown.
@ -16,11 +16,6 @@ import sys
|
|||||||
print("__file__ = ", __file__)
|
print("__file__ = ", __file__)
|
||||||
sys.path.append(os.path.normpath(os.path.join(__file__, '..', '..')))
|
sys.path.append(os.path.normpath(os.path.join(__file__, '..', '..')))
|
||||||
|
|
||||||
TESTDATA = os.path.join(os.path.abspath(__file__), '..', 'testdata')
|
|
||||||
|
|
||||||
TESTPATH = os.path.join(TESTDATA, 'myproject', 'Episodes', 'A.001-Pilot',
|
|
||||||
'Seq', 'LP-LastPoint', 'A.001-LP-1-BeginningOfEnd-anim.txt')
|
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
|
|
||||||
import abx
|
import abx
|
||||||
|
Loading…
Reference in New Issue
Block a user