How is the structure manager working?

On 23/03/2013 at 16:16, xxxxxxxx wrote:

Hello everybody,

i think its a bit difficulte to explain what I want, but Ill try to do my best. I wrote a tagdata to be able to work with large data tables. The result of the the tagdata is the variable list_.

import os  
import sys  
import c4d  
  
from c4d import plugins, utils, bitmaps, gui  
import datetime  
  
# be sure to use a unique ID obtained from www.plugincafe.com  
PLUGIN_ID = 10000045 #TEST ID  
  
def ValInType(o_val) :  
  val = o_val  
  if "," in o_val: o_val = o_val.replace(",", ".")  
  try: return float(o_val)  
  except ValueError: return str(val)  
  
class PlgTAG(plugins.TagData) :  
  
  list_ = []  
  
  def Init(self, node) :  
      return True  
  
  def Message(self, node, type, data) :  
      if type==c4d.MSG_DESCRIPTION_COMMAND:  
          if data['id'][0].id==10002:   
              self.LoadList(node)  
              self.GetVal(node)  
              self.FillPorts(node)  
      return True  
  
  def LoadList(self, op) :  
      try: txt = open(op[10001], 'r')  
      except IOError:   
          gui.MessageDialog("File is not readable.")  
          return  
  
      lns = txt.readlines()  
      txt.close()  
      self.list_ = []  
      self.lncnt = len(lns)  
      ccnt = 0  
  
      for ln in lns:  
          cols = ln.split(";")  
          if ccnt < len(cols) : ccnt =  len(cols) #Just to get the largest columne count  
  
      for ii in range(len(lns)) :  
          new_ln = []  
          cols = lns[ii][0:len(lns[ii])-1].split(";")  
          for i in range(ccnt) :  
              if i < len(cols) : new_ln.append(ValInType(cols[i]))  
              else: new_ln.append("None")  
          self.list_.append(new_ln)  
  
      self.colcnt = ccnt  
  
      return  
  
  
if __name__ == "__main__":  
   
  dir, file = os.path.split(__file__)  
  bmp = bitmaps.BaseBitmap()  
  bmp.InitWith(os.path.join(dir, "res", "icon.tif"))  
  
  plugins.RegisterTagPlugin(id=PLUGIN_ID,  
                           str="PlgTAG",  
                           g=PlgTAG,  
                           description="PlgTAG",  
                           icon=bmp,  
                           info=c4d.TAG_MULTIPLE|c4d.TAG_EXPRESSION|c4d.TAG_VISIBLE)

Now Id like to visible list_ in a commanddata.  I can get list_ from somewhere else with:

class GetValues(object) :  
  def __init__(self, list_=None, lncnt = 0, colcnt = 0) :  
      super(GetValues, self).__init__()  
      self.list_ = list_  
      self.lncnt = lncnt  
      self.colcnt = colcnt  
  
# tag is the tagdata  
list_obj = GetValues()  
tag.Message(10000045, list_obj)  
list_ = list_obj.list_  
lncnt = list_obj.lncnt  
colcnt = list_obj.colcnt

(https://plugincafe.maxon.net/topic/7036/7946_how-to-get-a-variable-from-plugintag  -> Thanks to littledevil and niklas)

The commanddata:

import c4d  
from c4d import bitmaps, gui, plugins, utils  
import collections, os  
  
PLUGIN_ID = 10000055 #TestID only!!!!!!!!!!!!  
  
class GetValues(object) :  
  def __init__(self, list_=None, lncnt = 0, colcnt = 0) :  
      super(GetValues, self).__init__()  
      self.list_ = list_  
      self.lncnt = lncnt  
      self.colcnt = colcnt  
  
class MyDialog(gui.GeDialog) :  
  
  tag = None  
  list_ = []  
  lncnt = 10  
  colcnt = 10  
  
  def CreateLayout(self) :  
      ln = 0  
      col = 0  
      element = 0  
      cntr = 0  
      hole = (self.lncnt+1)*(self.colcnt+1)  
  
      self.ScrollGroupBegin(  
          id=0,   
          flags=c4d.BFH_SCALEFIT|c4d.BFV_SCALEFIT,  
          scrollflags=c4d.SCROLLGROUP_VERT | c4d.SCROLLGROUP_HORIZ,  
          initw=300,   
          inith=50  
          )  
      self.GroupBegin(id=1, flags=c4d.BFV_TOP|c4d.BFH_LEFT, title="", cols=self.colcnt+1)  
  
      for i in range(self.lncnt+1) :  
        for ii in range(self.colcnt+1) :  
          cntr += 1  
          if i == 0:   
            if ii == 0: self.AddStaticText(id=i+2, flags=c4d.BFH_LEFT, name="", borderstyle=c4d.BORDER_NONE)  
            else:   
              self.AddStaticText(id=i+2, flags=c4d.BFH_LEFT, initw=100, name=str(col), borderstyle=c4d.BORDER_THIN_OUT)  
              col += 1  
          else:  
            if ii == 0:   
              self.AddStaticText(id=i+2, flags=c4d.BFH_LEFT, initw=100, name=str(ln), borderstyle=c4d.BORDER_THIN_OUT)  
              ln += 1  
            else:  
              a = int(float(element)/self.colcnt)  
              b = element - (a*self.colcnt)  
              self.AddStaticText(id=i+2, flags=c4d.BFH_LEFT, initw=100, name=str(self.list_[a][b]), borderstyle=c4d.BORDER_THIN_IN)  
              element += 1  
  
      self.GroupEnd()  
      self.GroupEnd()  
  
      return True  
  
  def Command(self, id, msg) :  
      return True  
  
  def CoreMessage(self,id,msg) :  
      if id == c4d.EVMSG_CHANGE or id == 1970300003:  
        activeTag = c4d.documents.GetActiveDocument().GetActiveTag()  
        if self.tag != activeTag:  
  
          if activeTag == None:  
            self.list_ = []  
            self.lncnt = 0  
            self.colcnt = 0  
  
          elif activeTag.GetType() != 10000045:  
            self.list_ = []  
            self.lncnt = 0  
            self.colcnt = 0  
  
          else:  
            self.tag = activeTag  
            list_obj = GetValues()  
            self.tag.Message(10000045, list_obj)  
            self.list_ = list_obj.list_  
            self.lncnt = list_obj.lncnt  
            self.colcnt = list_obj.colcnt  
  
          self.tag = activeTag  
          self.LayoutFlushGroup(0)  
          self.CreateLayout()  
          self.InitValues()  
          self.LayoutChanged(0)  
  
      return True  
  
class Test(plugins.CommandData) :  
  dialog = None  
  def Execute(self, doc) :  
      if self.dialog is None: self.dialog = MyDialog()  
      return self.dialog.Open(dlgtype=c4d.DLG_TYPE_ASYNC, pluginid=PLUGIN_ID, defaultw=1, defaulth=1)  
  def RestoreLayout(self, sec_ref) :  
      if self.dialog is None: self.dialog = MyDialog()  
      return self.dialog.Restore(pluginid=PLUGIN_ID, secret=sec_ref)  
  
if __name__ == "__main__":  
   bmp = bitmaps.BaseBitmap()  
   dir, f = os.path.split(__file__)  
   fn = os.path.join(dir, "res", "icon.tif")  
   bmp.InitWith(fn)  
   plugins.RegisterCommandPlugin(id=PLUGIN_ID,   
                                str="Test",  
                                info=0,  
                                help="Test",   
                                dat=Test(),  
                                icon=bmp)

If the active tag of the document is a TableTag, the commanddata shows the table. If not, the commendata is empty. If the active tag is changed from one TableTag to an other the commanddata is refreshing its one and the table of the new active TableTag is shown. So far, so good.
It works well, but this way simply takes to long for refreshing. I tested with 12.000 element (1000 line, 12 columnes, 1.8sec.) and 120.000 elements (10000 lines, 12columnes, more than 4min.).

So my question is: How is the structure manager working? I think it is a commanddata, too. And it gets the information from the pointtag of the active object.

I hope I explained comprehensibly.

Thanks for help
Greetings
rown

On 23/03/2013 at 16:50, xxxxxxxx wrote:

Hi rown,

just some points/thoughts:

  1. I don't see where you fill in the GetValues object you are passing to the tag's Message() method. Did I miss it?
  2. I recommend you to use symbols instead of integral numbers. Eg. PLUGIN_ID in place where 10000045 is used.
  3. If I understood you correctly: It is the dialog that changes, not the CommandData. Just to clear the missunderstandings here.
  4. You can not expect the same performance as the structure dialog. It is using an optimized GUI widget and is written in C++.

Best regards,
-Niklas

On 23/03/2013 at 17:08, xxxxxxxx wrote:

your code isn't very well commented, so it is kind of hard to tell what does make 
your code so slow. i guess there are multiple reasons, but a pretty obvious reason 
is that you are adding each data element as a seperate dialog element, so that your 
gedialog has to display and manage 120 000 (statictext) members.

c4d managers are generally speaking pretty sealed and we do not have much access 
to them. when you have to display more complex data structures the common approach 
would be to create a  ressource element which can display and manage this data on a 
lower level. as we do neither have access to CustomDataType/CustumGuiData in python 
nor we can use most of the existing CustomGui classes in python your best shot would 
be a UserAera. either by drawing each element into the Userarea or by creating a
large string and then just draw this string.

i think the structure manager uses a table to display its data and i think we cannot use
the table ressource element in python (not for sure, have never tested it).

On 23/03/2013 at 17:16, xxxxxxxx wrote:

I agree with littledevil, a UserArea might be the best (but not most comfortable) choice. Just
make sure you're just drawing what needs to be displayed and not all 120k rows.. o_O

Best,
Niklas

On 23/03/2013 at 17:18, xxxxxxxx wrote:

Hey Niklas,

I don't see where you fill in the GetValues object you are passing to the tag's Message() method. Did I or did you miss it?

Im sorry, but I do not really understand your question. GetValues() is called from Coremessage.

I recommend you to use symbols instead of integral numbers. Eg. PLUGIN_ID in place where 10000045 is used.

Yes, you are right. But until now it is a quick and dirty code. I waned it working befor well-looking.

If I understood you correctly: It is the dialog that changes, not the CommandData. Just to clear the missunderstandings here.

Yes, of course, the dialog changes.

You can not expect the same performance as the structure dialog. It is using an optimized GUI widget and is written in C++.

Of course. But Im sure there is a faster solution. For example: If I create the dialoge in the tagdata and the commanddata is getting it from there, the dialog just has to be created one time. But I dont know how to let the commanddata know that something has changed.

On 23/03/2013 at 17:34, xxxxxxxx wrote:

just another note, while this would actually slow your code down, rather than speeding
it up, the whole problem REALLY needs more modularization on the data level, instead 
of pushing arround a bunch of dicts, list and tupples. it would make it much easier to 
understand the code and add things like caching and displaying data in an efficient way.

about getvalues. i think the main missconeption here is that the name sounds and looks like 
a static method, while it is actually a class. you even fall yourself for this trap as you are saying 
'GetValues() is called from Coremessage.'

On 23/03/2013 at 18:22, xxxxxxxx wrote:

about getvalues. i think the main missconeption here is that the name sounds and looks like 
a static method, while it is actually a class. you even fall yourself for this trap as you are saying 
'GetValues() is called from Coremessage.'

You are right. I know that I rarly use the right terms. But I already have that problem in my mother tongue. Ill start working on it.

Im still not really sure to know what higher, lower or data level means. I should keep my eyes open for that. And I will take a look at UserArea.

Thanks you both
rown