mirror of https://github.com/H-uru/korman.git
31 changed files with 738 additions and 399 deletions
@ -0,0 +1,157 @@ |
|||||||
|
# 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 <http://www.gnu.org/licenses/>. |
||||||
|
|
||||||
|
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() } |
||||||
|
|
||||||
|
|
||||||
|
def poll_animated_objects(self, value): |
||||||
|
if value.animation_data is not None: |
||||||
|
if value.animation_data.action is not None: |
||||||
|
return True |
||||||
|
return False |
||||||
|
|
||||||
|
def poll_empty_objects(self, value): |
||||||
|
return value.type == "EMPTY" |
||||||
|
|
||||||
|
def poll_mesh_objects(self, value): |
||||||
|
return value.type == "MESH" |
||||||
|
|
||||||
|
def poll_softvolume_objects(self, value): |
||||||
|
return value.plasma_modifiers.softvolume.enabled |
||||||
|
|
||||||
|
def poll_visregion_objects(self, value): |
||||||
|
return value.plasma_modifiers.visregion.enabled |
||||||
|
|
||||||
|
def poll_envmap_textures(self, value): |
||||||
|
return isinstance(value, bpy.types.EnvironmentMapTexture) |
||||||
|
|
||||||
|
@bpy.app.handlers.persistent |
||||||
|
def _upgrade_node_trees(dummy): |
||||||
|
""" |
||||||
|
Logic node haxxor incoming! |
||||||
|
Logic nodes appear to have issues with silently updating themselves. I expect that Blender is |
||||||
|
doing something strange in the UI code that causes our metaprogramming tricks to be bypassed. |
||||||
|
Therefore, we will loop through all Plasma node trees and forcibly update them on blend load. |
||||||
|
""" |
||||||
|
|
||||||
|
for tree in bpy.data.node_groups: |
||||||
|
if tree.bl_idname != "PlasmaNodeTree": |
||||||
|
continue |
||||||
|
for node in tree.nodes: |
||||||
|
if isinstance(node, IDPropMixin): |
||||||
|
assert node._try_upgrade_idprops() |
||||||
|
bpy.app.handlers.load_post.append(_upgrade_node_trees) |
Loading…
Reference in new issue