SOLVED Aligning object local axis to world axis via Python

Hi there,

I've got a question that I can't seem to solve myself and neither is Google being of much help.

I've got an object of which it's local axis are not aligned with the world axis.
In this case:
The y of the object is on the x of the scene
The x of the object is on the -z of the scene
The z of the object is on the -y of the scene

I'm trying to build a custom bounding box for an internal tool and as you can imagine, reading of GetRad along completely wrong axis will give me an incorrect bounding box.

So I will need to align the axis of the object to the world axis first and then do my GetRad.

Normally as human, one would rotate the pivot point of the object to align with the world, however I would like to do this in code, as this will be part of a recursive loop going through a list of objects.

I've tried fiddling around with the matrices but this did not result in anything.

Kind regards,

@ferdinand : Thank you for the pointers to the articles, and I fully understand that you do not have the time to explain the more complex parts of the Matrix concepts.
I will read the material you have provided and otherwise search for info on Linear Algebra to get a better undertstanding.

I think I understand it a bit better now, basically those 3 numbers per axis (X,Y,Z) basically tell you along which world axis they lie.
So the main columns are X,Y,Z and then each row is technically X,Y,Z as well.

X Y Z
X X X
Y Y Y
Z Z Z

With the help of Cairyn I've figured out that the issue is with the transforms of parents and grandparents, so I will rewrite my code to remove all of those as well and this should hopefully fix the issue.

Thank you very much for your help and should I run into further issues on this, I will post again in this thread!

Kind regards,

Hello @peek,

Thank you for reaching out to us. I am not quite sure if I do understand your question correctly. Going by your title and the sentence,

Normally as human, one would rotate the pivot point of the object to align with the world, however I would like to do this in code, as this will be part of a recursive loop going through a list of objects.

I would assume that you have some input object obj with a transform T. You now want the orientation of T to be the same as the world frame (axis) but maintain the offset of T. Or in other words and from the user perspective: You want to zero out all rotation angles, but you want to preserve the position.

The world frame is the identity matrix in Cinema 4D as in most other applications. The identity matrix is constructed with the default constructor, i.e., identityMatrix = c4d.Matrix(). You then must overwrite the offset component of that transform by either writing later to it or by passing it into the constructor: worldFrameWithCustomOffset= c4d.Matrix(off=c4d.Vector(1, 2, 3). Some of the technical terms I have used here are explained in the Python Matrix Manual. It will also show you how to construct matrices for more complex cases than the world frame.

Find below a simple code snippet which demonstrates what I think is your task.

Cheers,
Ferdinand

Code Example:

"""Sets the orientation of the active object to the orientation of the world frame.

To learn more about matrices, you should read the Matrix Manual [1] in our Python documentation. It
will explain some of the math and concepts behind (linear) transforms, i.e., what is done with
`c4d.Matrix` in the Python API.

References:
    [1] https://developers.maxon.net/docs/Cinema4DPythonSDK/html/manuals/data_algorithms/classic_api/matrix.html
"""

import c4d

def main():
    """Sets the orientation of the active object to the orientation of the world frame.
    """
    # op is predefined in a script as the active object.
    if not isinstance(op, c4d.BaseObject):
        raise RuntimeError("Please select an object.")

    # Get the global matrix of the active object, i.e., its transform in the coordinate system of
    # the world/scene.
    objectWorldTransform = op.GetMg()

    # The world frame, i.e., the coordinate system you see as an axis in the view port is the 
    # identity matrix, which can be instantiated with the default matrix constructor.
    worldTransform = c4d.Matrix()

    # Since you want to preserve the orientation of the world frame, but likely not its offset,
    # you must overwrite the offset of that world frame with the offset of your object.
    newTransform = c4d.Matrix(off=objectWorldTransform.off)

    # And then set the new global matrix of the object.
    op.SetMg(newTransform)

    # Push an update event to Cinema 4D so that the viewport does update. When you do this in a
    # loop, you only should do this once after the loop and shortly before your script/plugin
    # ends its operation.
    c4d.EventAdd()


# Execute main()
if __name__=='__main__':
    main()

@ferdinand : Thanks a lot for replying in such an extensive manner, that code was really helpful in it's own way but for other purposes.

Your code actually rotates and moves my object, which I do not want. I want it to remain in exactly the same location and orientation but I want it's axis system to match up to that of the world.

It seems I wasn't detailed enough in my initial question:
The object has no rotational transforms (all are 0) but the Pivot ("Enable Axis" button) is incorrectly oriented. The objects' local coordinate system does not match up with the worlds' coordinate system in terms of orientation (X-axis, Y-axis, Z-axis).

Normally, as a human, one would press the "Enable Axis" button and manually rotate the objects'' Pivot point to match the orientation of the world axis. So that Y is up, etc.

How would one do this in Python code?

Currently the Y axis of the object is on the worlds' X axis, so if I call the GetRad() function, the width of the object is actually put in the Y component of the c4d.Vector, not in the X where it should belong.

Edit: I have read up on the matrix manual but it's confusing to me, but that is a different subject.

Kind regards,

@peek said in Aligning object local axis to world axis via Python:

The object has no rotational transforms (all are 0) but the Pivot ("Enable Axis" button) is incorrectly oriented. The objects' local coordinate system does not match up with the worlds' coordinate system in terms of orientation (X-axis, Y-axis, Z-axis).

That sounds a bit fishy to me because with no transformations, the object's axis system should be the world's. Either your object has a frozen transformation (check the Coord tab), or a parent of the object performs the transformation so the object is not directly an element of the World (top level object).

Assuming you have some rotated parent: Then the object exists in the space defined by that parent's transformation. If you want to re-transform the object into world space, its matrix needs to revert the parent's transformation.

I'd say this is easy with Ferdinand's code, except -- the object's points are defined as vectors within the object's space. So if you change the object's matrix, all points will follow suit (and all children of the object, too). The thing you need to do to keep the "object" in the same place (actually, the object's points and children) is to apply the original relative transformation to all points and children again.

Note that you cannot do this with parametric objects, since these are created around their origin.

There are plenty code examples for this kind of thing on the forum already, I'm sure Ferdinand has some links at hand.

The parent doesn't have the transforms but the grandparent indeed does, which seems to be the issue indeed.

I've already got my code to move the points around to the old relative transformations so I'll re-use that one for these purposes.
https://plugincafe.maxon.net/topic/13840/object-size-not-updated-after-points-move

The Subdivision Surface object two up has rotation on it, below that is a null with only a scale of 1.3 and inside that null is my object.
So if I store the points in my object and then reset the rotation of the SubS and scale of the null, and then put them back, all should be good.

I'm making an in-house turntable tool to isolate out objects in complex scenes and I get to see all kinds of weird modelling actions from team members and externals that I need to catch and correct 🐶 This is one of them.

But say I wanted to rotate the pivot point to offset parent(s) transformations, how would I go about doing that in code?

Hello @peek,

thank you for clarifying your question. I (think I) get what you want to do - you want to move the axis of an object - but @cairyn is right when he says this here:

@cairyn said in Aligning object local axis to world axis via Python:

@peek said in Aligning object local axis to world axis via Python:

The object has no rotational transforms (all are 0) but the Pivot ("Enable Axis" button) is incorrectly oriented. The objects' local coordinate system does not match up with the worlds' coordinate system in terms of orientation (X-axis, Y-axis, Z-axis).

That sounds a bit fishy to me because ...

If you are using the correct terminology or not does not really matter in the end, as word are just words. But it is important that you do understand the concepts behind all that because it will otherwise be hard for you to write efficient code. A (linear) transform is just another word for a matrix (or more precisely - a matrix is one way to express a linear transform). A transform encodes the orientation, scale, skew and in many cases also the offset (as in Cinema 4D) of an entity in an enclosing coordinate system. A transform, more specifically the basis of the transform, is also a coordinate system itself. In case of Cinema 4D this means that all objects in a scene live in the world coordinate system (ignoring local matrices for now) and provide a coordinate system for the vertices which are governed by them.

  • So, when your object has no rotational values, then its axis orientation must be aligned with orientation of the world axis.
  • The only possibility how your statement can be true, is when you object has a freeze transform. A freeze transform is a virtual null object, as it can be used to zero out values of an object in the coordinate manger although they are not really null. This is also explained in the Matrix Manual.

I would recommend again to read the Python Matrix Manual, as while it is certainly not perfect, it will give you the most important concepts of a transform and how the math behind them works and how they relate to objects.

About your implicit question of moving the axis of a PointObject. There is no functionality in our API which would do that for you, as the axis gizmo is just a virtual concept for users. There is no such thing as a moveable axis of an object in the API, everything is encoded by matrices. So, when you want to rotate or translate and object while holding its axis gizmo in place, you must move its vertices instead.This topic has been covered with examples for Python in How to Center the Axis of Objects and CAD Normal Tag flipped after Polyon merge (join) and axis repositioning - how realign? among other topics in the past. Note that while the operation itself might be trivial (you just transform the vertices by the inverse of where you want to move the axis), keeping a whole scene in check can be not. The second posting (the cad thingy) discusses this on the example of normal tags, but you can also run into problems with other data as for example rigging setups when you transform the vertices inside an PointObject.

Cheers,
Ferdinand

@ferdinand Thanks again for this extensive explanation, I will be sure to keep this at hand for reference.

I do indeed grasp the concepts of multiple layers of coordinate systems but my issue specifically with the Matrix manual is that when I look at for instance the second image, with the nice colored columns for each component (Or so it would seem to me).

Then when I see the 3rd image I see the number "1" jump colums which is the part that makes it confusing to me. I believe in the dabble I had with matrices that default "1" is the scale but I don't grasp why it doesn't just stay in one of the colored columns.

Another time when I read out the matrix of an object that had rotations on it, the matrix showed everything 0 apart from those 3x default 1 jumping columns.
So it appeared the matrix did not have any rotational values stored.
Again, this confused me.

To be open and honest here, I'm a visual artist that has started to do coding to make internal company tools and while I have an ok understanding of math some things like this I can't yet visualise for myself so I don't fully understand it yet.

Kind regards,
Joep

Hello @peek/Joep,

let us split here the thing into two parts:

  • It is important to understand the concept of matrices encoding transforms. This is not a particular complicated thing once you get it. I know that this stuff looks intimidating as most people have nothing to do with matrices in their daily work, but understanding this concept will make you much more flexible in what you do as a (technical) artist, not only in Python, but also in our scene nodes and even other products. But this is more a tip from me.
  • Your problem at hand: I am still not 100% sure what you want to do. If it is moving an axis, the topics How to Center the Axis of Objects and CAD Normal Tag flipped after Polygon merge (join) and axis repositioning - how realign? will likely give you a good starting point, as I did provide there commented code.

When this still does not solve your problem at hand, I would ask you to provide a scene file for your problem, as providing such usually solves these "explaining" problems the fastest (images also work).

Cheers,
Ferdinand

Then when I see the 3rd image I see the number "1" jump colums which is the part that makes it confusing to me. I believe in the dabble I had with matrices that default "1" is the scale but I don't grasp why it doesn't just stay in one of the colored columns.

With 3rd image I assume you mean this here?

This is the identity matrix I have talked about in my first posting, it encodes the world coordinate system, it is what you get when construct a matrix without any arguments. The stuff in red is the x-axis (1, 0, 0), green the y-axis (0, 1, 0), and blue the z-axis (0, 0, 1), these three vectors (red, green, and blue) are what form the basis of the transform. They are literally three axis.

bd55c9e3-96c8-430d-9cce-4eafd7a0562b-image.png

Another time when I read out the matrix of an object that had rotations on it, the matrix showed everything 0 apart from those 3x default 1 jumping columns.
So it appeared the matrix did not have any rotational values stored.
Again, this confused me.

When you object is not aligned with the world coordinate system, this is perfectly normal. So, when for example the x-axis of your object is aligned with the y-axis of the world axis (and the object z-axis is still aligned with the world z-axis), then the matrix will be

Matrix(v1: (0, 1, 0); v2: (-1, 0, 0); v3: (0, 0, 1); off: (0, 0, 0))

v1, the first component of the basis, i.e., the x-axis is now equal to the world y-axis (0, 1, 0). The same aplies to the y-axis of the object, which now has become equal to the inverse of the world x-axis (-1, 0, ,0)

9c54b579-e049-4a43-aa52-b5af8a286842-image.png

To be open and honest here, I'm a visual artist that has started to do coding to make internal company tools and while I have an ok understanding of math some things like this, I can't yet visualize for myself, so I don't fully understand it yet.

We understand that, and there is no rush from our side for you to come up to speed. We however do not prioritize explaining such fundamental concepts in the SDK Team here, as this is a boundless topic, as there is such much math and computer science concepts to explain to explain all the general concepts behind our APIs. For now, this reduced form of the Matrix manual must suffice. If you follow one of the educational courses on linear algebra, they will usually also provide a geometric explanation of the math (i.e., something visual).

Cheers,
Ferdinand

@ferdinand : Thank you for the pointers to the articles, and I fully understand that you do not have the time to explain the more complex parts of the Matrix concepts.
I will read the material you have provided and otherwise search for info on Linear Algebra to get a better undertstanding.

I think I understand it a bit better now, basically those 3 numbers per axis (X,Y,Z) basically tell you along which world axis they lie.
So the main columns are X,Y,Z and then each row is technically X,Y,Z as well.

X Y Z
X X X
Y Y Y
Z Z Z

With the help of Cairyn I've figured out that the issue is with the transforms of parents and grandparents, so I will rewrite my code to remove all of those as well and this should hopefully fix the issue.

Thank you very much for your help and should I run into further issues on this, I will post again in this thread!

Kind regards,

Hello @Peek,

without any further questions and other postings, we will consider this topic as solved and flag it as such by Friday, 17/06/2022.

Thank you for your understanding,
Ferdinand