SetAllPoints() Not Working On An Object With Skin/Bind



  • Hi,

    I have a mesh that needs a corrective pose.
    Rather than creating it within C4D, I export an uncorrected pose.
    And received a corrected pose, from the modeler.

    I now have the task to convert the corrected pose to a corrected shape.

    Here is my planned workflow:

    1. Store points of the corrected pose
    2. Go to the uncorrected pose. Add a posemorph, while in edit mode
    3. Restore points from the corrected pose to the uncorrected pose.

    It works as you can see here:
    https://www.dropbox.com/s/fkqndj892z3jlt2/c4d276_python_setallpoints_on_skin01.mp4?dl=0

    Both mesh should match 1 to 1. But not where the mesh is with Posemorph and Skin:
    https://www.dropbox.com/s/qufqggqo295atuo/c4d276_python_setallpoints_on_skin02.mp4?dl=0

    You can check the illustration file here: (Animated from 0 to 10)
    https://www.dropbox.com/s/5s9xlgujfw5ga42/c4d276_python_setallpoints_on_skin.c4d?dl=0

    Here is the working script so far:

    import c4d
    from c4d import gui
    
    # Select Corrective Pose
    # Select Base Pose
    # Execute Script
    
    def main():
        doc = c4d.documents.GetActiveDocument()
        obj_sel = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER)
        driver = obj_sel[0]
        driven = obj_sel[1]
    
        driven_world_matrix = driven.GetMg()
        driver_world_matrix = driver.GetMg()
    
        driver_points = [point * driver_world_matrix for point in driver.GetAllPoints()]
        new_points = [point * ~driven_world_matrix for point in driver_points ]
    
        driven.SetAllPoints(new_points)
        driven.Message (c4d.MSG_UPDATE)
    
    
        print driver_points
        print new_points
    
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    


  • Hi @bentraje I'm really not sure to understand correctly the workflow and your expected result.

    But just in case you seem to assume that pose morph create a kind of link to your corrected object, which is not true when you create a Relative pos, it copies the Point/Polygon information into the pose morph internal Data.
    So even in your scene if you manually modify the corrective_geo you can see that the change is not replicated into the stored corrective pose. So if you want to modify this result you have to modify this stored pose.
    To do so I let you read Pose Morph Tag which demonstrates how to do it.

    Here is your workflow more in detail if I understood correctly (but which is not really refelted by your code).

    1. You have a Cube (edited) that is rigged and skinned.
    2. You duplicate it, rename it Cube_Correct.
    3. Move Vertex 1 of Cube_Correct.
    4. Create a posemorph on Cube, and add a Relative Pose with Cube_Correct.
    5. You received a new Cube_Correct with Vertex 2 moved.
    6. Execute your script? To update the pose on the posemorph?

    So my question is does I understand your workflow right? If not feel free to explain it a bit more and tell us the whole context where your script come in action and what is the expected result on the actual scene.
    And if you have question relative to how the posemorph work, feel free to ask too, but I prefer to get the workflow right before starting a long discussion that may be useless.

    Thanks in advance,
    Cheers,
    Maxime.



  • @m_adam

    Thanks for your response. Apologies for the confusion.

    I think in general term (or in Maya term). I need to "extract the deltas".
    Anyhow, here is the video in Maya showing the same workflow I am trying to recreate in C4D.
    https://www.dropbox.com/s/z5zi4f9tpvifr1a/c4d276_python_setallpoints_on_skin03_in_maya.mp4?dl=0

    As you can see, it perfectly fits to the corrective shape. This creates the necessary delta mesh when basemesh is at neutral pose.

    Also as you can remember in video above, in C4D demonstration without skin/pose morph, it works as expected. Only with skin/pose morph, it doesn't.

    So I tried the link you mentioned and your instruction So if you want to modify this result you have to modify this stored pose. with this revised workflow:

    1. Store points of the corrected pose
    2. Go to the uncorrected pose. Add a posemorph, while in edit mode
    3. Executemorph.SetMode(doc, morph_tag, c4d.CAMORPH_MODE_FLAGS_ALL | c4d.CAMORPH_MODE_FLAGS_EXPAND, c4d.CAMORPH_MODE_REL) # to export the points
    4. Restore points from the corrected pose to the uncorrected pos
    5. Executemorph.SetMode(doc, morph_tag, c4d.CAMORPH_MODE_FLAGS_ALL | c4d.CAMORPH_MODE_FLAGS_COLLAPSE, c4d.CAMORPH_MODE_AUTO)

    Unfortunately, the result is still the same as before.
    Is there a way around this?
    (See Maya video for reference and the actual output in the link above).

    P.S Regarding your workflow (7 of them). I tried reperforming them.
    The result is still the same in my first post.

    Here is the WIP code so far:

    import c4d
    from c4d import gui
    
    # Select Corrective Pose
    # Select Base Pose
    # Execute Script
    
    def main():
        doc = c4d.documents.GetActiveDocument()
        obj_sel = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER)
        driver = doc.SearchObject('corrective_geo')
        driven = doc.SearchObject('base_geo')
        morph_tag = driven.GetTag(1024237)
        morph = morph_tag.GetMorph(1)
        print morph.GetName()
    
        driven_world_matrix = driven.GetMg()
        driver_world_matrix = driver.GetMg()
    
        driver_points = [point * driver_world_matrix for point in driver.GetAllPoints()]
        new_points = [point * ~driven_world_matrix for point in driver_points ]
        
        morph.SetMode(doc, morph_tag, c4d.CAMORPH_MODE_FLAGS_ALL | c4d.CAMORPH_MODE_FLAGS_EXPAND, c4d.CAMORPH_MODE_REL)
    
        driven.SetAllPoints(new_points)
        driven.Message (c4d.MSG_UPDATE)
            
        morph.SetMode(doc, morph_tag, c4d.CAMORPH_MODE_FLAGS_ALL | c4d.CAMORPH_MODE_FLAGS_COLLAPSE, c4d.CAMORPH_MODE_AUTO)
    
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    


  • Hi, I'm sorry but after re-reading for the 10thtime your topic I still don't get what you want to do.
    Making it impossible for me to help you currently.

    Can you try to explain step by step your workflow? And the action you do within Cinema 4D.
    And the most important explain the expected result within Cinema 4D.
    Maybe you can link another C4D file based on the previous one. But in the final state expected after your script execution.

    Cheers,
    Maxime.



  • @m_adam
    RE: But after re-reading for the 10thtime your topic
    Apologies for the inconvenience.

    Here is a better illustration of the problem:
    https://www.dropbox.com/s/kuaxt8audtcafus/c4d276_python_setallpoints_on_skin04.jpg?dl=0

    Here is the code so far:

    import c4d
    from c4d import gui
    
    # Main function
    def main():
        obj = doc.SearchObject('base_geo')
        should_be_vector_world = c4d.Vector(3.877,2.358, -1.663)
        should_be_vector_local = should_be_vector_world * ~obj.GetMg() # Convert to Local
        obj.SetPoint(44, should_be_vector_local)
    
        obj.Message (c4d.MSG_UPDATE)
        
        c4d.EventAdd()
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    P.S. Instead of explaining the workflow (which might be more confusing since blendshape/morph delta extraction is a bit niche), I made the question in its simplest form .

    Hope that helps.

    Let me know if you need more clarification.



  • Then the question is do you want this point to be at 3.877, 2.358, -1.663 all-time (so even without the morph) or only when the morph is applied?



  • Hi @m_adam,

    Only when the morph is applied and to be more specific with Pose Deformers turned off.
    It is turned off by default, but just in case.



  • Hi, unfortunately, I see no way of doing what you are looking for without enabling the Post Deformers option.

    Here a working script (you need to check the Post deformer)

    import c4d
    
    def GetPointDelta(baseGeo, correctiveGeo):
        baseGeoDeformed = baseGeo.GetDeformCache()
        if baseGeoDeformed is None:
            baseGeoDeformed = baseGeo.GetCache()
            
        if baseGeoDeformed is None:
            raise RuntimeError("Unable to find the deformed cache")
        
        if baseGeoDeformed.GetPointCount() != correctiveGeo.GetPointCount():
            raise ValueError("Corrective Pose and base Geo dont have the same point count")
        
        return [ptCorrective - ptBase for (ptBase, ptCorrective) in zip(baseGeoDeformed.GetAllPoints(), correctiveGeo.GetAllPoints())]
    
    def main():
        # Get obj
        baseGeo = doc.SearchObject("base_geo")
        correctiveGeo = doc.SearchObject("corrective_geo")
        if None in [baseGeo, correctiveGeo]:
            raise RuntimeError("Failed to found objects")
        
        ptsDelta = GetPointDelta(baseGeo, correctiveGeo)
        
        morphTag = baseGeo.GetTag(c4d.Tposemorph)
        if morphTag is None:
            raise RuntimeError("Unable to find the morph tag")
    
        morphTag.ExitEdit(doc, True)
    
        # Get the base pose
        basePose = morphTag.GetActiveMorph()
        if basePose is None:
            raise RuntimeError("Unable to find the base pose")
        
        # Expand to absolute position (absolute are still in local space of the object)
        basePose.SetMode(doc, morphTag, c4d.CAMORPH_MODE_FLAGS_ALL | c4d.CAMORPH_MODE_FLAGS_EXPAND, c4d.CAMORPH_MODE_REL)
        
        baseMorphNode = basePose.GetFirst()
        for ptId in range(baseMorphNode.GetPointCount()):
            pos = ptsDelta[ptId]
            baseMorphNode.SetPoint(ptId, pos)
        
        # Collapse all again
        basePose.SetMode(doc, morphTag, c4d.CAMORPH_MODE_FLAGS_ALL | c4d.CAMORPH_MODE_FLAGS_COLLAPSE, c4d.CAMORPH_MODE_AUTO)
        
        # Update the morph
        morphTag.UpdateMorphs()
        c4d.EventAdd()
        print("update")
    
    # Execute main()
    if __name__=='__main__':
        main()
    

    But why not using a Correctional PSD morph this is exactly the purpose of them. Providing correctional pose given some Joint Orientation (be sure to be in the mode "in place" before calling SetAllPoint).

    Cheers,
    Maxime.



  • @m_adam

    Thanks for the response.

    RE: without enabling the Post Deformers option.
    It seems like so. I initially thought I could reverse engineer it by getting the point's skin influence (its joint and its weight) and in a way factor it with the intended world vector. However, I gave up since I realize point can have several joints and several weight. Haha

    Don't get me wrong, enabling the post deformer works as expected.
    The problem comes because of its inherent nature
    "calculate the points position of the morph after they are deformed by a Deformer."

    It presupposes that the (near) last deformation comes from the pose morph.
    For example, Squash/Stretch>Bulge>Bend>Twist>Pose Morph.
    However, there are cases where the intended deformation is
    Pose Morph>Squash/Stretch>Bulge>Bend>Twist.

    So having the post deformer off while still getting the world position desired gives a bit of flexibility.

    Anyhow, thanks again for the help.
    I'll just use the post deformer for now and see where I get stuck.

    Have a great day ahead!


Log in to reply