Can I reset the keyframe of an object through Python script?



  • Hi, I am making a DEMO for my research work of crowd simulation. The people may walk and stop during the animation. The position and drection(Velocity) of the crowd have been recorded in a text file. I have written a Python script successfully read the data file at each keyframe. Now, I use TP partilce to animate the crowd. And, I subsitute the particle with a Man model through XPresso Pshape tags. Shown as followed.
    b3afd9da-2ab6-40ad-be83-c18da2d7dbc6-image.png

    When the position is not changed the model's motion should stop(stay at present keyframe) untill he moved again. The motion of the model is pre-rendered like a re-cycle movie.

    The keyframe of the Model as follow:
    26a31c95-5f23-46fa-81e0-454749ede650-image.png

    What should I do to hold on the present keyframe? Sorry ablout my poor question description!

    My Python scripts as followed:

    # Boids for Py4D by smart-page.net
    
    import c4d
    import math
    
    # particles' params
    boids_number = 1000
    currentframe = None
    
    # used for read user data frame by frame
    frame_step = boids_number+1 
    frame_total = 0
    
    
    def main():
        global tp
        global doc
        global currentframe    
        
        currentframe = doc.GetTime().GetFrame(doc.GetFps())
        
        # particles born at 0 frame
        if currentframe == 0:   
            tp.FreeAllParticles()
            tp.AllocParticles(boids_number)
                 
                    
        # life time for particles
        lt = c4d.BaseTime(1000)
    
        # user data for paritlces
        filename = op[c4d.ID_USERDATA, 1]
       
        # open the user file. First, read a frame of data from the user file. Then, read lines one bye one to feed the particles.
        with open(filename, 'r') as fn:
            
            # read all lines of the user data
            lines = fn.readlines()
            
            # compute how many frames of data in the file
            frame_total = int(len(lines) / frame_step)
            
            # 
            frame = 1
            i=0
    
            #read a frame of data according to the scene keyframe
            for frame in range(frame_total): 
                if frame == currentframe:
                    t_lines = lines[frame * frame_step:frame * frame_step + frame_step - 1]  
                    
                    #pase lines of the readed data 
                    for line in t_lines: 
                        if line == t_lines[0]:  # filter the first line of each frame in the text file, because is just flag words
                            print(line)                       
    
                        else:
                            #split position(x,y,z) and direction (dx,dy,dz)
                            x, y, z, dx, dy, dz = line.split()                      
                            pos = c4d.Vector(float(x), float(y), float(z) )
                            vol = c4d.Vector(float(dx), float(dy), float(dz))
                                                   
                            temp=(pos-tp.Position(i)).GetLength()  
                            
                            # some codes wanted here
                            if temp==0.0:
                                # the motion of the man should stop.
                                # should I  edit the keyframe of the walking Man model?
                                # when temp==0, then the walking Man stay at present keyframe but not go ahead along with the scene keyframe. 
                                                   
                                                 
                            
                           
                            # align to velocity direction
                            vel = vol.GetNormalized()
                            side = c4d.Vector(c4d.Vector(0, 1, 0).Cross(vel)).GetNormalized()
                            up = vel.Cross(side)
                            m = c4d.Matrix(c4d.Vector(0), side, up, vel)
                            tp.SetAlignment(i, m)
                           
                            # set position
                            tp.SetPosition(i,pos)
                            
                            # set life time for particle i
                            tp.SetLife(i, lt)  
                             
                            i=i+1
    
        c4d.EventAdd()
    
    if __name__=='__main__':
        main()
    code_text
    

    My user data format as followed.
    c6d1c4a1-6356-4120-a174-5dc56e19e6b2-image.png



  • Hi, @happygrass_cn first of all welcome in the plugincafe community.

    While you have deleted your topic (probably because you solved it). I had a solution that explores a few areas in Cinema 4D that are not so well documented so I restored it.

    So my solution uses a cloner with 2 clones (one animated and the other one similar but without animation).
    I also define a thinking particle channel data( a boolean "isMoving").
    A python effector will read this attribute and according to this attribute will affect the displayed cloned object to be either the animated one or not.
    Of course, this does not provide interpolation between animation so it can look a bit odd.

    Finally in Python SetPData does not support boolean, so "IsMoving" is not a boolean (DTYPE_BOOL) but an integer(DTYPE_LONG) that I thread as a boolean.

    The Python generator does the next things:

    • Thinking Particles Creation.
    • Custom Data Channel Creation/Assignation.
    • Movement of particles.
    import c4d
    
    def GetParticleSystemAndRootGroup():
        # Retrieves the Particles System of the current document
        pSys = doc.GetParticleSystem()
        if pSys is None:
            raise RuntimeError("op is none, please select one object.")
    
        # Retrieves the Root group (where all particles belongs as default)
        rootGrp = pSys.GetRootGroup()
        if rootGrp is None:
            raise RuntimeError("Failed to retrieve root group of tp master system.")
    
        return pSys, rootGrp
    
    def GetIsMovingChannelId():
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        for x in xrange(pSys.NumDataChannels()):
            if pSys.DataChannelName(x) == "isMoving(Integer)":
                return x
    
        return False
    
    
    def CreateParticle(count):
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        if pSys.NumParticles() >= count:
            return
    
        # Allows each particles to get a custom colors
        rootGrp[c4d.PGROUP_USE_COLOR] = False
    
        # Creates 90 Particles
        particlesIds = pSys.AllocParticles(count)
        if not particlesIds:
            raise RuntimeError("Failed to create X TP particles.")
    
        # Check if the "isMoving"" Channel data already exist and if not create it
        # This Boolean "isMoving" will store the moving state of a particle
        if GetIsMovingChannelId() is False:
            pSys.AddDataChannel(c4d.DTYPE_LONG, "isMoving")
    
        isMovingId = GetIsMovingChannelId()
        if isMovingId is False:
            raise RuntimeError("Failed to retrieve IsMoving particle Data")
    
        # Assigns position and colors for each particles
        for particleId in particlesIds:
            # Checks if particles ID is ok
            if particleId == c4d.NOTOK:
                continue
    
            # Defines a lifetime of 1000 frame for each particles
            pSys.SetLife(particleId, c4d.BaseTime(1000))
    
            # Calculates a position
            sin, cos = c4d.utils.SinCos(particleId)
            pos = c4d.Vector(sin * 300.0, cos * 300.0, particleId * 30.0)
            # Assigns position
            pSys.SetPosition(particleId, pos)
    
            # Calculates a color
            hsv = c4d.Vector(float(particleId) * 1.0 / count, 1.0, 1.0)
            rgb = c4d.utils.HSVToRGB(hsv)
    
            # Assigns color
            pSys.SetColor(particleId, rgb)
    
            # Assigns a "freeze" data to indicate either the particle is stoped or not
            pSys.SetPData(particleId, isMovingId, False)
    
        return particlesIds
    
    def MoveParticle(frame):
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        isMovingId = GetIsMovingChannelId()
        if isMovingId is False:
            raise RuntimeError("Failed to retrieve IsMoving particle Data")
    
        for particleId in xrange(pSys.NumParticles()):
            # If particle don't belong to the root group
            if pSys.Group(particleId) != rootGrp:
                continue
    
            sin, cos = c4d.utils.SinCos(particleId + (frame/40.00))
            pos = c4d.Vector(sin * 300.0, cos * 300.0, particleId * 30.0)
            # Assigns position
            pSys.SetPosition(particleId, pos)
    
            # Assigns a "freeze" data to indicate either the particle is stoped or not
            pSys.SetPData(particleId, isMovingId, True)
    
    
    def FreezeParticle():
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        isMovingId = GetIsMovingChannelId()
        if isMovingId is False:
            raise RuntimeError("Failed to retrieve IsMoving particle Data")
    
        for particleId in xrange(pSys.NumParticles()):
            # If particle don't belong to the root group
            if pSys.Group(particleId) != rootGrp:
                continue
    
            # Assigns a "freeze" data to indicate either the particle is stoped or not
            pSys.SetPData(particleId, isMovingId, 0)
    
    def main():
        currentFrame = doc.GetTime().GetFrame(doc.GetFps())
        if currentFrame == 0:
            CreateParticle(20)
    
        # Don't move particles from frame 150 to 250
        elif 150 <= currentFrame <= 250:
            if currentFrame == 150:
                FreezeParticle()
        else:
            MoveParticle(currentFrame)
    

    The Python Effector set in Full Control does the next things:

    • Read the data for each particle of the Custom Data Channel
    • Defines the cloned used according to this value
    import c4d
    
    def GetParticleSystemAndRootGroup():
        # Retrieves the Particles System of the current document
        pSys = doc.GetParticleSystem()
        if pSys is None:
            raise RuntimeError("op is none, please select one object.")
    
        # Retrieves the Root group (where all particles belongs as default)
        rootGrp = pSys.GetRootGroup()
        if rootGrp is None:
            raise RuntimeError("Failed to retrieve root group of tp master system.")
    
        return pSys, rootGrp
    
    def GetIsMovingChannelId():
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        for x in xrange(pSys.NumDataChannels()):
            if pSys.DataChannelName(x) == "isMoving(Integer)":
                return x
    
        return False
    
    def main() :
        moData = c4d.modules.mograph.GeGetMoData(op)
        if moData is None:
            return False
    
        cnt = moData.GetCount()
        mdClone = moData.GetArray(c4d.MODATA_CLONE)
    
        hasField = op[c4d.FIELDS].HasContent()
    
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        # Retrieve the isMovingData channel ID
        isMovingId = GetIsMovingChannelId()
        if isMovingId is False:
            raise RuntimeError("Failed to retrieve IsMoving particle Data")
    
        # Maybe there is more particles allocated than what's is used in the cloner (aka mutlipe particle group)
        # So we loop over them
        cloneId = 0
        for particleId in xrange(pSys.NumParticles()):
    
            # If particle don't belong to the root group
            if pSys.Group(particleId) != rootGrp:
                continue
    
            if cloneId > cnt:
                raise RuntimeError("There is more cloneID than allowed")
    
            # Retrieve the internal IsMoving Data and check if the particle is moving,
            isMoving = pSys.GetPData(particleId, isMovingId)
            if isMoving == 1:
                # Defines the float value representing the cloned 0 = First child, 1 = last child
                mdClone[cloneId] = 0.0
            else:
                mdClone[cloneId] = 1.0
    
            cloneId +=1
    
        moData.SetArray(c4d.MODATA_CLONE, mdClone, hasField)
        return True
    

    And the attached scene with everything together.
    generatorTp.c4d

    Cheers,
    Maxime.



  • Hi, @happygrass_cn first of all welcome in the plugincafe community.

    While you have deleted your topic (probably because you solved it). I had a solution that explores a few areas in Cinema 4D that are not so well documented so I restored it.

    So my solution uses a cloner with 2 clones (one animated and the other one similar but without animation).
    I also define a thinking particle channel data( a boolean "isMoving").
    A python effector will read this attribute and according to this attribute will affect the displayed cloned object to be either the animated one or not.
    Of course, this does not provide interpolation between animation so it can look a bit odd.

    Finally in Python SetPData does not support boolean, so "IsMoving" is not a boolean (DTYPE_BOOL) but an integer(DTYPE_LONG) that I thread as a boolean.

    The Python generator does the next things:

    • Thinking Particles Creation.
    • Custom Data Channel Creation/Assignation.
    • Movement of particles.
    import c4d
    
    def GetParticleSystemAndRootGroup():
        # Retrieves the Particles System of the current document
        pSys = doc.GetParticleSystem()
        if pSys is None:
            raise RuntimeError("op is none, please select one object.")
    
        # Retrieves the Root group (where all particles belongs as default)
        rootGrp = pSys.GetRootGroup()
        if rootGrp is None:
            raise RuntimeError("Failed to retrieve root group of tp master system.")
    
        return pSys, rootGrp
    
    def GetIsMovingChannelId():
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        for x in xrange(pSys.NumDataChannels()):
            if pSys.DataChannelName(x) == "isMoving(Integer)":
                return x
    
        return False
    
    
    def CreateParticle(count):
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        if pSys.NumParticles() >= count:
            return
    
        # Allows each particles to get a custom colors
        rootGrp[c4d.PGROUP_USE_COLOR] = False
    
        # Creates 90 Particles
        particlesIds = pSys.AllocParticles(count)
        if not particlesIds:
            raise RuntimeError("Failed to create X TP particles.")
    
        # Check if the "isMoving"" Channel data already exist and if not create it
        # This Boolean "isMoving" will store the moving state of a particle
        if GetIsMovingChannelId() is False:
            pSys.AddDataChannel(c4d.DTYPE_LONG, "isMoving")
    
        isMovingId = GetIsMovingChannelId()
        if isMovingId is False:
            raise RuntimeError("Failed to retrieve IsMoving particle Data")
    
        # Assigns position and colors for each particles
        for particleId in particlesIds:
            # Checks if particles ID is ok
            if particleId == c4d.NOTOK:
                continue
    
            # Defines a lifetime of 1000 frame for each particles
            pSys.SetLife(particleId, c4d.BaseTime(1000))
    
            # Calculates a position
            sin, cos = c4d.utils.SinCos(particleId)
            pos = c4d.Vector(sin * 300.0, cos * 300.0, particleId * 30.0)
            # Assigns position
            pSys.SetPosition(particleId, pos)
    
            # Calculates a color
            hsv = c4d.Vector(float(particleId) * 1.0 / count, 1.0, 1.0)
            rgb = c4d.utils.HSVToRGB(hsv)
    
            # Assigns color
            pSys.SetColor(particleId, rgb)
    
            # Assigns a "freeze" data to indicate either the particle is stoped or not
            pSys.SetPData(particleId, isMovingId, False)
    
        return particlesIds
    
    def MoveParticle(frame):
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        isMovingId = GetIsMovingChannelId()
        if isMovingId is False:
            raise RuntimeError("Failed to retrieve IsMoving particle Data")
    
        for particleId in xrange(pSys.NumParticles()):
            # If particle don't belong to the root group
            if pSys.Group(particleId) != rootGrp:
                continue
    
            sin, cos = c4d.utils.SinCos(particleId + (frame/40.00))
            pos = c4d.Vector(sin * 300.0, cos * 300.0, particleId * 30.0)
            # Assigns position
            pSys.SetPosition(particleId, pos)
    
            # Assigns a "freeze" data to indicate either the particle is stoped or not
            pSys.SetPData(particleId, isMovingId, True)
    
    
    def FreezeParticle():
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        isMovingId = GetIsMovingChannelId()
        if isMovingId is False:
            raise RuntimeError("Failed to retrieve IsMoving particle Data")
    
        for particleId in xrange(pSys.NumParticles()):
            # If particle don't belong to the root group
            if pSys.Group(particleId) != rootGrp:
                continue
    
            # Assigns a "freeze" data to indicate either the particle is stoped or not
            pSys.SetPData(particleId, isMovingId, 0)
    
    def main():
        currentFrame = doc.GetTime().GetFrame(doc.GetFps())
        if currentFrame == 0:
            CreateParticle(20)
    
        # Don't move particles from frame 150 to 250
        elif 150 <= currentFrame <= 250:
            if currentFrame == 150:
                FreezeParticle()
        else:
            MoveParticle(currentFrame)
    

    The Python Effector set in Full Control does the next things:

    • Read the data for each particle of the Custom Data Channel
    • Defines the cloned used according to this value
    import c4d
    
    def GetParticleSystemAndRootGroup():
        # Retrieves the Particles System of the current document
        pSys = doc.GetParticleSystem()
        if pSys is None:
            raise RuntimeError("op is none, please select one object.")
    
        # Retrieves the Root group (where all particles belongs as default)
        rootGrp = pSys.GetRootGroup()
        if rootGrp is None:
            raise RuntimeError("Failed to retrieve root group of tp master system.")
    
        return pSys, rootGrp
    
    def GetIsMovingChannelId():
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        for x in xrange(pSys.NumDataChannels()):
            if pSys.DataChannelName(x) == "isMoving(Integer)":
                return x
    
        return False
    
    def main() :
        moData = c4d.modules.mograph.GeGetMoData(op)
        if moData is None:
            return False
    
        cnt = moData.GetCount()
        mdClone = moData.GetArray(c4d.MODATA_CLONE)
    
        hasField = op[c4d.FIELDS].HasContent()
    
        # Retrieves the Particles System and the root group of the current document
        pSys, rootGrp = GetParticleSystemAndRootGroup()
    
        # Retrieve the isMovingData channel ID
        isMovingId = GetIsMovingChannelId()
        if isMovingId is False:
            raise RuntimeError("Failed to retrieve IsMoving particle Data")
    
        # Maybe there is more particles allocated than what's is used in the cloner (aka mutlipe particle group)
        # So we loop over them
        cloneId = 0
        for particleId in xrange(pSys.NumParticles()):
    
            # If particle don't belong to the root group
            if pSys.Group(particleId) != rootGrp:
                continue
    
            if cloneId > cnt:
                raise RuntimeError("There is more cloneID than allowed")
    
            # Retrieve the internal IsMoving Data and check if the particle is moving,
            isMoving = pSys.GetPData(particleId, isMovingId)
            if isMoving == 1:
                # Defines the float value representing the cloned 0 = First child, 1 = last child
                mdClone[cloneId] = 0.0
            else:
                mdClone[cloneId] = 1.0
    
            cloneId +=1
    
        moData.SetArray(c4d.MODATA_CLONE, mdClone, hasField)
        return True
    

    And the attached scene with everything together.
    generatorTp.c4d

    Cheers,
    Maxime.


Log in to reply