Jump to content

Rig controls - nulls and userdata ?


Recommended Posts

In the past I have done some simple character animations where I used nulls or userdata to control the rigged character.

I never took the time to find out how to use nulls AND userdata ... until today.

(Or maybe I already looked for this in the past, and didn't find a satisfactory answer)

 

Say you have a mesh with all the bits and pieces to make up a character, and want it to move or rotate.

You could:

1. create a null, and PSR constrain the mesh to the position/rotation of the null.

2. create some userdata/xpresso to handle the position/rotation. You can then add the userdata to the viewport HUD.

 

In both cases you can manipulate the character from the viewport.

 

However, you cannot create both solutions at once.

Once something is driven by userdata/xpresso you cannot manually adjust it, except via the userdata control.

Or is there a solution for this?

Can you have a null and userdata that drive something, which you can keep in sync, whichever of the two you use?

 

And if not, what makes you decide when to create a null control or a userdata?

 

 

 

Link to comment

Super easy, barely an inconvenience.*

 

You just need to create a little Python tag to store the previous values from both sources, then you compare the new values against the old ones and find what the current source of change was. Then you use that data as driver for the new previous values AND the target. Done!

 

*cough*

 

import c4d
from c4d import utils

def main():
    global PrevObjRotH, PrevObjRotP, PrevObjRotB
    global PrevUDatRotH, PrevUDatRotP, PrevUDatRotB

    obj = op.GetObject()
    target = obj.GetNext()
    ObjRotH = obj[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X]
    ObjRotP = obj[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y]
    ObjRotB = obj[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z]
    UDatRotH = obj[c4d.ID_USERDATA,1]
    UDatRotP = obj[c4d.ID_USERDATA,2]
    UDatRotB = obj[c4d.ID_USERDATA,3]

    if ObjRotH < 0.0:
        times = int(utils.RadToDeg(-ObjRotH)) // 360
        ObjRotH = ObjRotH + utils.DegToRad(360.0 * (times+1))
        print (times)
    if ObjRotH > utils.DegToRad(360.0):
        times = int(utils.RadToDeg(ObjRotH)) // 360
        ObjRotH = ObjRotH - utils.DegToRad(360.0 * times)

    if ObjRotP < 0.0:
        times = int(utils.RadToDeg(-ObjRotP)) // 360
        ObjRotP = ObjRotP + utils.DegToRad(360.0 * (times+1))
        print (times)
    if ObjRotP > utils.DegToRad(360.0):
        times = int(utils.RadToDeg(ObjRotP)) // 360
        ObjRotP = ObjRotP - utils.DegToRad(360.0 * times)

    if ObjRotB < 0.0:
        times = int(utils.RadToDeg(-ObjRotB)) // 360
        ObjRotB = ObjRotB + utils.DegToRad(360.0 * (times+1))
        print (times)
    if ObjRotB > utils.DegToRad(360.0):
        times = int(utils.RadToDeg(ObjRotB)) // 360
        ObjRotB = ObjRotB - utils.DegToRad(360.0 * times)

    print (ObjRotH, ObjRotP, ObjRotB)
    print (UDatRotH, UDatRotP, UDatRotB)
    obj[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X] = ObjRotH
    obj[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y] = ObjRotP
    obj[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z] = ObjRotB
    
    try:
        PrevObjRotH
        PrevObjRotP
        PrevObjRotB
        PrevUDatRotH
        PrevUDatRotP
        PrevUDatRotB
    except:
        print("Generating previous values")
        PrevObjRotH = 0.0
        PrevObjRotP = 0.0
        PrevObjRotB = 0.0
        PrevUDatRotH = 0.0
        PrevUDatRotP = 0.0
        PrevUDatRotB = 0.0

    if ObjRotH != PrevObjRotH or ObjRotP != PrevObjRotP or ObjRotB != PrevObjRotB:    
        print("Null object has changed")
        obj[c4d.ID_USERDATA,1] = ObjRotH
        obj[c4d.ID_USERDATA,2] = ObjRotP
        obj[c4d.ID_USERDATA,3] = ObjRotB
        PrevObjRotH = ObjRotH
        PrevObjRotP = ObjRotP
        PrevObjRotB = ObjRotB
        PrevUDatRotH = ObjRotH
        PrevUDatRotP = ObjRotP
        PrevUDatRotB = ObjRotB
    elif UDatRotH != PrevUDatRotH or UDatRotP != PrevUDatRotP or UDatRotB != PrevUDatRotB:    
        print("User data has changed")
        obj[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X] = UDatRotH
        obj[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y] = UDatRotP
        obj[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z] = UDatRotB
        PrevObjRotH = UDatRotH
        PrevObjRotP = UDatRotP
        PrevObjRotB = UDatRotB
        PrevUDatRotH = UDatRotH
        PrevUDatRotP = UDatRotP
        PrevUDatRotB = UDatRotB
        
    target[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_X] = ObjRotH
    target[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Y] = ObjRotP
    target[c4d.ID_BASEOBJECT_REL_ROTATION,c4d.VECTOR_Z] = ObjRotB
    c4d.EventAdd()

 

This tag uses a specific scene - tag is on the null, the null contains the user data too, the user data covers rotation HPB in elements 1,2,3, the target object is directly the next object. But you can, of course, use any other hierarchy for the source, I just went with the easiest.

 

Okay, maybe not totally the easiest since the rotation has to map to a 0-360 range, but that's just a small bonus.

 

 

* blatantly stolen from YouTube's ScreenRant

TwinControl.c4d

Link to comment
1 hour ago, Cairyn said:

Super easy ...

 

Completely forgot about the Python Tag, and what it is capable of.

Thanks a lot for sharing.

Link to comment

Still wondering about a null control over a userdata.

Example:

I have a single-mesh snail like character and use a bend deformer to manipulate the neck and head.

The bend deformer has a strength attribute, which allows to perform a tilt of the head up-down (nodding yes), and an angle attribute which allows me to tilt the head sideways.

 

Now, I could create null controls to steer de strength, and another one for the angle. Or I could simply create 2 userdata sliders and add them to the HUD.

When to prefer using the one over the other solution? Is this purely user preference? Or is there a philosophy behind when to use what?

Link to comment

you could just nest 2 nulls. one is controller with a controller, the other with sliders. 

 

they won't sync up. but it lets you use whichever one you prefer in the viewport. 

 

But yeah, end of the day its user preference. do you prefer to pose things like a puppet? or do you prefer just the numbers and animate with maths?

 

i feel like syncing up controls is gonna give you head aches when it comes to keyframing stuff.. 

Link to comment
  • Recently Browsing   0 members

    • No registered users viewing this page.
  • LATEST ACTIVITIES

    1. 7

      Draw primitives on surface of existing objects?

    2. 0

      Nodes Modifier result isn't updated anymore

    3. 220

      Scene Nodes | Capsules file pit

    4. 3

      Flipped clones in multi-segments curve

    5. 2

      How to create continuous UV texture for irregular wall like shapes ?

×
×
  • Create New...

Copyright Core 4D © 2023 Powered by Invision Community