diff --git a/korman/__init__.py b/korman/__init__.py index 5d516e1..093df40 100644 --- a/korman/__init__.py +++ b/korman/__init__.py @@ -22,7 +22,7 @@ from . import operators bl_info = { "name": "Korman", "author": "Guild of Writers", - "blender": (2, 78, 0), + "blender": (2, 79, 0), "location": "File > Import-Export", "description": "Exporter for Cyan Worlds' Plasma Engine", "warning": "beta", diff --git a/korman/idprops.py b/korman/idprops.py new file mode 100644 index 0000000..3342044 --- /dev/null +++ b/korman/idprops.py @@ -0,0 +1,118 @@ +# This file is part of Korman. +# +# Korman 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 3 of the License, or +# (at your option) any later version. +# +# Korman 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 Korman. If not, see . + +import bpy +from bpy.props import * + +class IDPropMixin: + """ + So, here's the rub. + + In Blender 2.79, we finally get the ability to use native Blender ID Datablock properties in Python. + This is great! It will allow us to specify other objects (Blender Objects, Materials, Textures) in + our plugin as pointer properties. Further, we can even specify a poll method to create a 'search list' + of valid options. + + Naturally, there are some cons. The con here is that we've been storing object NAMES in string properties + for several releases now. Therefore, the purpose of this class is simple... It is a mixin to be + used for silently upgrading these object name properties to ID Properties. You will need to override + the _idprop_mapping and _idprop_sources methods in your class. The mixin will handle upgrading + the properties when a derived class is touched. + + Unfortunately, it is not possible to easily batch convert everything on load or save, due to issues + in the way Blender's Python API functions. Long story short: PropertyGroups do not execute __new__ + or __init__. Furthermore, Blender's UI does not appreciate having ID Datablocks return from + __getattribute__. To make matters worse, all properties are locked in a read-only state during + the UI draw stage. + """ + + def __getattribute__(self, attr): + _getattribute = super().__getattribute__ + + # Let's make sure no one is trying to access an old version... + if attr in _getattribute("_idprop_mapping")().values(): + raise AttributeError("'{}' has been deprecated... Please use the ID Property".format(attr)) + + # I have some bad news for you... Unfortunately, this might have been called + # during Blender's draw() context. Blender locks all properties during the draw loop. + # HOWEVER!!! There is a solution. Upon inspection of the Blender source code, however, it + # appears this restriction is temporarily suppressed during property getters... So let's get + # a property that executes a getter :D + # ... + # ... + # But why not simply proxy requests here, you ask? Ah, young grasshopper... This is the + # fifth time I have (re-)written this code. Trust me when I say, 'tis a boondoggle. + assert _getattribute("idprops_upgraded") + + # Must be something regular. Just super it. + return super().__getattribute__(attr) + + def __setattr__(self, attr, value): + idprops = super().__getattribute__("_idprop_mapping")() + + # Disallow any attempts to set the old string property + if attr in idprops.values(): + raise AttributeError("'{}' has been deprecated... Please use the ID Property".format(attr)) + + # Inappropriate touching? + super().__getattribute__("_try_upgrade_idprops")() + + # Now, pass along our update + super().__setattr__(attr, value) + + @classmethod + def register(cls): + if hasattr(super(), "register"): + super().register() + + cls.idprops_upgraded = BoolProperty(name="INTERNAL: ID Property Upgrader HACK", + description="HAAAX *throws CRT monitor*", + get=cls._try_upgrade_idprops, + options={"HIDDEN"}) + cls.idprops_upgraded_value = BoolProperty(name="INTERNAL: ID Property Upgrade Status", + description="Have old StringProperties been upgraded to ID Datablock Properties?", + default=False, + options={"HIDDEN"}) + for str_prop in cls._idprop_mapping().values(): + setattr(cls, str_prop, StringProperty(description="deprecated")) + + def _try_upgrade_idprops(self): + _getattribute = super().__getattribute__ + + if not _getattribute("idprops_upgraded_value"): + idprop_map = _getattribute("_idprop_mapping")() + strprop_src = _getattribute("_idprop_sources")() + + for idprop_name, strprop_name in idprop_map.items(): + if not super().is_property_set(strprop_name): + continue + strprop_value = _getattribute(strprop_name) + idprop_value = strprop_src[strprop_name].get(strprop_value, None) + super().__setattr__(idprop_name, idprop_value) + super().property_unset(strprop_name) + super().__setattr__("idprops_upgraded_value", True) + + # you should feel like this now... https://youtu.be/1JBSs6MQJeI?t=33s + return True + + +class IDPropObjectMixin(IDPropMixin): + """Like IDPropMixin, but with the assumption that all IDs can be found in bpy.data.objects""" + + def _idprop_sources(self): + # NOTE: bad problems result when using super() here, so we'll manually reference object + cls = object.__getattribute__(self, "__class__") + idprops = cls._idprop_mapping() + return { i: bpy.data.objects for i in idprops.values() }