Custom Transform Node & xform

Custom Transform Node & xform

chirieacam
Enthusiast Enthusiast
5,185 Views
46 Replies
Message 1 of 47

Custom Transform Node & xform

chirieacam
Enthusiast
Enthusiast

Is it possible to add a rotation offset to a custom transform node while making the rest of maya be aware of it? I get the following issues with it:

  • Getting the transform's world rotation using xform doesn't work, it always return without the offset. I couldn't find what else I need to override in my node to make it work. The default joint or hikEffector both have extra pre-rotation/offset, and they do return the correct results, so there must be a way to do it. 
    maya.cmds.xform(obj, query=True, rotation=True, worldSpace=True)
     
  • Using Modify->Match Transformations->Match Rotation command also fails to take the offset into account. It might be related to issue #1 using xform.

Is there some documentation somewhere what those commands expects to make them aware of the new rotation offset?

0 Likes
Accepted solutions (1)
5,186 Views
46 Replies
Replies (46)
Message 2 of 47

brentmc
Autodesk
Autodesk

Hi,

Are you using the rockingTransform SDK example to model your custom transform?

If so you might want to try the following changes as I think the custom rotation should be applied in asRotateMatrix() and not in asMatrix().

 --

Brent

 

Brent McPherson
Principle Engineer
0 Likes
Message 3 of 47

chirieacam
Enthusiast
Enthusiast

Thank you @brentmc for replying.

 

I tried what you said: used the rockingTransform example and moved the custom rotation to asRotateMatrix().

Unfortunately it still doesn't work. 

Modify->Match Transformations->Match Rotation

and

maya.cmds.xform(obj, query=True, rotation=True, worldSpace=True)

still doesn't take notice of the extra rotation.

 

Any other ideas I can try?

0 Likes
Message 4 of 47

brentmc
Autodesk
Autodesk

Hi,

 

Unfortunately it appears that those commands are explicitly taking jointOrient into account so I don't think you will be able to get them to work with your custom transform in their current form.

We are aware of some limitations with custom transforms (trying to replicate joint-like rotation offsets) so I will log an issue for this.

Thanks.
--

Brent

Brent McPherson
Principle Engineer
0 Likes
Message 5 of 47

chirieacam
Enthusiast
Enthusiast

Oh, I see... But that means that those commands also takes into account the pre-rotation of the hikEffector? HumanIK seems to be a plugin, so I was thinking that wouldn't be hardcoded into those commands. It is possible that HumanIK makes it work somehow?

 

If indeed there's nothing we can do, I'll have to continue to use the joint and its jointOrient/parentOffsetMatrix/inverseScale to have pre-transform values.

But there is an issue with that: inverseScale acts like a parent scale, so if I rotate the object while having non-uniform scaling, the objects gets skewed.

Is it possible to fix that somehow, applying the scale after the rotation?

 

All of this is to use constraints without having the channels locked, and without using extra offsets nodes. Using the joint's extra pre-transform attributes works pretty good, except for non-uniform scaling because of how inverseScale works.

0 Likes
Message 6 of 47

chirieacam
Enthusiast
Enthusiast

Actually it seems the hikEffector's pre-rotation is only used for xform. Match Rotation doesn't work for it either.

Message 7 of 47

brentmc
Autodesk
Autodesk

Hi,

 

The hikEffector derives directly from Maya's Ttransform class and is using a preRotation (which is effectively the same as jointOrient) that is not exposed through MPxTransform. (and this is the limitation I mentioned in my last reply)

 

As for inverseScale. Each joint's inverseScale is directly connected to its parent's scaling attribute. Have you tried removing that connection in the Node Editor? (though I admit this is not my area of expertise)

--

Brent  

Brent McPherson
Principle Engineer
0 Likes
Message 8 of 47

brentmc
Autodesk
Autodesk

Match rotation (which is a fairly recent addition to Maya) is based on the orient constraint so I'm guessing that won't work either and we will need to update the match rotation command.

 

Thanks.

--

Brent

Brent McPherson
Principle Engineer
0 Likes
Message 9 of 47

chirieacam
Enthusiast
Enthusiast

Oh, then indeed, exposing that preRotation to MPxTransform would be great.

 

As for the inverseScale, yes, I don't have it connected to another joint. I'm connecting it to an object I want to match the scale from. Since it's an inverse, I divide each value from 1 (1 / scaleValue). Here's an example (both cubes should be the same, but the one under the joint is skewed if the joint is rotated): 

 

joint_inverseScale.jpg

0 Likes
Message 10 of 47

brentmc
Autodesk
Autodesk

Hi,

 

Do you have a simple example script or scene shwoing the hikEffector match rotation issue that I could attach to the issue?

 

Alternatively, can you describe in more detail the workflow. e.g. are you trying to match the hikEffector to another hikEffector or a different transform/joint?

Just want to make sure we have good repro steps.

Thanks.

--

Brent

Brent McPherson
Principle Engineer
0 Likes
Message 11 of 47

chirieacam
Enthusiast
Enthusiast

Hi,

 

Here's a little script to be run in an empty scene. I think that's clear enough.

import maya.cmds as cmds

effector = cmds.createNode('hikEffector')
cmds.setAttr(effector + '.preRotationX', 45)

cube = cmds.polyCube()[0]
cmds.matchTransform(cube, effector)

print cmds.xform(effector, query=True, rotation=True, worldSpace=True)
print cmds.xform(cube, query=True, rotation=True, worldSpace=True)

# result:
# [45.000000000000014, 0.0, 0.0]
# [0.0, 0.0, 0.0] - should be as above
0 Likes
Message 12 of 47

brentmc
Autodesk
Autodesk

Thanks for the script, it was very helpful.

Also wanted to mention that you can workaround this by applying a temporary orient constraint and then deleting it right after.

Note: The orientConstraint is connecting the preRotation on the hikEffector into the jointOrient attribute on the constraint which is why it works there while matchTransform was only looking for jointOrient.

--

Brent

Brent McPherson
Principle Engineer
0 Likes
Message 13 of 47

chirieacam
Enthusiast
Enthusiast
Accepted solution

For anyone interested, the 2022.3 Update fixes the match transform problem and also makes the pre-rotation available https://help.autodesk.com/view/MAYAUL/2022/ENU/?guid=Maya_SDK_What_s_New_What_s_Changed_2022_3_Whats...

Awesome! Thanks to anyone who listened and did this.

0 Likes
Message 14 of 47

brentmc
Autodesk
Autodesk

You're welcome! 😀

 

My team worked on both those issues and we appreciate your feedback in bringing them to our attention!

Brent McPherson
Principle Engineer
Message 15 of 47

chirieacam
Enthusiast
Enthusiast

Hey @brentmc,

I attempted to make use of the new classes, but there's still an issue. I've used the rockingTransform example that is using the newly PreRotation classes, but the xform still doesn't return the pre-rotation:

cmds.xform('rockingTransform', query=True, rotation=True, worldSpace=True)

 The rotate manipulator and the match transform command are working, which is great. Is this a new issue, or there's something extra I need to do that the rockingTransform doesn't do?

0 Likes
Message 16 of 47

chirieacam
Enthusiast
Enthusiast

I've also added a pre-translation and pre-scale.

 

The xform returns those extra pre-values when retrieving the matrix or scale:

cmds.xform('rockingTransform', query=True, matrix=True, worldSpace=True)
cmds.xform('rockingTransform', query=True, scale=True, worldSpace=True)

 But the translation and rotation are ignored:

 

cmds.xform('rockingTransform', query=True, translation=True, worldSpace=True)
cmds.xform('rockingTransform', query=True, rotation=True, worldSpace=True)

 

I've added the values in MPxTransformationMatrixPreRotation::asMatrix. Should we do extra things in other functions for translation and rotation to be retrieved correctly using xform?

 

0 Likes
Message 17 of 47

chirieacam
Enthusiast
Enthusiast

I've also tested the match transform command and that also doesn't work with pre-translation and pre-scale. It seems that xform and match transform command doesn't read the values from the matrix returned by asMatrix().

0 Likes
Message 18 of 47

brentmc
Autodesk
Autodesk

Hi,

 

Unfortunately, not all the issues with custom transforms are addressed in 2022.3

 

The additional fixes shown below also need to be applied to the rockingTransform example.

 

1) move the preRotation calculations from asMatrix to asRotateMatrix
2) override setRotatePivot

 

MMatrix rockingTransformMatrix::asMatrix() const
{
	return ParentClass::asMatrix();
}

MMatrix	rockingTransformMatrix::asRotateMatrix() const
{
	MMatrix Ro = rotateOrientationValue.asMatrix();
	MMatrix R  = rotationValue.asMatrix();
	MMatrix Rr = preRotation().asMatrix();

	MMatrix Rt;
	Rt[3][0] = rotatePivotTranslationValue.x;
	Rt[3][1] = rotatePivotTranslationValue.y;
	Rt[3][2] = rotatePivotTranslationValue.z;

	MMatrix Rp;
	Rp[3][0] = rotatePivotValue.x;
	Rp[3][1] = rotatePivotValue.y;
	Rp[3][2] = rotatePivotValue.z;

	MMatrix RpInv;
	RpInv[3][0] = -rotatePivotValue.x;
	RpInv[3][1] = -rotatePivotValue.y;
	RpInv[3][2] = -rotatePivotValue.z;

	return (RpInv * Ro * R * Rr * Rp * Rt);
}

MStatus rockingTransformMatrix::setRotatePivot(const MPoint &rotatePivot, MSpace::Space space, bool balance)
{
	MPoint newPivot(rotatePivot);
	if (space != MSpace::kTransform) {
		if (space == MSpace::kPostTransform) {
			newPivot *= asMatrixInverse();
		}
		newPivot *= asScaleMatrix();
	}

	if (balance) {
		MMatrix Ro = rotateOrientationValue.asMatrix();
		MMatrix R = rotationValue.asMatrix();
		MMatrix Rr = preRotation().asMatrix();

		MMatrix Rp;
		Rp[3][0] = newPivot.x;
		Rp[3][1] = newPivot.y;
		Rp[3][2] = newPivot.z;

		MMatrix RpInv;
		RpInv[3][0] = -newPivot.x;
		RpInv[3][1] = -newPivot.y;
		RpInv[3][2] = -newPivot.z;

		MMatrix leftMat = RpInv * Ro * R * Rr * Rp;

		MMatrix mat = leftMat.inverse() * asRotateMatrix();

		rotatePivotTranslationValue[0] = mat[3][0];
		rotatePivotTranslationValue[1] = mat[3][1];
		rotatePivotTranslationValue[2] = mat[3][2];
	}

	rotatePivotValue = newPivot;
	return MS::kSuccess;
}

 

Hope this helps. Note: we are continuing to track issues in this area.

Brent McPherson
Principle Engineer
0 Likes
Message 19 of 47

chirieacam
Enthusiast
Enthusiast

Hi Brent,

 

Thanks for tips, will try those.

 

I also moved the pre-rotation to asRotateMatrix since without it I couldn't move the object correctly in object-space. But, I did it differently, is there anything wrong in doing it like this?

 

 

 

MMatrix CustomTransformationMatrix::asRotateMatrix() const
{
    MMatrix mtx = ParentClass::asRotateMatrix();

    MTransformationMatrix tm(mtx);

    MQuaternion rot = rotateBuffer.asQuaternion();
    tm.addRotationQuaternion(rot.x, rot.y, rot.z, rot.w, MSpace::kTransform);

    return tm.asRotateMatrix();
}

 

 

I'll play around with setRotatePivot and other methods to see if I can get xform to return rotate/translate/scale values for world space. But, even without those, I'm still happy. I can find some workarounds to the the xform issues since I'm not using it directly anyway, but wrapped around.

0 Likes
Message 20 of 47

chirieacam
Enthusiast
Enthusiast

One big question I have is why most of the methods to retrieve translation or rotation receive a MSpace::kTransform instead of MSpace::kWorld when xform is used with worldSpace set to true.

 

Since the jointOrient is basically the preRotation of the transform, is the joint implementation doing anything extra, maybe overriding a specific method when a world rotation is requested, or this is another issue that needs to be fixed internally?

 

Maybe a little more details in what xform does/calls could help me find the right method to override.

0 Likes