Answered

I see that the new API also works for IK handles, but how do we know if we have a bone selected or an IK handle?

ytGameDevDave 2 years ago updated by Peter - Soxware Developer 2 years ago 9

I see that the new API also works for IK handles, but how do we know if we have a bone selected or an IK handle?

UMotion Version:
1.28
Unity Version:
2020 LTS
GOOD, I'M SATISFIED
Satisfaction mark by ytGameDevDave 2 years ago

Correction: it is possible to copy correct IKHandle position & rotation if we store the local rotation and position and the IK object is not pinned 

This is a workaround for now:

But there is no way to fetch the UMotion positions for the IK Handles when they are Pinned

the local position & the world position values do not match the values in the PoseEditor because of the ik being pinned

We're just not able to fetch ik positions when they are pinned

CopyPose and PastePose when IKHandles are not Pinned


(Place in Editor Folder, code is super unoptimized sorry)

using UnityEngine;
using UnityEditor;
using UMotionEditor.API;
using System;
using System.Collections;
using System.Collections.Generic;

[InitializeOnLoad]
public class UMotionExtensions
{
    public static List<transform> list, AllBones, AllIKs;
    public static List<quaternion> AllIKRotations, AllBoneRotations;
    public static List<vector3> AllIKPositions;

   static UMotionExtensions()
   {
      ClipEditor.AddMenuItem(ClipEditor.MenuCategory.File, "My Menu Item", MyMenuItemPressed);
      PoseEditor.AddButton(PoseEditor.FoldoutCategory.Tools, "Copy Pose", "Copies the bonedata.", CopyPose);
      PoseEditor.AddButton(PoseEditor.FoldoutCategory.Tools, "Paste Pose", "Pastes the bonedata.", PastePose);
   }

   public Quaternion[] allBoneRotations;
   
   private static void MyMenuItemPressed()
   {
       ClipEditor.SetFrameCursorPosition (5, "oh no");
   }

   static void CopyPose()
   {
       // Reset & Define the static lists for the Heap
        list = new List<transform>();

        PoseEditor.GetSelectedTransforms(list);

        AllBones = new List<transform>();
        AllIKs = new List<transform>();

        for (int i = 0; i < list.Count; i++)
        {
            if (!list[i].name.Contains("IK"))
            AllBones.Add (list[i]);
            else
            AllIKs.Add (list[i]);
        }

        // Reset & Define the static lists for the Heap
        AllBoneRotations = new List<quaternion>();
        
        AllIKRotations = new List<quaternion>();
        AllIKPositions = new List<vector3>();
        
        for (int i = 0; i < AllBones.Count; i++)
        {
            AllBoneRotations.Add (AllBones[i].localRotation);
        }

        for (int i = 0; i < AllIKs.Count; i++)
        {
            AllIKRotations.Add (AllIKs[i].localRotation);

            AllIKPositions.Add (AllIKs[i].localPosition);
        }
   }

   static void PastePose()
   {
        for (int i = 0; i < AllBones.Count; i++)
        {
            PoseEditor.TrySetFkLocalRotation(AllBones[i], AllBoneRotations[i], "Undo", true);
        }

        for (int i = 0; i < AllIKs.Count; i++)
        {
            PoseEditor.TrySetFkLocalRotation(AllIKs[i], AllIKRotations[i], "Undo", true);
            PoseEditor.TrySetFkLocalPosition(AllIKs[i], AllIKPositions[i], "Undo", true);
        }
   }
}

For IKHandles the code unfortunately does not work between clips, unity throws this error:

I think the reason for this is because IKHandles get generated per clip, so the transform is never the same. With bones it does work however, because the bones do stay the same through clips. 

Doesn't work for IKPoles either ofc.

This would need to be solved by storing strings and finding the objects by name again 

String solution worked... This fixes all of it... This is the workaround for now... Added bonepositions too since the root has to be moved as well for the IKHandles to paste into the correct place (because since they are not pinned, the root can move the IKHandles)...

using UnityEngine;
using UnityEditor;
using UMotionEditor.API;
using System;
using System.Collections;
using System.Collections.Generic;

[InitializeOnLoad]
public class UMotionExtensions
{
    public static List<transform> list, AllBones;
    public static List<string> AllIKs;
    public static List<quaternion> AllIKRotations, AllBoneRotations;
    public static List<vector3> AllIKPositions, AllBonePositions;

   static UMotionExtensions()
   {
      ClipEditor.AddMenuItem(ClipEditor.MenuCategory.File, "My Menu Item", MyMenuItemPressed);
      PoseEditor.AddButton(PoseEditor.FoldoutCategory.Tools, "Copy Pose", "Copies the bonedata.", CopyPose);
      PoseEditor.AddButton(PoseEditor.FoldoutCategory.Tools, "Paste Pose", "Pastes the bonedata.", PastePose);
   }

   public Quaternion[] allBoneRotations;
   
   private static void MyMenuItemPressed()
   {
       Debug.Log("My menu item was pressed!");
   }

   static void CopyPose()
   {
       // Reset & Define the static lists for the Heap
        list = new List<transform>();

        PoseEditor.GetSelectedTransforms(list);

        AllBones = new List<transform>();
        AllIKs = new List<string>();

        for (int i = 0; i < list.Count; i++)
        {
            if (!list[i].name.Contains("IK"))
            AllBones.Add (list[i]);
            else
            AllIKs.Add (list[i].name);
        }

        // Reset & Define the static lists for the Heap
        AllBoneRotations = new List<quaternion>();
        AllBonePositions = new List<vector3>();
        
        AllIKRotations = new List<quaternion>();
        AllIKPositions = new List<vector3>();
        
        for (int i = 0; i < AllBones.Count; i++)
        {
            AllBoneRotations.Add (AllBones[i].localRotation);

            AllBonePositions.Add (AllBones[i].localPosition);
        }

        for (int i = 0; i < AllIKs.Count; i++)
        {
            AllIKRotations.Add (GameObject.Find(AllIKs[i]).transform.localRotation);

            AllIKPositions.Add (GameObject.Find(AllIKs[i]).transform.localPosition);
        }        
   }

   static void PastePose()
   {
        for (int i = 0; i < AllBones.Count; i++)
        {
            PoseEditor.TrySetFkLocalRotation(AllBones[i], AllBoneRotations[i], "Undo", true);
            PoseEditor.TrySetFkLocalPosition(AllBones[i], AllBonePositions[i], "Undo", true);
        }

        for (int i = 0; i < AllIKs.Count; i++)
        {
            PoseEditor.TrySetFkLocalRotation(GameObject.Find(AllIKs[i]).transform, AllIKRotations[i], "Undo", true);
            PoseEditor.TrySetFkLocalPosition(GameObject.Find(AllIKs[i]).transform, AllIKPositions[i], "Undo", true);
        }
   }
}

Sidenote: It obviously also does not work with anything that is "childof" constrained... Maybe Peter will add a way for us to know if an IK has certain properties so that we can do alternative Transform calculations to fit the correct PoseEditor values?

A method transforming world pos/rot/sca to whatever the object transform needs depending on its properties would be a very nice feature... 

Btw it's a genius idea to give us this ability instead of just supplying us with premade features, we can now make anything ourselves.Thank you so much and can't wait to see more of the API.

Thank you Peter!

Answered

Hi,
thank you very much for using the UMotion API, providing all this useful feedback and for sharing your extension's code.

I see that the new API also works for IK handles, but how do we know if we have a bone selected or an IK handle?

Checking the name currently is the only way unfortunately. I'm trying to address this in a future release.

For IKHandles the code unfortunately does not work between clips, unity throws this error:
I think the reason for this is because IKHandles get generated per clip, so the transform is never the same. With bones it does work however, because the bones do stay the same through clips.

Your assumption is correct, all "custom transforms" (i.e. bones/transforms that only exist in the UMotion context and aren't part of the real object) do get re-created from time to time. I'm trying to provide a better way to address bones/transforms in a future UMotion API release.

the local position & the world position values do not match the values in the PoseEditor because of the ik being pinned

The transform's local coordinates are relative to the actual "real" parent. The key frame value of a pinned object inside UMotion's animation clip is relative to the "fake/temporary" parent, though. Same goes for regular child-of constraint. The API currently does not expose enough information to deal with this correctly. I need to think about a good API design to deal with that fact and am trying to improve that in a future release.

Let me know if you have any other questions.

Best regards,

Peter

Thank you for your response Peter 😊

I understand that you want to create the appropriate object space transformations dependently, but I think it doesn't have to be that complicated. 

If you store the world location and rotation of an object and apply it, any systems (pin/contraint) should adapt to these new values. In Unity, no matter what relationship an object has, it will position and rotate itself to the assigned world coordinates, and work accordingly unless there are conflicting systems.

If you write the local/world space separate from these systems (pin/constraint) and let them adapt to it agnostically/independently, it will work regardless of what systems are turned on. 

Maybe temporarily disabling such systems and then solving them in the right order (disable pin/contstraint systems first, apply world pos/rot/sca, then reapply contraint/pin systems) you could make writing transform data non-destructive.

Maybe I'm dead wrong and there is some detail/caveat that you know of that conflicts with this approach, but I think this is more an issue of how these systems are programmed to function and cooperate. 

Please let me know if I am painstakingly wrong. 

Thank you again Peter, this is a beautiful software and I really enjoy working with it.

Great input, thank you very much. I think you are correct and I'm aiming to get it done the way you described it.


Best regards,
Peter