From ad97d29f518dfc484e1e56ff858d9c1da1620450 Mon Sep 17 00:00:00 2001 From: filmfreedom-org Date: Sat, 29 May 2021 00:47:44 -0500 Subject: [PATCH] Added prop_factory module to create dynamic Blender property groups. --- abx/blender_context.py | 169 ------------------ abx/file_context.py | 2 +- abx/prop_factory.py | 98 ++++++++++ .../test_accumulate.cpython-35.pyc | Bin 10527 -> 10527 bytes .../test_render_profile.cpython-35.pyc | Bin 3392 -> 3216 bytes tests/test_render_profile.py | 5 - 6 files changed, 99 insertions(+), 175 deletions(-) delete mode 100644 abx/blender_context.py create mode 100644 abx/prop_factory.py diff --git a/abx/blender_context.py b/abx/blender_context.py deleted file mode 100644 index 1a7d217..0000000 --- a/abx/blender_context.py +++ /dev/null @@ -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) - - - - - \ No newline at end of file diff --git a/abx/file_context.py b/abx/file_context.py index 4dec9b9..aced7fb 100644 --- a/abx/file_context.py +++ b/abx/file_context.py @@ -1042,7 +1042,7 @@ class FileContext(NameContext): self.namepath_segment = [d['code'] for d in self.provided_data['project_unit']] self.code = self.namepath[-1] except: - print("Errors finding Name Path (is there a 'project_schema' or 'project_unit' defined?") + print("Can't find Name Path. Missing .yaml file?") pass # print("\n(899) filename = ", self.filename) # if 'project_schema' in self.provided_data: diff --git a/abx/prop_factory.py b/abx/prop_factory.py new file mode 100644 index 0000000..01a8eec --- /dev/null +++ b/abx/prop_factory.py @@ -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) + + + + + + \ No newline at end of file diff --git a/tests/__pycache__/test_accumulate.cpython-35.pyc b/tests/__pycache__/test_accumulate.cpython-35.pyc index 0ab3a36e3b9f37fd8db22fb4cb9da7ebc84d78b5..16ba3cc3b0b5da62b73e0601c2a5cc6a60dafef1 100644 GIT binary patch delta 1315 zcmbW1OHUI~7=}4*spS@1%GILK7HBJ?fFdTMSZoncq=DH8#&Ku|7@#xF>1e`=XrhT5 zHb-NOF(hu?XlAMY1rs%F&QkZrojc>aGjs~E3vu(zmvec)>zSRoow>JYdgg{1iBY0vw9l=~4n6FC0R&j3NWNE&v`%BU9qcJOFe#>I#?z@(IwfZ2 zW~b>{s9gX?fid6;a1}@cGbRFb7G?&R2X3+LhBgvqLVR#4YAz&>(un!tK2%JXi}ow$ v?&4;VR~M+F>U0tJtpAf&DimZj$9<|s^94y?wWq#^eTqYKyP=i+j)(sM?WF>K delta 1321 zcmbW1%TE(g6vnxw)beOrisd2CmRCnm0Ywr)vDhM@D8jfIjpNV`FhFOT+tGv-(L@tB z-HgT+^%?Jsa`!yd)G*VGfK^uV}1Lvhxu$zEppvCx5_q8?*^Ayl-76QAKt^Kg$43>9Y zQ&h{(r~NqSc%hZXUUTwTsXpj@ zJ?%;!+>cH|?q9CN{J2V|6j#mhu2&e^FVWa!DU(tkFe+AETFWjgsw{3v*-d*iEbpe0 z*NZAsh}>;gH5L0dhLny>Svg_5M04;gPMLjvx%dj%%rJ1(cH@O3jn7EBbg0Rt)a>rNg)6@GF!B&; zHxxZB=^_pbMy&tEb_arV0$o5E8S6wwp{kUXEx#zPD4Vh<+OOLVE;Qnou#n(q#MJy$ zDw#|r#f8P&b94l1W56ZgGH?Y*0M~#U4E1y#<}Kh3uw)bh%_L^<@!{DRTi}8;%08AK z71QOs^VY4({8rwo%27qt>0O+&|583Fmy^|ujQ5L}6e@y!&br$ZFJ5v` z?@FD00uSQu3-|^idQs4er`3np?H)XgKi$;V)%|sKeN}zYcT}qMdY&W4&o`cIfJaD; ztoEn1#m{hDX}3d!kbx8s2zc>52)x9%Kzh(DKu`cQTsGMkAt(aQhdBssH~@nYLc@oB z4nYUZhj|E{gm6+sr%AI?b@y(Z)hKQaw7y3JD*8bUe4tZyjd^PGW$dFX{Nj&Y@fWR?(I)F`sbf55jja`7A^Yi#DnJbz@6KbvaWW%ZObTCM_?*3rkNfxH4?Di++jquv@#ze6BLhQC!2F uZCX9E8AnOylG`agNxDRoJ=xp|cV#qXj^Z8-SzbZG1bR5k%<7}hN}2y<=x8_q delta 745 zcmZvaziSjx5XWcU{=EH>+r9HHoSp{5sU%<$QP2xfBBE);B>1D)MuoRRjy+CxH=t=! zoQ0)vw6w4is}sb+&elqTqIjLy3H}ehS&ca*ySxuG`<-8J*hh_b`&;9_@4WvpxV2pZ z_zokFt?Wg0vXi*g+T4rAI{q8iq zF+SAx((Clitm7=bG6%u`I`yn0c#57{&-P4h(w5!yPpdA)$bcJX>ABT@{n9>$Xwi1* zB9>^}$qsVe$i1|lBr9p}&RPv~ z?1`4BVOjGG^=h7$vN~0#&+d);ZDpnlj0U5|a2O`TQW)CT;w-;#JBy3m7s(@&{p~rl h7rH&Up2X+WUkh1N!PHvl2@ido`8|SQFTg-F^j~-%eKG(5 diff --git a/tests/test_render_profile.py b/tests/test_render_profile.py index ee9c794..da9bb28 100644 --- a/tests/test_render_profile.py +++ b/tests/test_render_profile.py @@ -16,11 +16,6 @@ import sys print("__file__ = ", __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 abx