Copy, Paste, Flip X a Pose Morph Target?



  • Hi,

    Is there a way I can copy, paste, and then flip X a pose morph target?
    You can see what I am after in the illustration video below:
    https://www.dropbox.com/s/gudjyeojikykbkt/c4d272_methods_for_pose_morph_rmb_menu.mp4?dl=0

    There is a CAMorph.CopyFrom method but I don't see any corresponding paste method or the flip X method.

    Is there a way around this?

    Regards,
    Ben



  • Hi,

    probably not. First of all CMorphTag would be the place to look into, but all this fiddly context menu and special GUI stuff of nodes is usually not accessible through the SDK. There might be message that you could send to that tag node that would tell it to execute that command, but that is highly speculative.

    But if I am understanding the online help correctly, the command is just mirroring that morph target on the x-axis? That should not be too hard to do on your own.

    Cheers,
    zipit



  • @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)
        c4d.EventAdd()
    
        # I hope this helps.
    
    if __name__ == "__main__":
        main()
    

    Cheers,
    zipit



  • Hi @zipit

    Thanks for the response.
    Here is an illustration using your script:
    https://www.dropbox.com/s/j7g9bjusa8l52ee/c4d272_methods_for_pose_morph_rmb_menu02.mp4?dl=0

    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



  • @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



  • 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++)
    {
    	Vector p = padr[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



  • 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



  • @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)
         
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

Log in to reply