• On 06/06/2017 at 02:02, xxxxxxxx wrote:

    I would still suggest to retrieve the UserData once and store it globally, to avoid calling
    GetUserDataContainer() multiple times. Converting it to a dictionary reduces the time
    searching for an entry with a specific ID immensely.

    import c4d
    # A dictionary that stores the object's UserData where the keys
    # are the UserData ID's and the values are the (DescID, BaseContainer)
    # pairs returned by GetUserDataContainer()
    ud = None
    def main() :
        obj = op.GetObject()
        # We could also do this once really (if ud is None: ...) but it
        # would make development harder since the tag would need to be
        # "refreshed" everytime you change the userdata. It's still a LOT
        # better than using GetUserDataContainer() multiple times and it
        # also enhances search times for entries with specific IDs.
        global ud
        ud = dict((dd[-1].id, (dd, bc)) for dd, bc in obj.GetUserDataContainer())
        # ...
    def reset(obj, group_id) :
        for dd, bc in ud.itervalues() :
            if bc[c4d.DESC_PARENTGROUP][-1].id in group_id:
                    obj[dd] = bc[c4d.DESC_DEFAULT]
                except TypeError:

    To look up an entry with a spefic ID, you'd just use the ud Dictionary.

    def main() :
        # ...
        desc_id, bc = ud[4]  # Get's DescID and item description of userdata with ID 4

    On a side note, you will loose the order of the items returned by GetUserDataContainer(). But it
    appears that you do not need it anyway.


  • On 06/06/2017 at 02:46, xxxxxxxx wrote:

    Thank-you Niklas and gr4ph0s for this code. It is greatly appreciated.

  • On 06/06/2017 at 02:54, xxxxxxxx wrote:

    But since op don't want to hardcode his user_id he will still need to iterate thourght the dictionary each time so it will be same as iterate GetUserDataContainer.

    So it's why in my previous method, in all case even if there is 3 reset On, it will iterate only one time.

    EDIT: a nicer way would be to get UserDataContainer only one time for build a dict with parent/child relation dict["parent_id]:[descid,bc] then store this dict globally.
    And finally do as I did in my previous post but using dict instead.

  • On 06/06/2017 at 03:07, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    But since op don't want to hardcode his user_id he will still need to iterate thourght the dictionary each time so it will be same as iterate GetUserDataContainer.

    It is not the same because GetUserDataContainer() needs to build the list from the object's
    DynamicDescription first, which is an overhead you  only have once instead of each time you
    use the function if you cache the result instead (either directly or as a dictionary, doesn't matter
    very much).

  • On 06/06/2017 at 07:02, xxxxxxxx wrote:


    sorry to interrupt the professional discussion once more.

    I was a bit surprised, when returning after a long weekend, to see this thread completely derailed and two long term community members ranting at each other.

    Looks like you already settled this, but just in case you didn't and you are still throwing bombs at each other via PM, please, reach your virtual hands. I'm pretty sure, nobody wanted to insult anybody and we shouldn't get on a personal level in this forum.

    Of course insults on a personal level are generally a no-go in this forum. And here I have to say, Scott, you went a bit overboard for no obvious reasons.

    One more thing to clarify:
    We want to have this forum as a source of information. And therefore it is absolutely necessary to discuss proposed solutions and also mention their shortcomings. Critique and corrections are the base for good solutions. And it's not that far fetched to assume, a post in a thread is also meant as a solution to the actual problem. So for me, Niklas was right to point out possible problems. We can not expect a future reader to implicitly notice these all by himself.

    So, if not already done so, exchange a PM apologizing to each other.

    And now I step back again and let you guys optimize the code further.

  • On 06/06/2017 at 09:08, xxxxxxxx wrote:

    ^#!$#!**t Andreas.
    This is the second time where you took the side of Niklas for attacking me for no good reason. And then had the gall to publicly lecture me as if I was actually in the wrong.
    And the second time where after I took the high road said ok never mind. Lets just drop it, and chalk it up to a misunderstanding. You then came back again and took another cheap shot at me for no good reason other than to get in one more dig at me.
    Since it was "Settled". Your involvement in this thread was completely unnecessary Andreas.
    But worse than that. You had the balls to point at me for defending myself from being attacked for no good reason by another member who was obviously completely out of line.

    Since you obviously have some major bias with Niklas. And not only allow him to attack people, but actually support it and take his side when he does it. I'm no longer going to participate in this forum.
    This is no longer the casual, ego free, easy going place, where developers can not only help each other out, but also talk comfortably with each other. Without worrying about being jumped on for asking simple questions. Or having a different opinion.
    This place is now a cold and harsh environment. Where massive egos are bowed to, and treated like Gods. And are actually encouraged to be rude and arrogant to other members who are considered to be "below their standards".
    There's no humans here anymore. Just robots that have no clue how to have a Human-To-Human conversation. And I have no need, or desire, to be in that kind of environment.
    That's probably why all the old timers don't post here anymore. This place has been infected with too many bloated egos.

    Shame on you. Shame on you both.
    You've ruined one of my favorite places to talk with developers. And turned it into StackOverflow 2.0.


    Edit: Admin removed profanity

  • On 06/06/2017 at 09:38, xxxxxxxx wrote:

    I have tested gr4ph0s last code against my initial code. I didn't implement Niklas' changes because I don't know how. Both clock in a 3 sec in the main (more complex) file. So they both appear to be taking the same time to accomplish the same task. Any idea why this may be the case?

  • On 06/06/2017 at 10:24, xxxxxxxx wrote:

    Here is what I am running:

    import c4d
    def reset(obj, group_id) :
        for descid, bc in obj.GetUserDataContainer() :
            if bc[c4d.DESC_PARENTGROUP][-1].id in group_id:
                    obj[descid] = bc[c4d.DESC_DEFAULT]
    def get_group_to_get(obj, datas) :
        buffer = list()
        for i in xrange(len(datas)) :
            if obj[c4d.ID_USERDATA, datas[i][0]]:
                obj[c4d.ID_USERDATA, datas[i][0]] = False
        return buffer
    def main() :
        # Get the object attached ot the tag
        obj = op.GetObject()
        #list of list[enable_id, group_id]
        datas = [[486, 284],
                [485, 285],
                [489, 287],
                [490, 289],
                [491, 291],
                [492, 293],
                [508, 295],
                [509, 297],
                [510, 299],
                [511, 301],
                [561, 558],
                [639, 636],
                [661, 658],
                [683, 680],
                [705, 702],
                [727, 724],
                [749, 746],
                [771, 768],
                [793, 790],
                [815, 812],
                [841, 838],
                [863, 860],
                [885, 882],
                [907, 904],
                [929, 926],
                [951, 948],
                [973, 970],
                [995, 992],
                [1017, 1014],
                [1039, 1036],
                [1064, 1061],
                [1086, 1083],
                [1108, 1105],
                [1130, 1127],
                [1152, 1149],
                [1174, 1171],
                [1196, 1193],
                [1218, 1215],
                [1240, 1237],
                [1262, 1259],
                [1287, 1284],
                [1309, 1306],
                [1331, 1328],
                [1353, 1350],
                [1375, 1372],
                [1397, 1394],
                [1419, 1416],
                [1441, 1438],
                [1463, 1460],
                [1485, 1482],
                [1510, 1507],
                [1532, 1529],
                [1554, 1551],
                [1576, 1573],
                [1598, 1595],
                [1620, 1617],
                [1642, 1639],
                [1664, 1661],
                [1686, 1683],
                [1708, 1705],
                [1733, 1730],
                [1755, 1752],
                [1777, 1774],
                [1799, 1796],
                [1821, 1818],
                [1843, 1840],
                [1865, 1862],
                [1887, 1884],
                [1909, 1906],
                [1931, 1928],
                [1956, 1953],
                [1978, 1975],
                [2000, 1997],
                [2022, 2019],
                [2044, 2041],
                [2066, 2063],
                [2088, 2085],
                [2110, 2107],
                [2132, 2129],
                [2154, 2151],
                [2179, 2176],
                [2201, 2198],
                [2223, 2220],
                [2245, 2242],
                [2267, 2264],
                [2289, 2286],
                [2311, 2308],
                [2333, 2330],
                [2355, 2352],
                [2377, 2374],
                [2408, 2405],
                [2430, 2427],
                [2452, 2449],
                [2474, 2471],
                [2496, 2493],
                [2518, 2515],
                [2540, 2537],
                [2562, 2559],
                [2584, 2581],
                [2606, 2603],
                [2631, 2628],
                [2653, 2650],
                [2675, 2672],
                [2697, 2694],
                [2719, 2716],
                [2741, 2738],
                [2763, 2760],
                [2785, 2782],
                [2807, 2804],
                [2829, 2826],
        group_to_check = get_group_to_get(obj, datas)
        if not group_to_check:
        #Reset data
        reset(obj, group_to_check)
        # Trigger c4d update
    if __name__ == '__main__':

  • On 06/06/2017 at 10:27, xxxxxxxx wrote:

    Here is a link to the file (3-4 sec before reset occurs). OPTIMIZE test.c4d?dl=0

  • On 06/06/2017 at 12:54, xxxxxxxx wrote:

    def get_group_to_get(obj, datas) :
        buffer = list()
        for i in xrange(len(datas)) :
            if obj[c4d.ID_USERDATA, datas[i][0]]:
                obj[c4d.ID_USERDATA, datas[i][0]] = False
        return buffer

    Take 90% of the time. Moreover everytime the code is executed twice. I dont know why (Even if I remove c4d.EventAdd())

    @Scotta: You gonna miss us. And I really hope you will change your decision

  • On 06/06/2017 at 13:03, xxxxxxxx wrote:

    Weird. Maybe Niklas has some ideas?

  • On 06/06/2017 at 14:46, xxxxxxxx wrote:

    Sorry, I was kind of still at your original code where you call GetUserDataContainer() multiple times.
    The file you posted with gr4ph0s code actually uses GetUserDataContainer() only once per call and
    doesn't search for entries by iterating over all of them, so the dictionary approach isn't actually
    necessary anymore.

    I did a quick profile of the main() function (by renaming it to _main())

    # ...
    def _main() :
      # ...
    import profile
    import pstats
    def main() :
        p = profile.Profile()
        stats = pstats.Stats(p)

    and it appears that most time is spent in get_group_to_get().

            13 function calls in 0.027 seconds
       Ordered by: internal time
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
            1    0.017    0.017    0.017    0.017 'reset optimize Python':13(get_group_to_get)
            1    0.006    0.006    0.010    0.010 'reset optimize Python':5(reset)
            1    0.004    0.004    0.004    0.004 :0(GetUserDataContainer)
            1    0.000    0.000    0.027    0.027 'reset optimize Python':23(_main)
            1    0.000    0.000    0.027    0.027 profile:0(<function _main at 0x0000016D72D5A208>)
            2    0.000    0.000    0.000    0.000 :0(stdout_write)
            2    0.000    0.000    0.000    0.000 <string>:17(write)
            1    0.000    0.000    0.000    0.000 :0(setprofile)
            1    0.000    0.000    0.000    0.000 :0(GetObject)
            1    0.000    0.000    0.000    0.000 :0(append)
            1    0.000    0.000    0.000    0.000 :0(len)
            0    0.000             0.000          profile:0(profiler)

    We can rewrite get_group_to_get() as

    def get_group_to_get(obj, datas) :
        bc = obj.GetDataInstance().GetContainerInstance(c4d.ID_USERDATA)
        buffer = list()
        for item in datas:
            if bc.GetBool(item[0]) :
                bc.SetBool(item[0], False)
        return buffer

    as setting parameters using [] is actually quite slow (although, usually the preferred method as
    some parameters are not actually stored in the container or special processing is necessary
    when they're being set or retrieved).

            121 function calls in 0.012 seconds
       Ordered by: internal time
       ncalls  tottime  percall  cumtime  percall filename:lineno(function)
            1    0.006    0.006    0.011    0.011 'reset optimize Python':5(reset)
            1    0.005    0.005    0.005    0.005 :0(GetUserDataContainer)
          110    0.000    0.000    0.000    0.000 :0(GetBool)
            1    0.000    0.000    0.000    0.000 'reset optimize Python':13(get_group_to_get)
            1    0.000    0.000    0.012    0.012 profile:0(<function _main at 0x0000016D72D5AF98>)
            1    0.000    0.000    0.011    0.011 'reset optimize Python':23(_main)
            1    0.000    0.000    0.000    0.000 :0(GetDataInstance)
            1    0.000    0.000    0.000    0.000 :0(GetContainerInstance)
            1    0.000    0.000    0.000    0.000 :0(setprofile)
            1    0.000    0.000    0.000    0.000 :0(SetBool)
            1    0.000    0.000    0.000    0.000 :0(GetObject)
            1    0.000    0.000    0.000    0.000 :0(append)
            0    0.000             0.000          profile:0(profiler)

    As you can see, it reduces the execution time of get_group_to_get() from 0.017s to 0.000s (rounded).
    However, the "reset Tag" is not the root of the delay. It only executes for some 0.03s before
    the optimiziation, and 0.012s after the optimization. It is the sum of everything in your setup.
    The rig alone, without any Python tags (not counting the ones that might be hidden in your
    hierarchy) causes a lag when I try to select the "nEOn" object.


    PS: Here's a page on how to interpret the profiling stats:

  • On 06/06/2017 at 15:13, xxxxxxxx wrote:

    Thanks you niklas I used a lot your profilling methode you describe in your blog. It's very powerfull !! :)

    Didn't thinked about getting data from GetDataInstance. Learned a lot thanks.

  • On 06/06/2017 at 15:42, xxxxxxxx wrote:

    My bloated scene! I will work on getting it to lose some weight. Thanks very much for taking the time with this, Niklas. And you too, gr4ph0s. :)

  • On 07/06/2017 at 09:19, xxxxxxxx wrote:

    Being one of the moderators of this forum, I think, I have to point out violations of our rules. Swearing also as getting on a personal level belong into this category.

    Furthermore we (MAXON's SDK Team) would like to have these forums as a source of information. So if we see different users posting different solutions on the same question, we are of the opinion, we (as the ones with some more insight to the actual product) need to point out the more correct solution or even put our fingers on issues in a posted solution. This is not meant to insult anybody, but in order to point future readers to the correct solution. And usually (at least we hope so) this adds to the learning experience for everyone involved.

    We do have no preference of any users or parts of our community. We try to provide everybody with roughly the same amount of attention and try to solve technical issues as best as we can, sharing our knowledge about C4D and its API. And yes, we also dare to say, if something is technically wrong. Just as we appreciate, if we get corrected, when we do make mistakes (and we certainly do).

    So far the few feedback we received was extremely positive, but I guess that doesn't mean much. So, if anybody shares such bad feelings or has other critique, please, go ahead, open a thread under "General Discussion" and tell us about it. We can only correct something, if we know about it.

Log in to reply