Browse Source

Merge pull request #415 from Hoikas/bounds_storage_overhaul

Bounds and Animation Property Deduplication
pull/333/merge
Adam Johnson 3 months ago committed by GitHub
parent
commit
9dc7f6819d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 167
      korman/enum_props.py
  2. 80
      korman/idprops.py
  3. 74
      korman/nodes/node_conditions.py
  4. 26
      korman/nodes/node_logic.py
  5. 81
      korman/nodes/node_messages.py
  6. 101
      korman/nodes/node_python.py
  7. 4
      korman/properties/modifiers/base.py
  8. 34
      korman/properties/modifiers/game_gui.py
  9. 17
      korman/properties/modifiers/logic.py
  10. 18
      korman/properties/modifiers/physics.py
  11. 35
      korman/properties/modifiers/region.py

167
korman/enum_props.py

@ -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)

80
korman/idprops.py

@ -145,6 +145,86 @@ def poll_visregion_objects(self, value):
def poll_envmap_textures(self, value):
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
def _upgrade_node_trees(dummy):
"""

74
korman/nodes/node_conditions.py

@ -21,11 +21,12 @@ import math
from PyHSPlasma import *
from typing import *
from .. import enum_props
from .node_core import *
from ..properties.modifiers.physics import bounds_types
from .node_deprecated import PlasmaVersionedNode
from .. import idprops
class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node):
class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaVersionedNode, bpy.types.Node):
bl_category = "CONDITIONS"
bl_idname = "PlasmaClickableNode"
bl_label = "Clickable"
@ -38,10 +39,13 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
description="Mesh object that is clickable",
type=bpy.types.Object,
poll=idprops.poll_mesh_objects)
bounds = EnumProperty(name="Bounds",
description="Clickable's bounds (NOTE: only used if your clickable is not a collider)",
items=bounds_types,
default="hull")
bounds = enum_props.bounds(
"clickable_object",
name="Bounds",
description="Clickable's bounds",
default="hull"
)
input_sockets: dict[str, Any] = {
"region": {
@ -151,8 +155,20 @@ class PlasmaClickableNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.N
def _idprop_mapping(cls):
return {"clickable_object": "clickable"}
@property
def latest_version(self):
return 2
def upgrade(self):
# In version 1 of this node, the bounds type was stored on this node. This could
# be overridden by whatever was in the collision modifier. Version 2 changes the
# bounds property to proxy to the collision modifier's bounds settings.
if self.version == 2:
enum_props.upgrade_bounds(self, "bounds")
self.version = 2
class PlasmaClickableRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node):
class PlasmaClickableRegionNode(idprops.IDPropObjectMixin, PlasmaVersionedNode, bpy.types.Node):
bl_category = "CONDITIONS"
bl_idname = "PlasmaClickableRegionNode"
bl_label = "Clickable Region Settings"
@ -162,10 +178,12 @@ class PlasmaClickableRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.t
description="Object that defines the region mesh",
type=bpy.types.Object,
poll=idprops.poll_mesh_objects)
bounds = EnumProperty(name="Bounds",
description="Physical object's bounds (NOTE: only used if your clickable is not a collider)",
items=bounds_types,
default="hull")
bounds = enum_props.bounds(
"region_object",
name="Bounds",
description="Physical object's bounds",
default="hull"
)
output_sockets = {
"satisfies": {
@ -215,6 +233,18 @@ class PlasmaClickableRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.t
def _idprop_mapping(cls):
return {"region_object": "region"}
@property
def latest_version(self):
return 2
def upgrade(self):
# In version 1 of this node, the bounds type was stored on this node. This could
# be overridden by whatever was in the collision modifier. Version 2 changes the
# bounds property to proxy to the collision modifier's bounds settings.
if self.version == 1:
enum_props.upgrade_bounds(self, "bounds")
self.version = 2
class PlasmaClickableRegionSocket(PlasmaNodeSocketBase, bpy.types.NodeSocket):
bl_color = (0.412, 0.0, 0.055, 1.0)
@ -395,7 +425,7 @@ class PlasmaVolumeReportNode(PlasmaNodeBase, bpy.types.Node):
row.prop(self, "threshold", text="")
class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node):
class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaVersionedNode, bpy.types.Node):
bl_category = "CONDITIONS"
bl_idname = "PlasmaVolumeSensorNode"
bl_label = "Region Sensor"
@ -419,9 +449,11 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
description="Object that defines the region mesh",
type=bpy.types.Object,
poll=idprops.poll_mesh_objects)
bounds = EnumProperty(name="Bounds",
description="Physical object's bounds",
items=bounds_types)
bounds = enum_props.bounds(
"region_object",
name="Bounds",
description="Physical object's bounds"
)
# Detector Properties
report_on = EnumProperty(name="Triggerers",
@ -586,6 +618,18 @@ class PlasmaVolumeSensorNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.type
return (self.find_input_socket("exit").allow or
self.find_input("exit", "PlasmaVolumeReportNode") is not None)
@property
def latest_version(self):
return 2
def upgrade(self):
# In version 1 of this node, the bounds type was stored on this node. This could
# be overridden by whatever was in the collision modifier. Version 2 changes the
# bounds property to proxy to the collision modifier's bounds settings.
if self.version == 1:
enum_props.upgrade_bounds(self, "bounds")
self.version = 2
class PlasmaVolumeSettingsSocket(PlasmaNodeSocketBase):
bl_color = (43.1, 24.7, 0.0, 1.0)

26
korman/nodes/node_logic.py

@ -20,8 +20,8 @@ from bpy.props import *
from typing import *
from PyHSPlasma import *
from .. import enum_props
from .node_core import *
from ..properties.modifiers.physics import bounds_types, bounds_type_index, bounds_type_str
from .. import idprops
class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.types.Node):
@ -33,25 +33,19 @@ class PlasmaExcludeRegionNode(idprops.IDPropObjectMixin, PlasmaNodeBase, bpy.typ
# ohey, this can be a Python attribute
pl_attrib = {"ptAttribExcludeRegion"}
def _get_bounds(self):
if self.region_object is not None:
return bounds_type_index(self.region_object.plasma_modifiers.collision.bounds)
return bounds_type_index("hull")
def _set_bounds(self, value):
if self.region_object is not None:
self.region_object.plasma_modifiers.collision.bounds = bounds_type_str(value)
region_object = PointerProperty(name="Region",
description="Region object's name",
type=bpy.types.Object,
poll=idprops.poll_mesh_objects)
bounds = EnumProperty(name="Bounds",
description="Region bounds",
items=bounds_types,
get=_get_bounds,
set=_set_bounds)
block_cameras = BoolProperty(name="Block Cameras",
description="The region blocks cameras when it has been cleared")
bounds = enum_props.bounds(
"region_object",
name="Bounds",
description="Region bounds"
)
block_cameras = BoolProperty(
name="Block Cameras",
description="The region blocks cameras when it has been cleared"
)
input_sockets:dict[str, dict[str, Any]] = {
"safe_point": {

81
korman/nodes/node_messages.py

@ -21,6 +21,7 @@ from PyHSPlasma import *
from typing import *
from .. import enum_props
from .node_core import *
from ..properties.modifiers.physics import subworld_types
from ..properties.modifiers.region import footstep_surfaces, footstep_surface_ids
@ -113,39 +114,30 @@ class PlasmaAnimCmdMsgNode(idprops.IDPropMixin, PlasmaMessageWithCallbacksNode,
("TEXTURE", "Texture", "Texture Action")],
default="OBJECT")
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
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
def _poll_target_object(self, value: bpy.types.Object) -> bool:
if self.anim_type == "TEXTURE":
return idprops.poll_drawable_objects(self, value)
elif self.anim_type == "MESH":
return idprops.poll_animated_objects(self, value)
else:
return True
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
raise RuntimeError()
target_object = PointerProperty(name="Object",
description="Target object",
type=bpy.types.Object)
target_material = PointerProperty(name="Material",
description="Target material",
type=bpy.types.Material,
poll=_poll_material)
target_texture = PointerProperty(name="Texture",
description="Target texture",
type=bpy.types.Texture,
poll=_poll_texture)
target_object = idprops.triprop_object(
"target_object", "target_material", "target_texture",
name="Object",
description="Target object",
poll=_poll_target_object
)
target_material = idprops.triprop_material(
"target_object", "target_material", "target_texture",
name="Material",
description="Target material"
)
target_texture = idprops.triprop_texture(
"target_object", "target_material", "target_texture",
name="Texture",
description="Target texture"
)
go_to = EnumProperty(name="Go To",
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")],
default="kEnd")
# Blender memory workaround
_ENTIRE_ANIMATION = "(Entire Animation)"
def _get_anim_names(self, context):
if self.anim_type == "OBJECT":
items = [(anim.animation_name, anim.animation_name, "")
for anim in self.target_object.plasma_modifiers.animation.subanimations]
return enum_props._get_object_animation_names(self, "target_object")
elif self.anim_type == "TEXTURE":
if self.target_texture is not None:
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, "")]
return enum_props._get_texture_animation_names(self, "target_object", "target_material", "target_texture")
else:
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",
description="Name of the animation to control",
items=_get_anim_names,

101
korman/nodes/node_python.py

@ -21,6 +21,7 @@ from contextlib import contextmanager
from pathlib import Path
from PyHSPlasma import *
from .. import enum_props
from .node_core import *
from .node_deprecated import PlasmaDeprecatedNode, PlasmaVersionedNode
from .. import idprops
@ -823,81 +824,43 @@ class PlasmaAttribTextureNode(idprops.IDPropMixin, PlasmaAttribNodeBase, bpy.typ
pl_attrib = ("ptAttribMaterial", "ptAttribMaterialList",
"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:
# is this the type of dealio that we're looking for?
attrib = self.to_socket
if attrib is not None:
attrib = attrib.attribute_type
if attrib == "ptAttribDynamicMap":
if not self._is_dyntext(value):
return False
elif attrib == "ptAttribMaterialAnimation":
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
if attrib == "ptAttribDynamicMap" and self._is_dyntext(value):
return True
elif attrib == "ptAttribMaterialAnimation" and not self._is_dyntext:
return True
return False
else:
return True
target_object = PointerProperty(name="Object",
description="",
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
# We're not hooked up to a PFM node yet, so let anything slide.
return True
anim_name = EnumProperty(name="Animation",
description="Name of the animation to control",
items=_get_anim_names,
options=set())
target_object = idprops.triprop_object(
"target_object", "material", "texture",
name="Object",
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):
super().init(context)
@ -967,10 +930,6 @@ class PlasmaAttribTextureNode(idprops.IDPropMixin, PlasmaAttribNodeBase, bpy.typ
return {"material_name": bpy.data.materials,
"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):
return texture.type == "IMAGE" and texture.image is None

4
korman/properties/modifiers/base.py

@ -263,7 +263,7 @@ def _restore_properties(dummy):
# 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:
for prop_name in getattr(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)
@ -283,6 +283,6 @@ def _upgrade_modifiers(dummy):
# 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:
for prop in getattr(mod_cls, "deprecated_properties", []):
RemoveProperty(mod_cls, attr=prop)
bpy.app.handlers.load_post.append(_upgrade_modifiers)

34
korman/properties/modifiers/game_gui.py

@ -203,26 +203,6 @@ class GameGuiAnimation(bpy.types.PropertyGroup):
else:
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(
name="Type",
description="Animation type to affect",
@ -233,23 +213,21 @@ class GameGuiAnimation(bpy.types.PropertyGroup):
default="OBJECT",
options=set()
)
target_object: bpy.types.Object = PointerProperty(
target_object: bpy.types.Object = idprops.triprop_object(
"target_object", "target_material", "target_texure",
name="Object",
description="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",
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",
description="Target texture",
type=bpy.types.Texture,
poll=_poll_texture
)

17
korman/properties/modifiers/logic.py

@ -30,9 +30,9 @@ if TYPE_CHECKING:
from ...addon_prefs import game_versions
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz
from ... import enum_props
from ...exporter import ExportError, utils
from ... import idprops
from .physics import bounds_type_index, bounds_type_str, bounds_types
entry_cam_pfm = {
"filename": "xEntryCam.py",
@ -103,15 +103,6 @@ class PlasmaSpawnPoint(PlasmaModifierProperties, PlasmaModifierLogicWiz):
bl_description = "Point at which avatars link into the Age"
bl_object_types = {"EMPTY"}
def _get_bounds(self) -> int:
if self.exit_region is not None:
return bounds_type_index(self.exit_region.plasma_modifiers.collision.bounds_type)
return bounds_type_index("hull")
def _set_bounds(self, value: int) -> None:
if self.exit_region is not None:
self.exit_region.plasma_modifiers.collision.bounds_type = bounds_type_str(value)
entry_camera = PointerProperty(
name="Entry Camera",
description="Camera to use when the player spawns at this location",
@ -126,12 +117,10 @@ class PlasmaSpawnPoint(PlasmaModifierProperties, PlasmaModifierLogicWiz):
poll=idprops.poll_mesh_objects
)
bounds_type = EnumProperty(
bounds_type = enum_props.bounds(
"exit_region",
name="Bounds",
description="",
items=bounds_types,
get=_get_bounds,
set=_set_bounds,
default="hull"
)

18
korman/properties/modifiers/physics.py

@ -18,18 +18,10 @@ from bpy.props import *
from PyHSPlasma import *
from .base import PlasmaModifierProperties
from ...enum_props import _bounds_types
from ...exporter import ExportError
from ... import idprops
# 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!)")
)
# These are the collision sound surface types
surface_types = (
# Danger: do not reorder this one.
@ -55,12 +47,6 @@ subworld_types = (
("subworld", "Separate World", "Causes all objects to be placed in a separate physics simulation"),
)
def bounds_type_index(key):
return list(zip(*bounds_types))[0].index(key)
def bounds_type_str(idx):
return bounds_types[idx][0]
def _set_phys_prop(prop, sim, phys, value=True):
"""Sets properties on plGenericPhysical and plSimulationInterface (seeing as how they are duped)"""
sim.setProperty(prop, value)
@ -78,7 +64,7 @@ class PlasmaCollider(PlasmaModifierProperties):
bounds = EnumProperty(name="Bounds Type",
description="",
items=bounds_types,
items=_bounds_types,
default="hull")
avatar_blocker = BoolProperty(name="Blocks Avatars",

35
korman/properties/modifiers/region.py

@ -22,9 +22,9 @@ from ...exporter import ExportError, ExportAssertionError
from ...helpers import bmesh_from_object
from ... import idprops
from .base import PlasmaModifierProperties, PlasmaModifierLogicWiz
from .base import PlasmaModifierProperties, PlasmaModifierUpgradable, PlasmaModifierLogicWiz
from ... import enum_props
from ..prop_camera import PlasmaCameraProperties
from .physics import bounds_types
footstep_surface_ids = {
"dirt": 0,
@ -128,7 +128,7 @@ class PlasmaCameraRegion(PlasmaModifierProperties):
return self.camera_type == "auto_follow"
class PlasmaFootstepRegion(PlasmaModifierProperties, PlasmaModifierLogicWiz):
class PlasmaFootstepRegion(PlasmaModifierProperties, PlasmaModifierUpgradable, PlasmaModifierLogicWiz):
pl_id = "footstep"
bl_category = "Region"
@ -136,14 +136,17 @@ class PlasmaFootstepRegion(PlasmaModifierProperties, PlasmaModifierLogicWiz):
bl_description = "Footstep Region"
bl_object_types = {"MESH"}
surface = EnumProperty(name="Surface",
description="What kind of surface are we walking on?",
items=footstep_surfaces,
default="stone")
bounds = EnumProperty(name="Region Bounds",
description="Physical object's bounds",
items=bounds_types,
default="hull")
surface = EnumProperty(
name="Surface",
description="What kind of surface are we walking on?",
items=footstep_surfaces,
default="stone"
)
bounds = enum_props.bounds(
name="Region Bounds",
description="Physical object's bounds",
default="hull"
)
def logicwiz(self, bo, tree):
nodes = tree.nodes
@ -172,6 +175,16 @@ class PlasmaFootstepRegion(PlasmaModifierProperties, PlasmaModifierLogicWiz):
def key_name(self):
return "{}_FootRgn".format(self.id_data.name)
@property
def latest_version(self):
return 2
def upgrade(self):
# Version 2 converts the bounds type to a proxy to the collision modifier.
if self.current_version < 2:
enum_props.upgrade_bounds(self, "bounds")
self.current_version = 2
class PlasmaPanicLinkRegion(PlasmaModifierProperties):
pl_id = "paniclink"

Loading…
Cancel
Save