You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

238 lines
9.4 KiB

# 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 *
import abc
from typing import Any, Dict, Generator, Optional
class PlasmaModifierProperties(bpy.types.PropertyGroup):
@property
def copy_material(self):
"""Materials MUST be single-user"""
return False
def created(self):
pass
def destroyed(self):
pass
@property
def draw_opaque(self):
"""Render geometry before the avatar"""
return False
@property
def draw_framebuf(self):
"""Render geometry after the avatar but before other blended geometry"""
return False
@property
def draw_no_defer(self):
"""Disallow geometry being sorted into a blending span"""
return False
@property
def draw_late(self):
return False
@property
def enabled(self):
return self.display_order >= 0
def export(self, exporter, bo, so):
"""This is the main phase of the modifier export where most, if not all, PRP objects should
be generated. No new Blender objects should be created unless their lifespan is constrained
to the duration of this method.
"""
pass
# Commented out to prevent conflicts with TranslationMixin overload.
"""
def export_localization(self, exporter):
'''This is an auxiliary export phase that should only convert localization data. PRP objects
are in an undefined state and therefore should not be used.
'''
pass
"""
@property
def face_sort(self):
"""Indicates that the geometry's faces should be sorted by the engine"""
return False
def harvest_actors(self):
return ()
@property
def key_name(self):
return self.id_data.name
@property
def no_face_sort(self):
"""Indicates that the geometry's faces should never be sorted by the engine"""
return False
@property
def no_span_sort(self):
"""Indicates that the geometry's Spans should never be sorted with those from other
Drawables that will render in the same pass"""
return False
# This is temporarily commented out to prevent MRO failure. Revisit in Python 3.7
'''
def pre_export(self, exporter, bo: bpy.types.Object) -> Generator:
"""This is the first phase of the modifier export; allowing modifiers to create additonal
objects or logic nodes to be used by the exporter. To do so, overload this method
and yield any Blender ID from your method. That ID will then be exported and deleted
when the export completes. PRP objects should generally not be exported in this phase.
"""
yield
'''
@property
def requires_actor(self):
"""Indicates if this modifier requires the object to be a movable actor"""
return False
# Guess what?
# You can't register properties on a base class--Blender isn't smart enough to do inheritance,
# you see... So, we'll store our definitions in a dict and make those properties on each subclass
# at runtime. What joy. Python FTW. See register() in __init__.py
_subprops = {
"display_order": (IntProperty, {"name": "INTERNAL: Display Ordering",
"description": "Position in the list of buttons",
"default": -1,
"options": {"HIDDEN"}}),
"show_expanded": (BoolProperty, {"name": "INTERNAL: Actually draw the modifier",
"default": True,
"options": {"HIDDEN"}}),
"current_version": (IntProperty, {"name": "INTERNAL: Modifier version",
"default": 1,
"options": {"HIDDEN"}}),
}
class PlasmaModifierLogicWiz:
def convert_logic(self, bo, **kwargs):
"""Creates, converts, and returns an unmanaged NodeTree for this logic wizard. If the wizard
fails during conversion, the temporary tree is deleted for you. However, on success, you
are responsible for removing the tree from Blender, if applicable."""
name = kwargs.pop("name", self.key_name)
assert not "tree" in kwargs
tree = bpy.data.node_groups.new(name, "PlasmaNodeTree")
kwargs["tree"] = tree
try:
self.logicwiz(bo, **kwargs)
except:
bpy.data.node_groups.remove(tree)
raise
else:
return tree
def _create_python_file_node(self, tree, filename: str, attributes: Dict[str, Any]) -> bpy.types.Node:
pfm_node = tree.nodes.new("PlasmaPythonFileNode")
with pfm_node.NoUpdate():
pfm_node.filename = filename
for attr in attributes:
new_attr = pfm_node.attributes.add()
new_attr.attribute_id = attr["id"]
new_attr.attribute_type = attr["type"]
new_attr.attribute_name = attr["name"]
pfm_node.update()
return pfm_node
def _create_python_attribute(self, pfm_node, attribute_name: str, attribute_type: Optional[str] = None, **kwargs):
"""Creates and links a Python Attribute Node to the Python File Node given by `pfm_node`.
This will automatically handle simple attribute types such as numbers and strings, however,
for object linkage, you should specify the optional `attribute_type` to ensure the proper
attribute type is found. For attribute nodes that require multiple values, the `value` may
be set to None and handled in your code."""
from ...nodes.node_python import PlasmaAttribute, PlasmaAttribNodeBase
if attribute_type is None:
assert len(kwargs) == 1 and "value" in kwargs, \
"In order to deduce the attribute_type, exactly one attribute value must be passed as a kw named `value`"
attribute_type = PlasmaAttribute.type_LUT.get(kwargs["value"].__class__)
node_cls = next((i for i in PlasmaAttribNodeBase.__subclasses__() if attribute_type in i.pl_attrib), None)
assert node_cls is not None, "'{}': Unable to find attribute node type for '{}' ('{}')".format(
self.id_data.name, attribute_name, attribute_type
)
node = pfm_node.id_data.nodes.new(node_cls.bl_idname)
node.link_output(pfm_node, "pfm", attribute_name)
for i, j in kwargs.items():
setattr(node, i, j)
return node
@abc.abstractmethod
def logicwiz(self, bo, tree):
pass
def pre_export(self, exporter, bo):
"""Default implementation of the pre_export phase for logic wizards that simply triggers
the logic nodes to be created and for their export to be scheduled."""
yield self.convert_logic(bo)
class PlasmaModifierUpgradable:
@property
@abc.abstractmethod
def latest_version(self):
raise NotImplementedError()
@property
def requires_upgrade(self):
current_version, latest_version = self.current_version, self.latest_version
assert current_version <= latest_version
return current_version < latest_version
@abc.abstractmethod
def upgrade(self):
raise NotImplementedError()
@bpy.app.handlers.persistent
def _restore_properties(dummy):
# When Blender opens, it loads the default blend. The post load handler
# below is executed and deprecated properties are unregistered. When the
# user goes to load a new blend file, the handler below tries to execute
# again and BOOM--there are no deprecated properties available. Therefore,
# we reregister them here.
for mod_cls in PlasmaModifierUpgradable.__subclasses__():
for prop_name in mod_cls.deprecated_properties:
# Unregistered propertes are a sequence of (property function,
# property keyword arguments). Interesting design decision :)
prop_cb, prop_kwargs = getattr(mod_cls, prop_name)
del prop_kwargs["attr"] # Prevents proper registration
setattr(mod_cls, prop_name, prop_cb(**prop_kwargs))
bpy.app.handlers.load_pre.append(_restore_properties)
@bpy.app.handlers.persistent
def _upgrade_modifiers(dummy):
# First, run all the upgrades
for i in bpy.data.objects:
for mod_cls in PlasmaModifierUpgradable.__subclasses__():
mod = getattr(i.plasma_modifiers, mod_cls.pl_id)
if mod.requires_upgrade:
mod.upgrade()
# Now that everything is upgraded, forcibly remove all properties
# from the modifiers to prevent sneaky zombie-data type export bugs
for mod_cls in PlasmaModifierUpgradable.__subclasses__():
for prop in mod_cls.deprecated_properties:
RemoveProperty(mod_cls, attr=prop)
bpy.app.handlers.load_post.append(_upgrade_modifiers)