123 lines
4.3 KiB
Python
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()]
|