retrieve data from multiple cloners in python



  • Hi,
    I'm sorry if I'm posting a repeated topic. I am trying to get color data from multiple mo-graph in python to send out to a micro controller for realtime animation.
    I am explaining what I've done so far
    I have 90 linear cloners of cubes (there are 30 of them in each cloner). I want to get color data from each clone and send the rgb color to the controller. After a couple of hours searching I came to this conclusion to make a blank 2D list, append each cloner as an object to the list and therefore, I can have an ID to retrieve the data from. from my one week experience in C4D and python, I wrote this code. I set 2 cloners as user data. this it absolutely not working. If any one has done sth similar to what I want to do, his/her help would be much appreciated.

    import c4d
    from c4d.modules import mograph as mo
    from c4d import utils
      
    def main(): 
        n = 2
        m = 33
        Matrix = [[0] * m for i in range(n)]
       
        Obj1 = op[c4d.ID_USERDATA,2]
        Obj2 = op[c4d.ID_USERDATA,1]
        
        count = len(Obj1) 
        print count
        
        for j in range (32): 
           Matrix[j][0] = Obj1
           Matrix[j][1] = Obj2 
           
        md = mo.GeGetMoData(op)
        if md==None: return False
        
        cnt = md.GetCount()
        marr = md.GetArray(c4d.MODATA_MATRIX)
        carr = md.GetArray(c4d.MODATA_COLOR)
        
        print carr
           
        for i in reversed(xrange(0, cnt)):
           md.SetArray(c4d.MODATA_COLOR, carr, True)
           md.SetArray(c4d.MODATA_WEIGHT, warr, True)
           return True
    


  • Hi @Parvin, first of all, welcome in the plugincafe community!

    Regarding your requestion, a cloner is simply a generator that will copy objects in its cache (like any generator), so in order to access the correct data, you should first access the cache of the first cloner then here you can iterates the cloner and query them for their color like so.

    """
    Copyright: MAXON Computer GmbH
    Author: Maxime Adam
    
    Description:
        - Retrieves the color or each clone using two ways.
        - First one by accessing directly the Mograph Data.
        - Second one by accessing the polygon cache representation of the Mograph Cloner.
    
    Class/method highlighted:
        - c4d.modules.mograph.GeGetMoData()
        - c4d.modules.mograph.MoData
        - MoData.GetArray()
        - BaseObject.GetCache()
        - BaseObject.GetDeformCache()
    
    Compatible:
        - Win / Mac
        - R16, R17, R18, R19, R20
    """
    import c4d
    
    
    def RetrieveColorWithMoData(op):
        # Retrieving internal cache of the first cloner to access each cloner and query them.
        cacheMainClone = op.GetCache()
        if cacheMainClone is None:
            raise RuntimeError("Failed to retrieves the cached cloner.")
    
        # Cloner object creates a null, with all their clones. So we dive into this null, and it should be a cloner object
        clonerCache = cacheMainClone.GetDown()
        if not clonerCache.CheckType(1018544):
            raise TypeError("objects are not cloners.")
    
        # Iterates each cloner and retrieves the data for each one
        finalList = []
        while clonerCache:
            # Retrieves the modata
            md = c4d.modules.mograph.GeGetMoData(clonerCache)
            if md is None:
                continue
    
            # Retrieves the clone offset and the color array
            offset = clonerCache[c4d.MG_LINEAR_OFFSET]
            colorList = md.GetArray(c4d.MODATA_COLOR)
    
            # Appends the color list taking in account the offset (aka skip the first elements)
            finalList.append(colorList[offset:])
    
            # Process the next cloner
            clonerCache = clonerCache.GetNext()
    
        return finalList
    
    
    def DeformedPolygonCacheIterator(op):
        """
        A Python Generator to iterate over all PolygonCache of passed BaseObject
        :param op: The BaseObject to retrieves all PolygonObject cache.
        """
        if not isinstance(op, c4d.BaseObject):
            raise TypeError("Expected a BaseObject or derived class got {0}".format(op.__class__.__name__))
    
        # Try to retrieves the deformed cache of the object
        temp = op.GetDeformCache()
        if temp is not None:
            # If there is a deformed cache we iterate over him, a deformed cache can also contain deformed cache
            # e.g. in case of a nested deformer
            for obj in DeformedPolygonCacheIterator(temp):
                yield obj
    
        # Try to retrieves the cache of the Object
        temp = op.GetCache()
        if temp is not None:
            # If there is a cache iterate over its, a cache can also contain deformed cache
            # e.g. an instance, have a cache of its linked object but if this object is deformed, then you have a deformed cache as well
            for obj in DeformedPolygonCacheIterator(temp):
                yield obj
    
        # If op is not a generator / modifier
        if not op.GetBit(c4d.BIT_CONTROLOBJECT):
            # If op is a PolygonObject we return it
            if op.IsInstanceOf(c4d.Opolygon):
                yield op
    
        # Then finally iterates over the child of the current object to retrieves all objects
        # e.g. in a cloner set to Instance mode, all clones is a new object.
        temp = op.GetDown()
        while temp:
            for obj in DeformedPolygonCacheIterator(temp):
                yield obj
            temp = temp.GetNext()
    
    
    def RetrieveColorWithCache(op):
        # Iterates the polygon cache of a cloner (does work only, in case of simple instance mode)
        childClonerCnt = op.GetDown()[c4d.MG_LINEAR_COUNT]
        finalList = []
        # Iterates overs each polygon object cache
        for i, obj in enumerate(DeformedPolygonCacheIterator(op)):
            # For each new list we add a new list
            d = float(i) / float(childClonerCnt)
            if float(d) == int(d):
                finalList.append([])
    
            # Adds the object information in the last list
            finalList[-1].append(obj[c4d.ID_BASEOBJECT_COLOR])
    
        return finalList
    
    
    def main():
        # Checks if there is a selected object, and this selected object get a child object.
        if not op or not op.GetDown():
            raise RuntimeError("Failed to retrieves op and its child.")
    
        # Checks if selected object and child objects are cloner objects.
        if not op.CheckType(1018544) or not op.GetDown().CheckType(1018544):
            raise TypeError("objects are not cloners.")
    
        print RetrieveColorWithMoData(op)
        print RetrieveColorWithCache(op)
    
    
    if __name__ == "__main__":
        main()
    
    

    Don't worry since it's your first topic, in order to setup correctly your next topics (I've done it for this one) please read and apply:

    Of course, if you have any question, please let me know.
    Cheers,
    Maxime.



  • Wow! thanks Adam. I have to take my time to process all the useful information you gave me. I will reply on this post if I have further questions.



  • @m_adam when I ran the code you posted, I got the error "Failed to retrieves op and its child". I am calling each function in the main to realize what each of them is doing. But I think all of them are linked to one another.
    So my main question is that when you say I need to select my objects in order, how should I exactly do this? I put the Python Effector in the effectors list for each 90 cloners that I have, and I assumed this would work.

    Thanks,
    Parvin



  • Hoo sorry, I overlooked the fact that you are directly in a python effector and not in the script manager (in fact I missed to ask you, please next time try to say us, in which context you are executing python).

    So I should have mentioned that the selected object should be the topmost cloner (the cloner of the cloner).
    So with that's said, with my previous scrip use "gen" instead of op in main and it's fixed see Python Effector.
    Here is the example cloner.c4d

    Cheers,
    Maxime.



  • Oh I see. I take that note for python effector, thanks. I think I explained it in a very wrong way because as I see you have a cloner as the child of other cloner. In my example, I have 90 different cloners. I will attach my example.

    P

    90 strut_Animation.c4d



  • Then I'm afraid I still don't understand the final result, please be more explicit about what you are trying to achieve, what the expected behavior. In your first post, you said it's not working, but now I do have a scene example so please tell me what you want to achieve within the scene.

    Cheers,
    Maxime.



  • I have 90 strut shaping a sphere in this scene. each strut is a linear cloner of cubes and they represent LED strips. I want to animate the light in realtime. So, I am trying to animate the sphere with effectors and change the color, then send the color data through Lumos Library to a Raspberry Pi to animate light. I'm awfully new to C4D and Python in C4D.
    I know I have to give address to each cube and link it with the address I have for the LED pixel. But that goes way further. what I'm struggling with now is capturing data of each cloner.

    Thanks for your patience,
    Parvin



  • I don't think effector is the best way to achieve that at least your effector could retrieve data only for the current generator/object it's currently sampling, so one object per time. So an effector does not have an idea of the whole picture.

    The best way would be either to have a GeDialog that will monitor the scene and send the data (but that involve to make a plugin, it's not complicated but its an external file and not directly embedded within a C4D file)
    Another solution could be to do a scripting tag, with the correct priority (so it's executed after mograph generation) which will retrieve all data you need. You could add an InExclude UserData to this tag so could drag and drop the cloner in the list and use this as an ID.

    Anyway I adapted the example to make it work, with simple cloner (no nested), so the op object of the main method should be a cloner and it will tell you the color for each clone.

    """
    Copyright: MAXON Computer GmbH
    Author: Maxime Adam
    
    Description:
        - Retrieves the color or each clone using two ways.
        - First one by accessing directly the Mograph Data.
        - Second one by accessing the polygon cache representation of the Mograph Cloner.
    
    Class/method highlighted:
        - c4d.modules.mograph.GeGetMoData()
        - c4d.modules.mograph.MoData
        - MoData.GetArray()
        - BaseObject.GetCache()
        - BaseObject.GetDeformCache()
    
    Compatible:
        - Win / Mac
        - R16, R17, R18, R19, R20
    """
    import c4d
    
    
    def RetrieveColorWithMoData(op):
        if not op.CheckType(1018544):
            raise TypeError("objects is not a cloner.")
    
        # Retrieves the modata
        md = c4d.modules.mograph.GeGetMoData(op)
        if md is None:
            return []
    
        # Retrieves the clone offset and the color array
        offset = op[c4d.MG_LINEAR_OFFSET]
        colorList = md.GetArray(c4d.MODATA_COLOR)
    
        # Appends the color list taking in account the offset (aka skip the first elements)
        return colorList[offset:]
    
    
    def DeformedPolygonCacheIterator(op):
        """
        A Python Generator to iterate over all PolygonCache of passed BaseObject
        :param op: The BaseObject to retrieves all PolygonObject cache.
        """
        if not isinstance(op, c4d.BaseObject):
            raise TypeError("Expected a BaseObject or derived class got {0}".format(op.__class__.__name__))
    
        # Try to retrieves the deformed cache of the object
        temp = op.GetDeformCache()
        if temp is not None:
            # If there is a deformed cache we iterate over him, a deformed cache can also contain deformed cache
            # e.g. in case of a nested deformer
            for obj in DeformedPolygonCacheIterator(temp):
                yield obj
    
        # Try to retrieves the cache of the Object
        temp = op.GetCache()
        if temp is not None:
            # If there is a cache iterate over its, a cache can also contain deformed cache
            # e.g. an instance, have a cache of its linked object but if this object is deformed, then you have a deformed cache as well
            for obj in DeformedPolygonCacheIterator(temp):
                yield obj
    
        # If op is not a generator / modifier
        if not op.GetBit(c4d.BIT_CONTROLOBJECT):
            # If op is a PolygonObject we return it
            if op.IsInstanceOf(c4d.Opolygon):
                yield op
    
        # Then finally iterates over the child of the current object to retrieves all objects
        # e.g. in a cloner set to Instance mode, all clones is a new object.
        temp = op.GetDown()
        while temp:
            for obj in DeformedPolygonCacheIterator(temp):
                yield obj
            temp = temp.GetNext()
    
    
    def RetrieveColorWithCache(op):
        # Iterates the polygon cache of a cloner (does work only, in case of simple instance mode)
        finalList = []
        # Iterates overs each polygon object cache
        for i, obj in enumerate(DeformedPolygonCacheIterator(op)):
    
            # Adds the object information in the last list
            finalList.append(obj[c4d.ID_BASEOBJECT_COLOR])
    
        return finalList
    
    
    def main():
        # Checks if there is a selected object, and this selected object get a child object.
        if not op:
            raise RuntimeError("Failed to retrieves op")
    
        # Checks if selected object and child objects are cloner objects.
        if not op.CheckType(1018544):
            raise TypeError("objects is not cloner.")
    
        print RetrieveColorWithMoData(op)
        print RetrieveColorWithCache(op)
    
    
    if __name__ == "__main__":
        main()
    
    

    With that's said I think you have everything you need to build your stuff, at least for reading a cloner object.

    The project looks cool, so even if it seems complicated don't give up and don't hesitate to ask questions ! 😉
    Cheers,
    Maxime.



  • Thank you @m_adam it's really helpful! I was going to a wrong direction. I chose to do a project that has every new aspect to explore :)) I will for sure disturb you in future with my questions. I appreciate your suggestions.

    Parvin