Files
Hippolyzer/addon_examples/deformer_helper.py
2021-04-30 17:30:24 +00:00

123 lines
4.3 KiB
Python

"""
Helper for making deformer anims. This could have a GUI I guess.
"""
import dataclasses
from typing import *
from hippolyzer.lib.base.datatypes import Vector3, Quaternion, UUID
from hippolyzer.lib.base.llanim import Joint, Animation, PosKeyframe, RotKeyframe
from hippolyzer.lib.proxy.addon_utils import show_message, BaseAddon, SessionProperty
from hippolyzer.lib.proxy.addons import AddonManager
from hippolyzer.lib.proxy.commands import handle_command, Parameter
from hippolyzer.lib.proxy.region import ProxiedRegion
from hippolyzer.lib.proxy.sessions import Session
import local_anim
# We require any addons from local_anim to be loaded, and we want
# our addon to be reloaded whenever local_anim changes.
AddonManager.hot_reload(local_anim, require_addons_loaded=True)
@dataclasses.dataclass
class DeformerJoint:
pos: Optional[Vector3] = None
rot: Optional[Quaternion] = None
def build_deformer(joints: Dict[str, DeformerJoint]) -> bytes:
anim = Animation(
major_version=1,
minor_version=0,
base_priority=5,
duration=1.0,
loop_out_point=1.0,
loop=True,
)
for joint_name, joint in joints.items():
if not any((joint.pos, joint.rot)):
continue
anim.joints[joint_name] = Joint(
priority=5,
rot_keyframes=[RotKeyframe(time=0.0, rot=joint.rot)] if joint.rot else [],
pos_keyframes=[PosKeyframe(time=0.0, pos=joint.pos)] if joint.pos else [],
)
return anim.to_bytes()
class DeformerAddon(BaseAddon):
deform_joints: Dict[str, DeformerJoint] = SessionProperty(dict)
@handle_command()
async def save_deformer(self, _session: Session, _region: ProxiedRegion):
filename = await AddonManager.UI.save_file(filter_str="SL Anim (*.anim)")
if not filename:
return
with open(filename, "wb") as f:
f.write(build_deformer(self.deform_joints))
# `sep=None` makes `coord` greedy, taking the rest of the message
@handle_command(
joint_name=str,
coord_type=str,
coord=Parameter(Vector3.parse, sep=None),
)
async def set_deformer_joint(self, session: Session, region: ProxiedRegion,
joint_name: str, coord_type: str, coord: Vector3):
"""
Set a coordinate for a joint in the deformer
Example:
set_deformer_joint mNeck pos <0, 0, 0.5>
set_deformer_joint mNeck rot <0, 180, 0>
"""
joint_data = self.deform_joints.setdefault(joint_name, DeformerJoint())
if coord_type == "pos":
joint_data.pos = coord
elif coord_type == "rot":
joint_data.rot = Quaternion.from_euler(*coord, degrees=True)
else:
show_message(f"Unknown deformer component {coord_type}")
return
self._reapply_deformer(session, region)
@handle_command()
async def stop_deforming(self, session: Session, region: ProxiedRegion):
"""Disable any active deformer, may have to reset skeleton manually"""
self.deform_joints.clear()
self._reapply_deformer(session, region)
def _reapply_deformer(self, session: Session, region: ProxiedRegion):
anim_data = None
if self.deform_joints:
anim_data = build_deformer(self.deform_joints)
local_anim.LocalAnimAddon.apply_local_anim(session, region, "deformer_addon", anim_data)
def handle_rlv_command(self, session: Session, region: ProxiedRegion, source: UUID,
cmd: str, options: List[str], param: str):
# An object in-world can also tell the client how to deform itself via
# RLV-style commands.
# We only handle commands
if param != "force":
return
if cmd == "stop_deforming":
self.deform_joints.clear()
elif cmd == "deform_joints":
self.deform_joints.clear()
for joint_data in options:
joint_split = joint_data.split("|")
pos = Vector3(*joint_split[1].split("/")) if joint_split[1] else None
rot = Quaternion(*joint_split[2].split("/")) if joint_split[2] else None
self.deform_joints[joint_split[0]] = DeformerJoint(pos=pos, rot=rot)
else:
return
self._reapply_deformer(session, region)
return True
addons = [DeformerAddon()]