Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversion of XYZ to XZY in terms of rotation during extract layout #17

Closed
moonyuet opened this issue Jul 11, 2024 · 12 comments
Closed

Comments

@moonyuet
Copy link
Member

moonyuet commented Jul 11, 2024

Created the issue for discussion

@LiborBatek The updated PR can successfully flip y axis to z axis. The only circumstance is not working when the y rotation and z rotation both filled out with different values Should we make a validator to make sure these y and z rotations are not filled at the same time to ensure it works correctly in unreal?

thats sounds really restrictive...as things living in 3 dimensional space, no?? I guess we have to solve it somehow...maybe someone can step in and help you out?

Yes It would be great if @antirotor or @BigRoy or even some potential community members can join to discuss on that and needs more research or discussion on figuring out as when y axis moves and it would move the z axis and vice versa. The numbers are varied under the condition of z-axis being over 90, and y axis being less than 90. Anyways, it would be great to have some discussion on how to fix the rotation when it has Y axis and Z axis.

Not sure if i gonna try on the graph discussed below but I think the best idea is to create another issue to discuss and do the decent fix.
image
It is resolved by #64
Originally posted by @moonyuet in #12 (comment)

@moonyuet moonyuet changed the title > > @LiborBatek The updated PR can successfully flip y axis to z axis. The only circumstance is not working when the y rotation and z rotation both filled out with different values Should we make a validator to make sure these y and z rotations are not filled at the same time to ensure it works correctly in unreal? Conversion of XYZ to XZY in terms of rotation during extract layout Jul 11, 2024
@BigRoy
Copy link
Contributor

BigRoy commented Jul 11, 2024

Could you break down what it is exactly that you're trying to do - in the simplest steps / least words?

  • You are looking to transform a transformation matrix from one scene up-axis format to another scene up-axis format?

I feel like this is a problem that has been fixed many times before? (many exporters support toggles for this.) So it would mean we'd just need to find that algorithm/logic and use it as well.

I assume it is basically multiplying a transformation matrix with a "conversion matrix" to generate a new composed matrix that has e.g. the extra rotation in it.

Also commented here.

@moonyuet
Copy link
Member Author

Could you break down what it is exactly that you're trying to do - in the simplest steps / least words?

  • You are looking to transform a transformation matrix from one scene up-axis format to another scene up-axis format?

I feel like this is a problem that has been fixed many times before? (many exporters support toggles for this.) So it would mean we'd just need to find that algorithm/logic and use it as well.

I assume it is basically multiplying a transformation matrix with a "conversion matrix" to generate a new composed matrix that has e.g. the extra rotation in it.

Also commented here.

do you mean multiplying the transform matrix with an inverse matrix?

@BigRoy
Copy link
Contributor

BigRoy commented Jul 11, 2024

Here's a very simple example:

from maya import cmds
import maya.api.OpenMaya as om
import math

# Convert Y-up axis to Z-up axis transformation
rotation = math.radians(-90)
xform = om.MTransformationMatrix()
xform.setRotation(om.MEulerRotation(rotation, 0, 0))
conversion_matrix = xform.asMatrix()

# Get object matrix
node = cmds.ls(sl=1)[0]
matrix = cmds.xform(node, query=True, worldSpace=True, matrix=True)
matrix = om.MMatrix(matrix)


# Rotate the object world-space around origin using the conversion matrix
matrix *= conversion_matrix

# Apply the matrix to test
cmds.xform(node, worldSpace=True, matrix=matrix)

Rotate a node around the origin along the world-space X-axis with -90 degrees so that what was pointing at the Y-axis is now pointing at the Z-axis. Basically converting the Y up axis to the Z as up axis.

Note how the rotation matrix applied doesn't change - this conversion matrix is the same to be applied for all nodes then.

You'd only need this conversion for root nodes, because child node would already 'reorient' based on the parent's transformation. :)

@moonyuet
Copy link
Member Author

moonyuet commented Jul 11, 2024

2024-07-11.23-22-45.mp4

I have tested with Maya, the number of Y and Z axis isn't being switched accordingly in the scene.

@BigRoy
Copy link
Contributor

BigRoy commented Jul 11, 2024

2024-07-11.23-22-45.mp4

I have tested with Maya, the number of Y and Z axis isn't being switched accordingly in the scene.

Visually to me, this is exactly doing what it should. Whatever is pointing Z-axis (in world) in your scene is now after the script pointing Y-axis. Which as mentioned before should "Basically converting the Y up axis to the Z as up axis."

We don't care about the rotation values - we care about the scene's orientation from? No?

@moonyuet
Copy link
Member Author

2024-07-11.23-22-45.mp4
I have tested with Maya, the number of Y and Z axis isn't being switched accordingly in the scene.

Visually to me, this is exactly doing what it should. Whatever is pointing Z-axis (in world) in your scene is now after the script pointing Y-axis. Which as mentioned before should "Basically converting the Y up axis to the Z as up axis."

We don't care about the rotation values - we care about the scene's orientation from? No?

Perhaps you are right.
I implemented the code with some changes of basis matrix on the extract layout. The Y and Z rots are correct, scaleXYZ are correct, only that the translation and X rot is with a bit strange number
I will update the clip tomorrow to explain about that

@MustafaJafar
Copy link
Contributor

Perhaps you are right.
I implemented the code with some changes of basis matrix on the extract layout. The Y and Z rots are correct, scaleXYZ are correct, only that the translation and X rot is with a bit strange number
I will update the clip tomorrow to explain about that

So, you want to convert the axis rotation while preserving the the scene rotation values ?

@moonyuet
Copy link
Member Author

moonyuet commented Jul 12, 2024

I have edited the extract layout to export the data, the basis identity matrix ensure the axis would go xzy instead of xyz
(There is some progress for sure after the code tweak)

import json
import math
import os

from ayon_api import get_representation_by_id
from ayon_maya.api import plugin
from maya import cmds
from maya.api import OpenMaya as om

class ExtractLayout(plugin.MayaExtractorPlugin):
    """Extract a layout."""

    label = "Extract Layout"
    families = ["layout"]
    project_container = "AVALON_CONTAINERS"
    optional = True

    def process(self, instance):
        # Define extract output file path
        stagingdir = self.staging_dir(instance)

        # Perform extraction
        self.log.debug("Performing extraction..")

        if "representations" not in instance.data:
            instance.data["representations"] = []

        json_data = []
        # TODO representation queries can be refactored to be faster
        project_name = instance.context.data["projectName"]

        for asset in cmds.sets(str(instance), query=True):
            # Find the container
            project_container = self.project_container
            container_list = cmds.ls(project_container)
            if len(container_list) == 0:
                self.log.warning("Project container is not found!")
                self.log.warning("The asset(s) may not be properly loaded after published") # noqa
                continue

            grp_loaded_ass = instance.data.get("groupLoadedAssets", False)
            if grp_loaded_ass:
                asset_list = cmds.listRelatives(asset, children=True)
                # WARNING This does override 'asset' variable from parent loop
                #   is it correct?
                for asset in asset_list:
                    grp_name = asset.split(':')[0]
            else:
                grp_name = asset.split(':')[0]
            containers = cmds.ls("{}*_CON".format(grp_name))
            if len(containers) == 0:
                self.log.warning("{} isn't from the loader".format(asset))
                self.log.warning("It may not be properly loaded after published") # noqa
                continue
            container = containers[0]

            representation_id = cmds.getAttr(
                "{}.representation".format(container))

            representation = get_representation_by_id(
                project_name,
                representation_id,
                fields={"versionId", "context"}
            )

            self.log.debug(representation)

            version_id = representation["versionId"]
            # TODO use product entity to get product type rather than
            #    data in representation 'context'
            repre_context = representation["context"]
            product_type = repre_context.get("product", {}).get("type")
            if not product_type:
                product_type = repre_context.get("family")

            json_element = {
                "product_type": product_type,
                "instance_name": cmds.getAttr(
                    "{}.namespace".format(container)),
                "representation": str(representation_id),
                "version": str(version_id)
            }

            loc = cmds.xform(asset, query=True, translation=True)
            rot = cmds.xform(asset, query=True, rotation=True, euler=True)
            scl = cmds.xform(asset, query=True, relative=True, scale=True)

            json_element["transform"] = {
                "translation": {
                    "x": loc[0],
                    "y": loc[1],
                    "z": loc[2]
                },
                "rotation": {
                    "x": math.radians(rot[0]),
                    "y": math.radians(rot[1]),
                    "z": math.radians(rot[2])
                },
                "scale": {
                    "x": scl[0],
                    "y": scl[1],
                    "z": scl[2]
                }
            }

            row_length = 4
            t_matrix_list = cmds.xform(asset, query=True, worldSpace=True, matrix=True)

            transform_mm = om.MMatrix(t_matrix_list)
            transform = om.MTransformationMatrix()
            transform.setRotation(om.MEulerRotation(math.radians(-90), 0, 0))

            conversion_matrix = transform.asMatrix()
            transform_mm *= conversion_matrix
            t_matrix_list = list(transform_mm)

            t_matrix = []
            for i in range(0, len(t_matrix_list), row_length):
                t_matrix.append(t_matrix_list[i:i + row_length])

            json_element["transform_matrix"] = [
                list(row)
                for row in t_matrix
            ]

            basis_list = [
                1, 0, 0, 0,
                0, 0, 1, 0,
                0, 1, 0, 0,
                0, 0, 0, 1
            ]

            basis_mm = om.MMatrix(basis_list)
            basis = om.MTransformationMatrix(basis_mm)

            b_matrix_list = list(basis.asMatrix())
            b_matrix = []

            for i in range(0, len(b_matrix_list), row_length):
                b_matrix.append(b_matrix_list[i:i + row_length])

            json_element["basis"] = []
            for row in b_matrix:
                json_element["basis"].append(list(row))

            json_data.append(json_element)

        json_filename = "{}.json".format(instance.name)
        json_path = os.path.join(stagingdir, json_filename)

        with open(json_path, "w+") as file:
            json.dump(json_data, fp=file, indent=2)

        json_representation = {
            'name': 'json',
            'ext': 'json',
            'files': json_filename,
            "stagingDir": stagingdir,
        }
        instance.data["representations"].append(json_representation)

        self.log.debug("Extracted instance '%s' to: %s",
                       instance.name, json_representation)

When I extract layout in maya with the code above, and then import the layout, it shows wrong translation, and the rotation is wrong. You can find this scenegrab helpful to understand the issue
image

2024-07-12.15-13-55.mp4

@BigRoy
Copy link
Contributor

BigRoy commented Jul 12, 2024

I don't understand - Houdini has a different up-axis - and due to this you need to rotate your objects so it faces the correct direction. As soon as you start applying a rotation, the values being different is correct behavior, right? And if the object already had rotation - to rotate it in world-space (to convert Y-up to Z-up) would basically mean you're rotating across multiple euler angles (depending on the current values and rotation order).

In short, the values not looking like the exact values in Maya in the outliner is entirely correct.

The rotation values in Unreal in the attributes are irrelevant - the primary point is that we convert the transformations to be valid for Unreal so we need to check the object visually in the scene instead, not compare the attributes.

If you do not want to mess with the rotation values you may be be better off importing everything into a group - and then you apply the rotation to the group. Then all child transforms can remain completely unaffected.

@BigRoy
Copy link
Contributor

BigRoy commented Jul 12, 2024

Side note: You seem to be exporting the transform matrix but also individual translate, rotate, scale into the JSON. I'm not sure why. The transformation matrix defines the translate, rotate, scale (and shear) OR vice-versa. If one changes, the other should as well. So you'd only need to store one of the two, usually the matrix - unless for whatever reason you need rotation values below/above 360 degrees span (e.g. outside -180 and +180) because the matrix can't hold that information.

@moonyuet
Copy link
Member Author

moonyuet commented Jul 15, 2024

I did some other test within Maya and read along the unreal code. I guess we can tweak the code from both sides.
It could be some converting issues on the matrix, where somehow the there is some multiplication between scale and rotation.

from maya.api import OpenMaya as om
row_length = 4
basis_list = [
    1, 0, 0, 0,
    0, 0, 1, 0,
    0, 1, 0, 0,
    0, 0, 0, 1
]
# identity matrix to have (x, z, y, w)
basis_mm = om.MMatrix(basis_list)
basis = om.MTransformationMatrix(basis_mm)

b_matrix_list = basis.asMatrix()


t_matrix_list = cmds.xform("pSphere1", query=True, matrix=True, worldSpace=True)


transform_mm = om.MMatrix(t_matrix_list)
transform = om.MTransformationMatrix(transform_mm)
t = transform.translation(om.MSpace.kWorld)
t = om.MVector(t.x, t.z, t.y)
transform.setTranslation(t, om.MSpace.kWorld)
transform.rotateBy(
    om.MEulerRotation(0, 0, 0), om.MSpace.kWorld)
transform.scaleBy([1.0, 1.0, 1.0], om.MSpace.kObject)
t_matrix_list = transform.asMatrix()
t_matrix_inverse_list = transform.asMatrixInverse()

test_matrix = t_matrix_list * b_matrix_list
# test_matrix = t_matrix_inverse_list * t_matrix_list * b_matrix_list    # M^-1 *M*I
cmds.xform("pSphere2", matrix=test_matrix, worldSpace=True)

I also did some calculations to figure out the matrix formula to converting Y to Z to find out their relationship.

from maya.api import OpenMaya as om
# t.x = 10, t.y=9, t.z=8, r.x = 30, r.y=60, r.z=90, s.x=10, s.y=11, s.z=12
worldMatrix_1 = cmds.xform('pSphere1', query=True, matrix=True, worldSpace=True)
# t.x = 10, t.y=8, t.z=9, r.x = 30, r.y=90, r.z=60, s.x=10, s.y=12, s.z=11
worldMatrix_2 = cmds.xform('pSphere2', query=True, matrix=True, worldSpace=True)  
world_Matrix_mm_1 = om.MMatrix(worldMatrix_1)
world_transform_1 = om.MTransformationMatrix(world_Matrix_mm_1)

# find the inverse
world_1_matrix_list = world_transform_1.asMatrixInverse()

world_Matrix_mm_2 = om.MMatrix(worldMatrix_2)
world_transform_2 = om.MTransformationMatrix(world_Matrix_mm_2)

world_2_matrix_list = world_transform_2.asMatrix()

# Not a inverse of world_1_matrix_list * H = world_2_matrix_list
H = world_2_matrix_list *world_1_matrix_list
# maya.api.OpenMaya.MMatrix(((0.86602540378443859659, -0.2272727272727274872, -0.36084391824351613742, 0), (0.51961524227066357984, 0.88146840206423893171, 0.39951905283832911397, 0), 
#(0.27499999999999980016, -0.53349364905389040636, 0.74067831006786766235, 0), (-0.13660254037844390962, -0.016637518353838032237, -0.026415608175648364053, 1)))

I would be tweaking on both ayon-maya, ayon-unreal (hard-coded a little bit) to see if this outcome can help to get the correct attributes for Alembic
EDIT: I am actually overthinking a little bit, I can use the original code to improve.

@moonyuet
Copy link
Member Author

moonyuet commented Aug 1, 2024

I have used the code below to manually put the axis, the matrix would be changed depending on what value we get from cmds.xform("the asset", matrix=True, query=True)

import unreal
transform_matrix = unreal.Matrix(
    [1.0, 0.0, 0.0, 0.0],
    [0.0, 1.0, 0.0, 0.0],
    [0.0, 0.0, 1.0, 0.0],
    [0.0, 0.0, 0.0, 1.0]
)
t = transform_matrix.transform()
sel_objects = unreal.EditorUtilityLibrary.get_selected_assets()
ar = unreal.AssetRegistryHelpers.get_asset_registry()
for asset in sel_objects:
    obj = ar.get_asset_by_object_path(asset.get_path_name()).get_asset()
    actor = unreal.EditorLevelLibrary.spawn_actor_from_object(
        obj, t.translation
    )
    actor.set_actor_rotation(t.rotation.rotator(), False)
    actor.set_actor_scale3d(t.scale3d)

and get this conclusion below so far
IMG_20240801_235600

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants