Solved how to get object's position and material color on all time line?

Dear. MAXON's SDK Team

I use R21 on microsoft windows with python.

I have threads that how to get object's position and material color on all time line.
below my simple source code.

import c4d							# import Cinema 4D module
doc = c4d.documents.GetActiveDocument()	# Cinema 4D active document

number_of_sphere = 10		# [Set] sphere number
frames_count = 2600		# [Set] frames counter

for i in range(0, number_of_sphere):
	for f in range(0, frames_count):
		doc.SetTime(c4d.BaseTime(f, doc[c4d.DOCUMENT_FPS]))
		c4d.EventAdd()
		obj = doc.SearchObject('Sphere_' + str(i))
		# get object position
		x = int(obj.GetMg().off.x)
		y = int(obj.GetMg().off.y)
		z = int(obj.GetMg().off.z)
		# get object color
		r = int(obj[c4d.ID_BASEOBJECT_COLOR].x * 255)
		g = int(obj[c4d.ID_BASEOBJECT_COLOR].y * 255)
		b = int(obj[c4d.ID_BASEOBJECT_COLOR].z * 255)
		print(obj.GetName() + ", Frame = " + str(f) + ", (X,Y,Z) = " + str(x) + "," + str(y) + "," + str(z) + ", RGB = " + str(r) + "," + str(g) + "," + str(b))

Please, guide to me for method of this.

Cheers,
MAXON's SDK Team

Hi jhpark, thanks for reaching out us.

Aside from the notes left by @blastframe - thanks dude for the remarks - I think it's worthy, thinking of a more generic scene, to mention the need BaseDocument::ExecutePasses() to be sure that everything is actually evaluated before querying the scene rather than the EventAdd() which serves a different scope.
This function is responsible to execute the scene evaluation and, consequently to be sure that, moving from a frame to another, all the items in the scene reflect the changes imposed by the frame switch.

The approach used by @blastframe actually operates on CTracks and key but, although this approach works fine for your specific case, when more evaluation dependencies are created in the scene you could easily end up in unexpected results.

The code could then look like

    frames_count = 10 # [Set] frames counter

    for f in range(0, frames_count):
        doc.SetTime(c4d.BaseTime(f, doc.GetFps()))
        
        # evaluate the scene
        doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_NONE)

        obj = doc.SearchObject('Cube')

        # get object position
        x = int(obj.GetMg().off.x)
        y = int(obj.GetMg().off.y)
        z = int(obj.GetMg().off.z)

        # get object color
        r = int(obj[c4d.ID_BASEOBJECT_COLOR].x * 255)
        g = int(obj[c4d.ID_BASEOBJECT_COLOR].y * 255)
        b = int(obj[c4d.ID_BASEOBJECT_COLOR].z * 255)
        print("Frame = " + str(f) + ", (X,Y,Z) = " + str(x) + "," + str(y) + "," + str(z) + ", RGB = " + str(r) + "," + str(g) + "," + str(b))

Hi @jhpark!
I don't work for the SDK team, but I believe the script below will do what you want.

Some quick notes about posting:

  1. When entering your code into a post on this forum, make sure you hit this button first
    code.png
    It creates code tags in your post. Put your code in between those and then it will format your code automatically.

  2. Also, after submitting, hit the button Topic Tools at the bottom right of your post to Ask as Question.
    code3.png

  3. When someone has answered your question correctly, click this button at the bottom of their post.
    code2.png
    This makes it clear to the moderators when the question has been correctly answered.

Here's the code. Because you were using ID_BASEOBJECT_COLOR, I was unsure if you wanted the object's display color or the material color (they are two different things), but I wrote this for the sphere's texture tags' material's color. Also, the code is for the spheres' relative position. More work would need to be done to get the animating position track values into global space.

import c4d
from c4d import gui

def GetNextObject(op):
    #function for navigating the hierarchy
    if op==None: return None
    if op.GetDown(): return op.GetDown()
    while not op.GetNext() and op.GetUp():
        op = op.GetUp()
    return op.GetNext()
    c4d.EventAdd()

def getPreviewRange(doc,fps):
    #returns the active preview range
    fps = doc.GetFps()
    fromTime = doc.GetLoopMinTime().GetFrame(fps)
    toTime = doc.GetLoopMaxTime().GetFrame(fps)+1
    return [fromTime,toTime]

def convertVecToRgb(vector):
    #converts vector to rgb list
    return [vector[0]*255,vector[1]*255,vector[2]*255]

def main(doc):
    fps = doc.GetFps()
    previewRange = getPreviewRange(doc,fps) #rather than needing to set frames manually, you can simply resize your preview range.
    frame_count = previewRange[1]-previewRange[0]

    # this section navigates the hierarchy and saves all of the spheres to a list called 'output'
    # it's better to do this than to use doc.SearchObject in the case you have multiple spheres with the same name
    obj = doc.GetFirstObject()
    if obj==None:
        gui.MessageDialog('There are no objects in the scene.')
        return

    output = []
    while obj and obj!=None:
        if obj.GetType() == c4d.Osphere:
            output.append(obj)
        obj = GetNextObject(obj)

    if len(output) == 0:
        gui.MessageDialog('There are no spheres in the scene.')

    # loops through spheres in the scene
    for sphere in output:
        #prints a separating line to the console
        print '#' * 80
        for f in range(previewRange[0], previewRange[1]):
            doc.SetTime(c4d.BaseTime(0, doc[c4d.DOCUMENT_FPS]))
            keyTime = c4d.BaseTime(f,fps) #get the current frame

            # POSITION
            pTracks = sphere.GetCTracks() #get the sphere's animating tracks
            
            pos = [sphere.GetMl().off.x,sphere.GetMl().off.y,sphere.GetMl().off.z] #get the sphere's default relative position

            #replace those values with the animating ones.
            for t in pTracks:
                descid = t.GetDescriptionID() #get the track's id
                if descid[0].id == c4d.ID_BASEOBJECT_REL_POSITION: #see if it matches the object's position track
                    curve = t.GetCurve() #get the track's animation curve
                    keyvalue = curve.GetValue(keyTime, fps) #get the animation curve's value at the current frame
                    if descid[1].id == c4d.VECTOR_X:
                        pos[0] = keyvalue #add to x
                    elif descid[1].id == c4d.VECTOR_Y:
                        pos[1] = keyvalue #add to x
                    elif descid[1].id == c4d.VECTOR_Z:
                        pos[2] = keyvalue #add to z

            # MATERIAL COLOR
            tags = sphere.GetTags() #get sphere's tags

            matColor = [] #create material color list

            for tag in tags: #loop through sphere's tags
                if tag.GetType() == c4d.Ttexture: #check if tag is a texture tag
                    mat = tag.GetMaterial() #if yes, get the tag's material

                    tracks = mat.GetCTracks() #get the material's animating tracks
                    
                    for t in tracks:
                        descid = t.GetDescriptionID() #get the track's id
                        if descid[0].id == c4d.MATERIAL_COLOR_COLOR: #see if it matches the material color track
                            curve = t.GetCurve() #get the track's animation curve
                            keyvalue = curve.GetValue(keyTime, fps) #get the animation curve's value at the current frame
                            matColor.append(keyvalue*255) #add r,g,b to matColor
                    
                    if len(tracks) == 0: #in case it's not animating, use general Color
                        matColor = convertVecToRgb(mat[c4d.MATERIAL_COLOR_COLOR])

            # I prefer using string formatting with the placeholder %s for strings, %d for numbers,
            # and the % as the replacement operator
            print("Name: %s, Frame: %d, Position (x,y,z): %d,%d,%d, Material Color (r,g,b): %d,%d,%d"%(
                                                                            sphere.GetName(),f,pos[0],pos[1],pos[2],
                                                                            matColor[0],matColor[1],matColor[2]))

if __name__=='__main__':
    # rather than using documents.GetActiveDocument, I found that you can pass a reference to the document using this method
    main(doc)

Here's a scene file where the object's display colors and material colors are different. The display colors are visible in the viewport, but you will see the material color if you render.
Spheres.c4d

Hi jhpark, thanks for reaching out us.

Aside from the notes left by @blastframe - thanks dude for the remarks - I think it's worthy, thinking of a more generic scene, to mention the need BaseDocument::ExecutePasses() to be sure that everything is actually evaluated before querying the scene rather than the EventAdd() which serves a different scope.
This function is responsible to execute the scene evaluation and, consequently to be sure that, moving from a frame to another, all the items in the scene reflect the changes imposed by the frame switch.

The approach used by @blastframe actually operates on CTracks and key but, although this approach works fine for your specific case, when more evaluation dependencies are created in the scene you could easily end up in unexpected results.

The code could then look like

    frames_count = 10 # [Set] frames counter

    for f in range(0, frames_count):
        doc.SetTime(c4d.BaseTime(f, doc.GetFps()))
        
        # evaluate the scene
        doc.ExecutePasses(None, True, True, True, c4d.BUILDFLAGS_NONE)

        obj = doc.SearchObject('Cube')

        # get object position
        x = int(obj.GetMg().off.x)
        y = int(obj.GetMg().off.y)
        z = int(obj.GetMg().off.z)

        # get object color
        r = int(obj[c4d.ID_BASEOBJECT_COLOR].x * 255)
        g = int(obj[c4d.ID_BASEOBJECT_COLOR].y * 255)
        b = int(obj[c4d.ID_BASEOBJECT_COLOR].z * 255)
        print("Frame = " + str(f) + ", (X,Y,Z) = " + str(x) + "," + str(y) + "," + str(z) + ", RGB = " + str(r) + "," + str(g) + "," + str(b))