Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login
    The forum rollback caused push notifications and recent user data to malfunction. The problem will fix itself naturally within the next days. See the topic Broken Push Notifications for a more in detail explanation. You can fix this yourself by forcibly clearing your browser cache (for most browsers: CTRL + F5).

    SetTime() and document updating

    Cinema 4D SDK
    python r21
    3
    12
    2.0k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • B
      brucek5
      last edited by

      I've created two Python scripts (tag based) that transfer animation from one character to another. The first one simply copies the source character joint rotation keyframes to the target character joints. This script works fine.

      The second script is a bit different. Here the source character is setup with IK chains and goals. I first perform my animation using the goals plus some xPresso that also moves the joints. In this case, the joints don't have any keyframes to copy because the joints are being rotates by external means. I wrote my script to read the source joints rotation values, then copy them to the target character joints creating keyframe. I'm using the doc.SetTime() function to move to the next frame in the timeline. The problem is, it looks like doc.SetTime() doesn't actually change the current frame while the script is running. My target character does get new keyframes for each frame, but it's only of the first frame. It's like time line (and source character) isn't getting updated every time doc.SetTime() is called. Is there a Python function that tells Cinema 4D to update everything when the current frame is changed by the script?

      I'm using Cinema 4D R21.

      1 Reply Last reply Reply Quote 0
      • CairynC
        Cairyn
        last edited by

        That's correct, SetTime() doesn't perform an evaluation of the scene. You need to call BaseDocument.ExecutePasses() to do that.

        1 Reply Last reply Reply Quote 0
        • B
          brucek5
          last edited by

          Hi Cairyn,
          I already tried doc.ExecutePasses(None,True,True,True, c4d.BUILDFLAGS_NONE) after the SetTime() function, but it doesn't make a difference. Are there different parameters I should be using?

          1 Reply Last reply Reply Quote 0
          • CairynC
            Cairyn
            last edited by

            Ah, I see now you wrote "tag based". Since the tag is evaluated within the pass execution, I don't think you can call ExecutePasses() at all (but then, you shouldn't be able to SetTime() either? Are you executing the script as response to a button press, or during the regular execution of the tag?) I am now a bit confused about the logic of it all. You create keyframes, so why is this a tag instead of a regular script that you would only trigger once?

            If you want to create keyframes that reflect a certain time-based state, you could run through the scene in a script (not tag) and ExecutePasses from there, running the script only when necessary. This would essentially be a form of baking your dynamic target values into keyframes.

            1 Reply Last reply Reply Quote 0
            • B
              brucek5
              last edited by

              Hi Cairyn,
              Thanks for the reply. Much of your insight is correct, here is the breakdown.

              1. I created a null and added user data for parameters, object links and a button. This is the only way I know how to create an interface.

              2. I attached a Python tag to the null. The Python tag has a AddEventNotification and a message function to capture when the button is pressed, which in turn calls the Python code that does the re-targeting.

              3. When the re-targeting code starts, it first obtains all the user data (interface) attached to the null, then performs the re-targeting.

              I'm guessing the AddEventNotification is stealing Cinema 4D performance because it's being called anytime anything happens in Cinema 4D.

              Reading between the lines of your last reply, I should be calling a Python script? Does this mean a standalone Python file? If so, do you know of any examples of that demonstrate how to create a user interface that is displayed that the Python code can obtain the parameters and run specific code when buttons is pressed?

              CairynC 1 Reply Last reply Reply Quote 0
              • CairynC
                Cairyn @brucek5
                last edited by Cairyn

                @brucek5

                Well, first of all you wouldn't need Event Notification if the button is on the Python tag itself, instead of the null. For its own controls, the message function already receives all events:

                import c4d
                
                def message(msg_type, data):
                    if msg_type == c4d.MSG_DESCRIPTION_COMMAND:
                        print (data)
                        desc_id = data['id']
                        print (desc_id, " received")
                
                def main():
                    pass
                

                (to be used in a Python tag with one button)

                This is still called very often but at least doesn't require the whole notification shebang which is listed as private in the documentation, and as long as you use early return methods, it's not bad.
                There may be reasons why you'd want the GUI attached to the null but in this case I would readily put it on the Python tag.

                As far as Python scripts go: Yes, the advantage of a Python tag is that the code is bound to the document/project/file, which is not the case with a script, which resides separately in the script folder, or a full Python plugin, which goes in the plugin folder. There are arguments for both ways to store Python code - the script or plugin is available for all projects (if properly generalized); the plugin even allows setting up a GUI that can be embedded in the layout.

                In your case, I am honestly not sure whether I recommend a script or plugin; I guess I personally would realize it as such (being a script fan 😁 ) but if the code is tightly bound to your project, doing it within the tag is just as fine, as long as you execute the code as reaction to a button (in the message function) and not as part of the scene evaluation (in the main function).

                Setting up your own dialog (or even plugin, for which you'd need a unique plugin ID to be obtained from Maxon) indeed requires a bit more work than the bare user data. Here's a sample script (not plugin) that opens a very simple dialog with two buttons:

                import c4d
                from c4d import gui, utils
                
                ID_EDIT_BUTTONGROUP = 1007
                ID_EDIT_OKBUTTON = 1008
                ID_EDIT_CANCELBUTTON = 1009
                
                class settingsDialog(c4d.gui.GeDialog):
                
                    def __init__(self):
                        c4d.gui.GeDialog.__init__(self)
                        self.exitok = False
                
                
                    def CreateLayout(self):
                        self.SetTitle("Several text fields")
                        if self.GroupBegin(ID_EDIT_BUTTONGROUP, c4d.BFH_CENTER, 2, 0):
                            self.AddButton(ID_EDIT_OKBUTTON, c4d.BFH_LEFT, name="OK")
                            self.AddButton(ID_EDIT_CANCELBUTTON, c4d.BFH_LEFT, name="Cancel")
                        self.GroupEnd()
                        return True
                
                
                    def Command(self, id, msg):
                        if id==ID_EDIT_OKBUTTON:
                            print ("OK clicked")
                            self.exitok = True
                            self.Close()
                        if id==ID_EDIT_CANCELBUTTON:
                            print ("Cancel clicked")
                            self.exitok = False
                            self.Close()
                        return True
                
                
                def main():
                    sDialog = settingsDialog()
                    if sDialog.Open(c4d.DLG_TYPE_MODAL_RESIZEABLE):
                        print ("Exit:", sDialog.exitok)
                
                
                if __name__=='__main__':
                    main()
                

                I do have lessons for script dialogs galore here (section 10):
                https://www.patreon.com/cairyn
                and also simple command plugins that allow you to embed the dialog in the GUI, but that is not really the core of the question. (There are also examples here on the forum if you search for GeDialog, I assume.)

                Back to the original topic:
                I have set up a simple example with a cube falling on a plane through dynamics.
                The Python tag contains a button that, if clicked, executes the scene for frames 0-80 and creates keyframes for rotation and translation of the cube. The tag's code looks like this:

                import c4d
                
                def SetKey (currentTime, obj, parameterDesc, value):
                    track = obj.FindCTrack(parameterDesc)
                
                    if track == None:
                        # create track for this parameter
                        track = c4d.CTrack(obj, parameterDesc)
                        obj.InsertTrackSorted(track)
                    curve = track.GetCurve() # always present
                    currentKey = curve.FindKey(currentTime) # returns dictionary "key" (Key), "idx" (Index)
                    if currentKey:
                        key = currentKey['key'] # key reference to change
                        doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, key)
                        key.SetValue(curve, value)
                        curve.SetKeyDirty()
                    else:
                        defkey, dub = doc.GetDefaultKey() # new key to insert
                        key = defkey.GetClone() # needed?
                        key.SetValue(curve, value)
                        key.SetTime(curve, currentTime)
                        key.SetInterpolation(curve, c4d.CINTERPOLATION_SPLINE)
                        curve.InsertKey(key, True)
                        doc.AddUndo(c4d.UNDOTYPE_NEW, key)
                
                def message(msg_type, data):
                    if msg_type == c4d.MSG_DESCRIPTION_COMMAND:
                        print (data) # irrelevant since there is only one button
                        fps = doc.GetFps()
                        theCube = doc.GetFirstObject()
                        for frame in range(81):
                            doc.SetTime(c4d.BaseTime(frame, fps))
                            doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_INTERNALRENDERER)
                            print ("Rot:", theCube[c4d.ID_BASEOBJECT_REL_ROTATION])
                            print ("Pos:", theCube[c4d.ID_BASEOBJECT_REL_POSITION])
                
                            descX = c4d.DescID(
                                    c4d.DescLevel(c4d.ID_BASEOBJECT_REL_ROTATION, c4d.DTYPE_VECTOR, 0),
                                    c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0)
                            )
                            descY = c4d.DescID(
                                    c4d.DescLevel(c4d.ID_BASEOBJECT_REL_ROTATION, c4d.DTYPE_VECTOR, 0),
                                    c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0)
                            )
                            descZ = c4d.DescID(
                                    c4d.DescLevel(c4d.ID_BASEOBJECT_REL_ROTATION, c4d.DTYPE_VECTOR, 0),
                                    c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0)
                            )
                            val = theCube[descX]
                            SetKey (doc.GetTime(), theCube, descX, val)
                            val = theCube[descY]
                            SetKey (doc.GetTime(), theCube, descY, val)
                            val = theCube[descZ]
                            SetKey (doc.GetTime(), theCube, descZ, val)
                            descX = c4d.DescID(
                                    c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0),
                                    c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, 0)
                            )
                            descY = c4d.DescID(
                                    c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0),
                                    c4d.DescLevel(c4d.VECTOR_Y, c4d.DTYPE_REAL, 0)
                            )
                            descZ = c4d.DescID(
                                    c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, 0),
                                    c4d.DescLevel(c4d.VECTOR_Z, c4d.DTYPE_REAL, 0)
                            )
                            val = theCube[descX]
                            SetKey (doc.GetTime(), theCube, descX, val)
                            val = theCube[descY]
                            SetKey (doc.GetTime(), theCube, descY, val)
                            val = theCube[descZ]
                            SetKey (doc.GetTime(), theCube, descZ, val)
                
                def main():
                    pass
                

                As you see, the scene evaluation is triggered in the aforementioned way by setting the frame and then calling ExecutePasses, which will realize the dynamics. Then the keyframes are generated for the new cube position. You can then switch off dynamics and use only the keyframes. (I am using BUILDFLAGS_INTERNALRENDERER here but I am not sure whether this makes a difference.)

                You can play with that; I suppose without knowing your exact code I can't help much more.

                EDIT: Here's the scene for your convenience:
                Python tag triggers scene evaluation.c4d

                1 Reply Last reply Reply Quote 0
                • B
                  brucek5
                  last edited by

                  Hi Cairyn,
                  Thanks for the VERY detailed reply and sample project! I'll try re-doing my project.

                  BTW - I'm trying to join your Patreon (if I get the web site to send me an email to reset my password). I have another Python joint animation re-targeting project I'm having challenges with matrixes and changing joint rotation orientation.

                  Bruce Kingsley

                  1 Reply Last reply Reply Quote 0
                  • B
                    brucek5
                    last edited by

                    Hi Cairyn,
                    Your suggested changed fixed my issue! How do I access the user data for parameters from the tag?
                    My old method was to first find the null object, then the user data within it:

                    # Find this controller
                    contlr = doc.SearchObject("Re-Target_Controller")
                    if not contlr:
                        c4d.gui.MessageDialog("ERROR  Motion Retarget Controller not found!")
                        return
                    
                    baseSource = contlr[c4d.ID_USERDATA, 2]
                    if not baseSource:
                        c4d.gui.MessageDialog("ERROR  Source object joint root not provided!")
                        return
                    
                    1 Reply Last reply Reply Quote 0
                    • CairynC
                      Cairyn
                      last edited by

                      There are predefined variables available inside the execution context of the Python tag. op is the tag itself (just like doc is the document). So you can just write op[c4d.ID_USERDATA, 2]
                      obj = op.GetObject() is the object the tag is on, e.g. the null. It makes sense to place the Python tag appropriately, so you don't have to use SearchObject, which is an "expensive" operation.

                      Note that in my sample code I use doc.GetFirstObject() to get the cube, which is of course bad code and you shouldn't do that 😉

                      1 Reply Last reply Reply Quote 0
                      • ManuelM
                        Manuel
                        last edited by

                        Hi,

                        and thanks a lot @Cairyn for the answers.

                        I have almost nothing to add, we have an example on github about how to copy keyframes from an object to another that could help. This will just copy keyframes. You only need to execute passes if you want to retrieve data driven by expression or dynamics.

                        I hope i did not missed any question, if so, just ask.

                        Cheers,
                        Manuel

                        MAXON SDK Specialist

                        MAXON Registered Developer

                        1 Reply Last reply Reply Quote 0
                        • B
                          brucek5
                          last edited by

                          Thanks again Cairyn. It sounds like 'op' is similar to 'this' in c#.
                          BTW - I was able to join your Patreon.

                          This posting is now closed.

                          ManuelM 1 Reply Last reply Reply Quote 0
                          • ManuelM
                            Manuel @brucek5
                            last edited by

                            @brucek5 said in SetTime() and document updating:

                            It sounds like 'op' is similar to 'this' in c#

                            op is a pre-defined variable. Using a tag to script your python, op point to the tag itself. But using the visual selector, op will point to the object in the link and could be None.

                            You can have a look at our manuals page where you will find Python Scripting Nodes that will give information about pre-defined variables for each node where you can use python.

                            Cheers,
                            Manuel

                            MAXON SDK Specialist

                            MAXON Registered Developer

                            1 Reply Last reply Reply Quote 0
                            • First post
                              Last post