Browse Source

Unify animation naming and polling.

pull/415/head
Adam Johnson 3 months ago
parent
commit
adb8b23284
Signed by: Hoikas
GPG Key ID: 0B6515D6FF6F271E
  1. 60
      korman/enum_props.py
  2. 80
      korman/idprops.py
  3. 81
      korman/nodes/node_messages.py
  4. 101
      korman/nodes/node_python.py
  5. 34
      korman/properties/modifiers/game_gui.py

60
korman/enum_props.py

@ -20,6 +20,32 @@ from bpy.props import *
from typing import * from typing import *
import warnings 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. # These are the kinds of physical bounds Plasma can work with.
# This sequence is acceptable in any EnumProperty # This sequence is acceptable in any EnumProperty
_bounds_types = ( _bounds_types = (
@ -105,3 +131,37 @@ def upgrade_bounds(bl, bounds_attr: str) -> None:
if bounds_value_curr != bounds_value_new: if bounds_value_curr != bounds_value_new:
print(f"Stashing bounds property: [{bl.name}] ({cls.__name__}) {bounds_value_curr} -> {bounds_value_new}") # TEMP print(f"Stashing bounds property: [{bl.name}] ({cls.__name__}) {bounds_value_curr} -> {bounds_value_new}") # TEMP
setattr(bl, bounds_attr, bounds_value_new) 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)

80
korman/idprops.py

@ -145,6 +145,86 @@ def poll_visregion_objects(self, value):
def poll_envmap_textures(self, value): def poll_envmap_textures(self, value):
return isinstance(value, bpy.types.EnvironmentMapTexture) return isinstance(value, bpy.types.EnvironmentMapTexture)
def triprop_material(object_attr: str, material_attr: str, texture_attr: str, **kwargs) -> bpy.types.Material:
user_poll = kwargs.pop("poll", None)
def poll_proc(self, value: bpy.types.Material) -> bool:
target_object = getattr(self, object_attr)
if target_object is None:
target_object = getattr(self, "id_data", None)
# Don't filter materials by texture - this would (potentially) result in surprising UX
# in that you would have to clear the texture selection before being able to select
# certain materials.
if target_object is not None:
object_materials = (slot.material for slot in target_object.material_slots if slot and slot.material)
result = value in object_materials
else:
result = True
# Downstream processing, if any.
if result and user_poll is not None:
result = user_poll(self, value)
return result
assert not "type" in kwargs
return PointerProperty(
type=bpy.types.Material,
poll=poll_proc,
**kwargs
)
def triprop_object(object_attr: str, material_attr: str, texture_attr: str, **kwargs) -> bpy.types.Texture:
assert not "type" in kwargs
if not "poll" in kwargs:
kwargs["poll"] = poll_drawable_objects
return PointerProperty(
type=bpy.types.Object,
**kwargs
)
def triprop_texture(object_attr: str, material_attr: str, texture_attr: str, **kwargs) -> bpy.types.Object:
user_poll = kwargs.pop("poll", None)
def poll_proc(self, value: bpy.types.Texture) -> bool:
target_material = getattr(self, material_attr)
target_object = getattr(self, object_attr)
if target_object is None:
target_object = getattr(self, "id_data", None)
# must be a legal option... but is it a member of this material... or, if no material,
# any of the materials attached to the object?
if target_material is not None:
result = value.name in target_material.texture_slots
elif target_object is not None:
for i in (slot.material for slot in target_object.material_slots if slot and slot.material):
if value in (slot.texture for slot in i.texture_slots if slot and slot.texture):
result = True
break
else:
result = False
else:
result = False
# Is it animated?
if result and target_material is not None:
result = (
(target_material.animation_data is not None and target_material.animation_data.action is not None)
or (value.animation_data is not None and value.animation_data.action is not None)
)
# Downstream processing, if any.
if result and user_poll:
result = user_poll(self, value)
return result
assert not "type" in kwargs
return PointerProperty(
type=bpy.types.Texture,
poll=poll_proc,
**kwargs
)
@bpy.app.handlers.persistent @bpy.app.handlers.persistent
def _upgrade_node_trees(dummy): def _upgrade_node_trees(dummy):
""" """

81
korman/nodes/node_messages.py

@ -21,6 +21,7 @@ from PyHSPlasma import *
from typing import * from typing import *
from .. import enum_props
from .node_core import * from .node_core import *
from ..properties.modifiers.physics import subworld_types from ..properties.modifiers.physics import subworld_types
from ..properties.modifiers.region import footstep_surfaces, footstep_surface_ids from ..properties.modifiers.region import footstep_surfaces, footstep_surface_ids
@ -113,39 +114,30 @@ class PlasmaAnimCmdMsgNode(idprops.IDPropMixin, PlasmaMessageWithCallbacksNode,
("TEXTURE", "Texture", "Texture Action")], ("TEXTURE", "Texture", "Texture Action")],
default="OBJECT") default="OBJECT")
def _poll_texture(self, value): def _poll_target_object(self, value: bpy.types.Object) -> bool:
# must be a legal option... but is it a member of this material... or, if no material, if self.anim_type == "TEXTURE":
# any of the materials attached to the object? return idprops.poll_drawable_objects(self, value)
if self.target_material is not None: elif self.anim_type == "MESH":
return value.name in self.target_material.texture_slots return idprops.poll_animated_objects(self, value)
elif self.target_object is not None:
for i in (slot.material for slot in self.target_object.material_slots if slot and slot.material):
if value in (slot.texture for slot in i.texture_slots if slot and slot.texture):
return True
return False
else: else:
return True raise RuntimeError()
def _poll_material(self, value):
# Don't filter materials by texture - this would (potentially) result in surprising UX
# in that you would have to clear the texture selection before being able to select
# certain materials.
if self.target_object is not None:
object_materials = (slot.material for slot in self.target_object.material_slots if slot and slot.material)
return value in object_materials
return True
target_object = PointerProperty(name="Object", target_object = idprops.triprop_object(
description="Target object", "target_object", "target_material", "target_texture",
type=bpy.types.Object) name="Object",
target_material = PointerProperty(name="Material", description="Target object",
description="Target material", poll=_poll_target_object
type=bpy.types.Material, )
poll=_poll_material) target_material = idprops.triprop_material(
target_texture = PointerProperty(name="Texture", "target_object", "target_material", "target_texture",
description="Target texture", name="Material",
type=bpy.types.Texture, description="Target material"
poll=_poll_texture) )
target_texture = idprops.triprop_texture(
"target_object", "target_material", "target_texture",
name="Texture",
description="Target texture"
)
go_to = EnumProperty(name="Go To", go_to = EnumProperty(name="Go To",
description="Where should the animation start?", description="Where should the animation start?",
@ -205,37 +197,14 @@ class PlasmaAnimCmdMsgNode(idprops.IDPropMixin, PlasmaMessageWithCallbacksNode,
("kStop", "Stop", "When the action is stopped by a message")], ("kStop", "Stop", "When the action is stopped by a message")],
default="kEnd") default="kEnd")
# Blender memory workaround
_ENTIRE_ANIMATION = "(Entire Animation)"
def _get_anim_names(self, context): def _get_anim_names(self, context):
if self.anim_type == "OBJECT": if self.anim_type == "OBJECT":
items = [(anim.animation_name, anim.animation_name, "") return enum_props._get_object_animation_names(self, "target_object")
for anim in self.target_object.plasma_modifiers.animation.subanimations]
elif self.anim_type == "TEXTURE": elif self.anim_type == "TEXTURE":
if self.target_texture is not None: return enum_props._get_texture_animation_names(self, "target_object", "target_material", "target_texture")
items = [(anim.animation_name, anim.animation_name, "")
for anim in self.target_texture.plasma_layer.subanimations]
elif self.target_material is not None or self.target_object is not None:
if self.target_material is None:
materials = (i.material for i in self.target_object.material_slots if i and i.material)
else:
materials = (self.target_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 = [(PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, "")]
else: else:
raise RuntimeError() raise RuntimeError()
# We always want "(Entire Animation)", if it exists, to be the first item.
entire = items.index((PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, ""))
if entire not in (-1, 0):
items.pop(entire)
items.insert(0, (PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, PlasmaAnimCmdMsgNode._ENTIRE_ANIMATION, ""))
return items
anim_name = EnumProperty(name="Animation", anim_name = EnumProperty(name="Animation",
description="Name of the animation to control", description="Name of the animation to control",
items=_get_anim_names, items=_get_anim_names,

101
korman/nodes/node_python.py

@ -21,6 +21,7 @@ from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from PyHSPlasma import * from PyHSPlasma import *
from .. import enum_props
from .node_core import * from .node_core import *
from .node_deprecated import PlasmaDeprecatedNode, PlasmaVersionedNode from .node_deprecated import PlasmaDeprecatedNode, PlasmaVersionedNode
from .. import idprops from .. import idprops
@ -823,81 +824,43 @@ class PlasmaAttribTextureNode(idprops.IDPropMixin, PlasmaAttribNodeBase, bpy.typ
pl_attrib = ("ptAttribMaterial", "ptAttribMaterialList", pl_attrib = ("ptAttribMaterial", "ptAttribMaterialList",
"ptAttribDynamicMap", "ptAttribMaterialAnimation") "ptAttribDynamicMap", "ptAttribMaterialAnimation")
def _poll_material(self, value: bpy.types.Material) -> bool:
# Don't filter materials by texture - this would (potentially) result in surprising UX
# in that you would have to clear the texture selection before being able to select
# certain materials.
if self.target_object is not None:
object_materials = (slot.material for slot in self.target_object.material_slots if slot and slot.material)
return value in object_materials
return True
def _poll_texture(self, value: bpy.types.Texture) -> bool: def _poll_texture(self, value: bpy.types.Texture) -> bool:
# is this the type of dealio that we're looking for? # is this the type of dealio that we're looking for?
attrib = self.to_socket attrib = self.to_socket
if attrib is not None: if attrib is not None:
attrib = attrib.attribute_type attrib = attrib.attribute_type
if attrib == "ptAttribDynamicMap": if attrib == "ptAttribDynamicMap" and self._is_dyntext(value):
if not self._is_dyntext(value): return True
return False elif attrib == "ptAttribMaterialAnimation" and not self._is_dyntext:
elif attrib == "ptAttribMaterialAnimation": return True
if not self._is_animated(self.material, value):
return False
# must be a legal option... but is it a member of this material... or, if no material,
# any of the materials attached to the object?
if self.material is not None:
return value.name in self.material.texture_slots
elif self.target_object is not None:
for i in (slot.material for slot in self.target_object.material_slots if slot and slot.material):
if value in (slot.texture for slot in i.texture_slots if slot and slot.texture):
return True
return False return False
else:
return True
target_object = PointerProperty(name="Object", # We're not hooked up to a PFM node yet, so let anything slide.
description="", return True
type=bpy.types.Object,
poll=idprops.poll_drawable_objects)
material = PointerProperty(name="Material",
description="Material the texture is attached to",
type=bpy.types.Material,
poll=_poll_material)
texture = PointerProperty(name="Texture",
description="Texture to expose to Python",
type=bpy.types.Texture,
poll=_poll_texture)
# Blender memory workaround
_ENTIRE_ANIMATION = "(Entire Animation)"
def _get_anim_names(self, context):
if self.texture is not None:
items = [(anim.animation_name, anim.animation_name, "")
for anim in self.texture.plasma_layer.subanimations]
elif self.material is not None or self.target_object is not None:
if self.material is None:
materials = (i.material for i in self.target_object.material_slots if i and i.material)
else:
materials = (self.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 = [(PlasmaAttribTextureNode._ENTIRE_ANIMATION, PlasmaAttribTextureNode._ENTIRE_ANIMATION, "")]
# We always want "(Entire Animation)", if it exists, to be the first item.
entire = items.index((PlasmaAttribTextureNode._ENTIRE_ANIMATION, PlasmaAttribTextureNode._ENTIRE_ANIMATION, ""))
if entire not in (-1, 0):
items.pop(entire)
items.insert(0, (PlasmaAttribTextureNode._ENTIRE_ANIMATION, PlasmaAttribTextureNode._ENTIRE_ANIMATION, ""))
return items
anim_name = EnumProperty(name="Animation", target_object = idprops.triprop_object(
description="Name of the animation to control", "target_object", "material", "texture",
items=_get_anim_names, name="Object",
options=set()) description="Target object"
)
material = idprops.triprop_material(
"target_object", "material", "texture",
name="Material",
description="Material the texture is attached to"
)
texture = idprops.triprop_texture(
"target_object", "material", "texture",
name="Texture",
description="Texture to expose to Python",
poll=_poll_texture
)
anim_name = enum_props.triprop_animation(
"target_object", "material", "texture",
name="Animation",
description="Name of the animation to control",
options=set()
)
def init(self, context): def init(self, context):
super().init(context) super().init(context)
@ -967,10 +930,6 @@ class PlasmaAttribTextureNode(idprops.IDPropMixin, PlasmaAttribNodeBase, bpy.typ
return {"material_name": bpy.data.materials, return {"material_name": bpy.data.materials,
"texture_name": bpy.data.textures} "texture_name": bpy.data.textures}
def _is_animated(self, material, texture):
return ((material.animation_data is not None and material.animation_data.action is not None)
or (texture.animation_data is not None and texture.animation_data.action is not None))
def _is_dyntext(self, texture): def _is_dyntext(self, texture):
return texture.type == "IMAGE" and texture.image is None return texture.type == "IMAGE" and texture.image is None

34
korman/properties/modifiers/game_gui.py

@ -203,26 +203,6 @@ class GameGuiAnimation(bpy.types.PropertyGroup):
else: else:
return idprops.poll_drawable_objects(self, value) return idprops.poll_drawable_objects(self, value)
def _poll_texture(self, value):
# must be a legal option... but is it a member of this material... or, if no material,
# any of the materials attached to the object?
if self.target_material is not None:
return value.name in self.target_material.texture_slots
else:
target_object = self.target_object if self.target_object is not None else self.id_data
for i in (slot.material for slot in target_object.material_slots if slot and slot.material):
if value in (slot.texture for slot in i.texture_slots if slot and slot.texture):
return True
return False
def _poll_material(self, value):
# Don't filter materials by texture - this would (potentially) result in surprising UX
# in that you would have to clear the texture selection before being able to select
# certain materials.
target_object = self.target_object if self.target_object is not None else self.id_data
object_materials = (slot.material for slot in target_object.material_slots if slot and slot.material)
return value in object_materials
anim_type: str = EnumProperty( anim_type: str = EnumProperty(
name="Type", name="Type",
description="Animation type to affect", description="Animation type to affect",
@ -233,23 +213,21 @@ class GameGuiAnimation(bpy.types.PropertyGroup):
default="OBJECT", default="OBJECT",
options=set() options=set()
) )
target_object: bpy.types.Object = PointerProperty( target_object: bpy.types.Object = idprops.triprop_object(
"target_object", "target_material", "target_texure",
name="Object", name="Object",
description="Target object", description="Target object",
poll=_poll_target_object, poll=_poll_target_object,
type=bpy.types.Object
) )
target_material: bpy.types.Material = PointerProperty( target_material: bpy.types.Material = idprops.triprop_material(
"target_object", "target_material", "target_texure",
name="Material", name="Material",
description="Target material", description="Target material",
type=bpy.types.Material,
poll=_poll_material
) )
target_texture: bpy.types.Texture = PointerProperty( target_texture: bpy.types.Texture = idprops.triprop_texture(
"target_object", "target_material", "target_texure",
name="Texture", name="Texture",
description="Target texture", description="Target texture",
type=bpy.types.Texture,
poll=_poll_texture
) )

Loading…
Cancel
Save