-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #415 from Hoikas/bounds_storage_overhaul
Bounds and Animation Property Deduplication
- Loading branch information
Showing
11 changed files
with
408 additions
and
229 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
# 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/>. | ||
|
||
from __future__ import annotations | ||
|
||
from bpy.props import * | ||
|
||
from typing import * | ||
import warnings | ||
|
||
# Workaround for Blender memory management limitation, | ||
# don't change this to a literal in the code! | ||
_ENTIRE_ANIMATION = "(Entire Animation)" | ||
|
||
def _get_object_animation_names(self, object_attr: str) -> Sequence[Tuple[str, str, str]]: | ||
target_object = getattr(self, object_attr) | ||
if target_object is not None: | ||
items = [(anim.animation_name, anim.animation_name, "") | ||
for anim in target_object.plasma_modifiers.animation.subanimations] | ||
else: | ||
items = [(_ENTIRE_ANIMATION, _ENTIRE_ANIMATION, "")] | ||
|
||
# We always want "(Entire Animation)", if it exists, to be the first item. | ||
entire = items.index((_ENTIRE_ANIMATION, _ENTIRE_ANIMATION, "")) | ||
if entire not in (-1, 0): | ||
items.pop(entire) | ||
items.insert(0, (_ENTIRE_ANIMATION, _ENTIRE_ANIMATION, "")) | ||
|
||
return items | ||
|
||
def animation(object_attr: str, **kwargs) -> str: | ||
def get_items(self, context): | ||
return _get_object_animation_names(self, object_attr) | ||
|
||
return EnumProperty(items=get_items, **kwargs) | ||
|
||
# These are the kinds of physical bounds Plasma can work with. | ||
# This sequence is acceptable in any EnumProperty | ||
_bounds_types = ( | ||
("box", "Bounding Box", "Use a perfect bounding box"), | ||
("sphere", "Bounding Sphere", "Use a perfect bounding sphere"), | ||
("hull", "Convex Hull", "Use a convex set encompasing all vertices"), | ||
("trimesh", "Triangle Mesh", "Use the exact triangle mesh (SLOW!)") | ||
) | ||
|
||
def _bounds_type_index(key: str) -> int: | ||
return list(zip(*_bounds_types))[0].index(key) | ||
|
||
def _bounds_type_str(idx: int) -> str: | ||
return _bounds_types[idx][0] | ||
|
||
def _get_bounds(physics_attr: Optional[str]) -> Callable[[Any], int]: | ||
def getter(self) -> int: | ||
physics_object = getattr(self, physics_attr) if physics_attr is not None else self.id_data | ||
if physics_object is not None: | ||
return _bounds_type_index(physics_object.plasma_modifiers.collision.bounds) | ||
return _bounds_type_index("hull") | ||
return getter | ||
|
||
def _set_bounds(physics_attr: Optional[str]) -> Callable[[Any, int], None]: | ||
def setter(self, value: int): | ||
physics_object = getattr(self, physics_attr) if physics_attr is not None else self.id_data | ||
if physics_object is not None: | ||
physics_object.plasma_modifiers.collision.bounds = _bounds_type_str(value) | ||
return setter | ||
|
||
def bounds(physics_attr: Optional[str] = None, store_on_collider: bool = True, **kwargs) -> str: | ||
assert not {"items", "get", "set"} & kwargs.keys(), "You cannot use the `items`, `get`, or `set` keyword arguments" | ||
if store_on_collider: | ||
kwargs["get"] = _get_bounds(physics_attr) | ||
kwargs["set"] = _set_bounds(physics_attr) | ||
else: | ||
warnings.warn("Storing bounds properties outside of the collision modifier is deprecated.", category=DeprecationWarning) | ||
if "default" not in kwargs: | ||
kwargs["default"] = "hull" | ||
return EnumProperty( | ||
items=_bounds_types, | ||
**kwargs | ||
) | ||
|
||
def upgrade_bounds(bl, bounds_attr: str) -> None: | ||
# Only perform this process if the property has a value. Otherwise, we'll | ||
# wind up blowing away the collision modifier's settings with nonsense. | ||
if not bl.is_property_set(bounds_attr): | ||
return | ||
|
||
# Before we unregister anything, grab a copy of what the collision modifier currently thinks. | ||
bounds_value_curr = getattr(bl, bounds_attr) | ||
|
||
# So, here's the deal. If someone has been playing with nodes and changed the bounds type, | ||
# Blender will think the property has been set, even if they wound up with the property | ||
# at the default value. I don't know that we can really trust the default in the property | ||
# definition to be the same as the old default (they shouldn't be different, but let's be safe). | ||
# So, let's apply rough justice. If the destination property thinks it's a triangle mesh, we | ||
# don't need to blow that away - it's a very specific non default setting. | ||
if bounds_value_curr == "trimesh": | ||
return | ||
|
||
# Unregister the new/correct proxy bounds property (with getter/setter) and re-register | ||
# the property without the proxy functions to get the old value. Reregister the new property | ||
# again and set it. | ||
cls = bl.__class__ | ||
prop_func, prop_def = getattr(cls, bounds_attr) | ||
RemoveProperty(cls, attr=bounds_attr) | ||
del prop_def["attr"] | ||
|
||
# Remove the things we don't want in a copy to prevent hosing the new property. | ||
old_prop_def = dict(prop_def) | ||
del old_prop_def["get"] | ||
del old_prop_def["set"] | ||
setattr(cls, bounds_attr, prop_func(**old_prop_def)) | ||
bounds_value_new = getattr(bl, bounds_attr) | ||
|
||
# Re-register new property. | ||
RemoveProperty(cls, attr=bounds_attr) | ||
setattr(cls, bounds_attr, prop_func(**prop_def)) | ||
|
||
# Only set the property if the value different to avoid thrashing and log spam. | ||
if bounds_value_curr != bounds_value_new: | ||
print(f"Stashing bounds property: [{bl.name}] ({cls.__name__}) {bounds_value_curr} -> {bounds_value_new}") # TEMP | ||
setattr(bl, bounds_attr, bounds_value_new) | ||
|
||
def _get_texture_animation_names(self, object_attr: str, material_attr: str, texture_attr: str) -> Sequence[Tuple[str, str, str]]: | ||
target_object = getattr(self, object_attr) | ||
material = getattr(self, material_attr) | ||
texture = getattr(self, texture_attr) | ||
|
||
if texture is not None: | ||
items = [(anim.animation_name, anim.animation_name, "") | ||
for anim in texture.plasma_layer.subanimations] | ||
elif material is not None or target_object is not None: | ||
if material is None: | ||
materials = (i.material for i in target_object.material_slots if i and i.material) | ||
else: | ||
materials = (material,) | ||
layer_props = (i.texture.plasma_layer for mat in materials for i in mat.texture_slots if i and i.texture) | ||
all_anims = frozenset((anim.animation_name for i in layer_props for anim in i.subanimations)) | ||
items = [(i, i, "") for i in all_anims] | ||
else: | ||
items = [(_ENTIRE_ANIMATION, _ENTIRE_ANIMATION, "")] | ||
|
||
# We always want "(Entire Animation)", if it exists, to be the first item. | ||
entire = items.index((_ENTIRE_ANIMATION, _ENTIRE_ANIMATION, "")) | ||
if entire not in (-1, 0): | ||
items.pop(entire) | ||
items.insert(0, (_ENTIRE_ANIMATION, _ENTIRE_ANIMATION, "")) | ||
|
||
return items | ||
|
||
def triprop_animation(object_attr: str, material_attr: str, texture_attr: str, **kwargs) -> str: | ||
def get_items(self, context): | ||
return _get_texture_animation_names(self, object_attr, material_attr, texture_attr) | ||
|
||
assert not {"items", "get", "set"} & kwargs.keys(), "You cannot use the `items`, `get`, or `set` keyword arguments" | ||
return EnumProperty(items=get_items, **kwargs) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.