# Solved Copy, Paste, Flip X a Pose Morph Target?

@zipit
Thanks for the response.

RE: CMorphTag
Unfortunately, that class doesn't always have the appropriate methods.

RE: the command is just mirroring that morph target on the x-axis?
Yes, it does. But the trick is the point order must not change.
If I just simply flip x-axis (i.e. such as scaling in negative -1 x), it will not work as expected.

Hm,

I have no clue about the whole rigging stuff, which might be a source of confusion for me, and I also am a bit confused about what you are referring to with point order, since manipulating the matrix of an object does not change anything about the order of its points. But this might be insightful to you:

import c4d

def main():
"""
"""
# This function requires you to select an editable polygon object.
if not isinstance(op, c4d.PolygonObject):
raise TypeError("Blah")

# This is probably what you meant with flipping the x-axis, right? And
# with point order you mean the point order in the polygons, i.e. the
# fact the textures are not being mirrored, right ?
clone = op.GetClone(c4d.COPYFLAGS_NONE)
# Invert the x-component of the frame of the object.
mg = clone.GetMg()
mg.v1 *= -1
clone.SetMg(mg)

clone.SetName("clone_frame_manipulation")
doc.InsertObject(clone)

# To do what you want, you could do this instead. You could also write
# the UVWs, manipulate the frame (matrix) of the object and then
# manipulate the polygons, there are many ways to do this. I went for
# manipulating the points and polygons, because UVWs are kind of a
# hassle to access, and matrices with negative scalings can cause all
# sorts of problems.

clone = op.GetClone(c4d.COPYFLAGS_NONE)
# First we reflect each point individually and write that data back.
transform = c4d.Matrix()
transform.v1 *= -1
points = [p * transform for p in clone.GetAllPoints()]
clone.SetAllPoints(points)

# But now we have the problem that all our normals have been inverted. We
# could use SendModellingCommand to rectify this, or we could just do
# it manually like I have done it here.
for index, cpoly in enumerate(clone.GetAllPolygons()):
new_poly = (c4d.CPolygon(cpoly.c, cpoly.b, cpoly.a, cpoly.a)
if cpoly.IsTriangle() else
c4d.CPolygon(cpoly.d, cpoly.c, cpoly.b, cpoly.a))
clone.SetPolygon(index, new_poly)
clone.Message(c4d.MSG_UPDATE)

clone.SetName("clone_point_manipulation")
doc.InsertObject(clone)

# I hope this helps.

if __name__ == "__main__":
main()

Cheers,
zipit

MAXON SDK Specialist
developers.maxon.net

Hi @zipit

Thanks for the response.
Here is an illustration using your script:

As you can see, when I select the point on my original mesh, the point order should 3 but when I click on the other cubes it is 1.

This is what I meant by a change of point order.
For the morph to work seamlessly, the point order should remain the same.

Hi,

okay, now I do understand and it makes sense that this is required, but unfortunately that is not that easy to do, because it would require you to find the topological symmetry of that mesh. Does that pose morph reflect thingy also work with topological non-symmetric meshes? I cannot give you an easy solution, but maybe I am overlooking something and someone else can point that out?

Cheers,
zipit

MAXON SDK Specialist
developers.maxon.net

@zipit

RE: Does that pose morph reflect thingy also work with topological non-symmetric meshes?
I haven't actually tried it with non-symmetric meshes since all my meshes are symmetrical if I'm mirroring/flipping. I just create a manual version of it.

But anyway, with a brief test with a cube, no it doesn't work on the non-symmetric mesh.

RE: because it would require you to find the topological symmetry of that mesh
Yes, unfortunately, this is a tricky part for me :(
That's why I want to leverage pose morph tag flip command.

Hi,

RE: because it would require you to find the topological symmetry of that mesh
Yes, unfortunately, this is a tricky part for me :(

not just for you, depending on how arbitrary you want this to be, self-similarity or similarity measurements in general can get extremely complex.

But I just thought of an relatively easy hack. If you have a base mesh that is not only topologically, but also axis symmetrical (i.e. vertex symmetrical), you could use that as a cheat sheet so to speak. The idea would be to compute the reflected point for each point in the mesh and then find the point that has these coordinates and store an index tuple of these two matching points. Do this until all points are consumed. In your "reflected" mesh you would then just copy over the points from such a point tuple.

This should work if I am not overlooking something. One downside would be that this tuple-finding without an effective geometry access data structure like for example some kind of BSP-Tree would be rather slow on big meshes (we do not have Cinema's KD-Tree in Python unfortunately), since you would have to go over all remaining points for each point. The other downside would be that this would not work on anything that is topologically symmetrical, but not not axis symmetrical (a character with an raised eyebrow modelled into the base mesh for example).

Cheers,
zipit

MAXON SDK Specialist
developers.maxon.net

hi,

the commands are not exposed. But you can probably do it yourself.

@zipit said in Copy, Paste, Flip X a Pose Morph Target?:

since you would have to go over all remaining points for each point.

For what i see in the code, that's the idea. At least for points. The code is doing a lot of stuff because there's a lot of cases in this tag. But one part of the code is the following.

for (i = 0; i < pcnt; i++)
{

switch (axis)
{
case 0: p.x = -p.x; break;
case 1: p.y = -p.y; break;
case 2: p.z = -p.z; break;
}

Int32 fnd = NOTOK;
Float mxdst = MAXRANGE;

for (l = 0; l < pcnt; l++)
{
Float dst = (padr[l] - p).GetSquaredLength();
if (dst < mxdst)
{
mxdst = dst;
fnd = l;
}
}

pmap[i] = fnd;
}

You can see there's the double loop for each points. padr and pcnt are pointer to the base morph.
Just to be sure you know that "trick" the GetSquaredLength is not doing more computation, that the opposite. You need a square root to go from SquaredLenght to Lenght. (Pythagore)
So comparing squaredLenght is faster.

Cheers,
Manuel

MAXON SDK Specialist

MAXON Registered Developer

Thanks for the response

@zipit

RE: compute the reflected point for each point in the mesh and then find the point that has these coordinates
Yea, although computationally taxing, I think this is feasible since most of the meshes I plan to flip (using code) are only the face geo (rather than the whole body).

RE: not work on anything that is topologically symmetrical, but not not axis symmetrical
No worries. I'd expect the mesh to be topologically and axis symmetrical

I'll probably post the working code on a separate thread as it not only limited to pose morph target

@m_magalhaes
Thanks for the code, although I have a problem reading the C++.
I'll probably post the working code on a separate thread as it not only limited to pose morph target.

=============

That said, do you have thoughts on how to do the Copy and Paste function in the pose morph target?
I couldn't find the corresponding command in the manual (even in C++)
https://developers.maxon.net/docs/Cinema4DCPPSDK/html/page_manual_camorphnode.html#page_manual_camorphnode_data_parameters

https://developers.maxon.net/docs/Cinema4DCPPSDK/html/page_manual_caposemorphtag.html#page_manual_caposemorphtag_morphs

Hi,

about the time complexity of such algorithm: I only mentioned this, because I was justifying my proposal. Although it is technically an algorithm of high complexity, you should be totally fine on normal use case meshes of characters, which are intended for SDS, i.e. something with less than 100,000 vertices or so. Modern computers are fast ;) As you can see in MAXON's code, they did not utilise any optimisation whatsoever either; there are just two loops sitting on top of each other (they do not even remove the already found points from their comparison array, which puts their code's complexity squarely into exponential territory). But Python is quite a bit slower than C++, so it might be advisable to employ some kind of optimisation in Python. You could could for example use some Dynamic Programming to only compute each point pair distance once. But then again: Modern computers are very fast, you are probably fine with brute forcing yourself to success ;)

Cheers,
zipit

MAXON SDK Specialist
developers.maxon.net

@m_magalhaes @zipit

I managed to flip the mesh (not necessarily a pose morph target since I'd have to know the API).
Anyhow, here is a demo of it working:
https://www.dropbox.com/s/bh4p26s4m9qwljw/c4d272_flip_miror_mesh.mp4?dl=0

Here is wip script. It only works if the x-axis is dead set on 0.
Also, it is slow since it has to loop within a loop.

import c4d
from c4d import gui

# Main function
def main():
neutral_geo = doc.SearchObject('neutral_geo')
posed_geo = doc.SearchObject('posed_geo')
neutral_world_matrix = neutral_geo.GetMg()
posed_world_matrix = posed_geo.GetMg()

neut_lcl_pnts = neutral_geo.GetAllPoints()
neut_gbl_pnts = [point * neutral_world_matrix for point in neut_lcl_pnts]

posed_lcl_pnts = posed_geo.GetAllPoints()
posed_gbl_pnts = [point * posed_world_matrix for point in posed_lcl_pnts]

match_pnts = []
left_pnts = []
right_pnts = []

for idx, point in enumerate(neut_gbl_pnts):
if point[0] == 0.0: # ignore points at the world x axis
continue

if point[0] > 0.0:
left_pnts.append((idx,point))

if point[0] < 0.0:
right_pnts.append((idx,point))

for left in left_pnts:
for right in right_pnts:
if left[1][1] == right[1][1]: # check if Y pos match
if left[1][2] == right[1][2]: # check if Z pos match
if left[1][0] == -1 * (right[1][0]):# check if X pos are inverse
match_pnts.append((left[0],right[0]))

for pnt in match_pnts:

reversed_left = posed_lcl_pnts[pnt[1]]
reversed_left[0] = reversed_left[0] * -1
reversed_right = posed_lcl_pnts[pnt[0]]
reversed_right[0] = reversed_right[0] * -1

posed_geo.SetPoint(pnt[0], reversed_left)
posed_geo.SetPoint(pnt[1], reversed_right)

posed_geo.Message(c4d.MSG_UPDATE)