Set TreeView values dynamically
-
On 12/04/2018 at 08:21, xxxxxxxx wrote:
Hi everyone,
I was having a bit of experimentation with the TreeView custom GUI, which btw is preety awesome, but having some difficulty in setting the values correctly to each column.
So will explain the ideas/problems that I have which hopefully someone will be able to help.
Process 1: You add the selected objects to a list, by pressing the "Add" button.
Process 2: Each selected object and values populate the list, in the correct column.
Issue 2: When I create a list, can't distribute the values correctly to each column. As per the final result example above.It may be related with some of the code below (taken from a good example by Maxime: https://developers.maxon.net/forum/topic/10654/14102_using-customgui-listview&KW=tree&PID=56287#56287)
I can see that only works for one value, but would like set the 3 values.
class PickObjs(object) : objSel = "" _selected = False def __init__(self, objSel) : self.objSel = objSel print self.objSel <-- PRINTS CORRECTLY THE OBJECT AND VALUES! @property def IsSelected(self) : return self._selected def Select(self) : self._selected = True def Deselect(self) : self._selected = False def __repr__(self) : return str(self) def __str__(self) : return self.objSel <--- CAN ONLY RETURN A STRING AT THE TIME?
Process 3: When changing the ComboBox value, change the axis value in list for check marked objects.
Issue 3: Need to have the values in the correct place and access it to make the change. Not sure how this would be done?Hopefully this is comprehensive, but please let me know if don't know what I'm talking about .
Thank you in advance!
Andre
-
On 13/04/2018 at 04:58, xxxxxxxx wrote:
Hi Andre,
we expect the issue rather in your implementation of the TreeViewFunctions. Maybe you can show us some code of these?
-
On 13/04/2018 at 05:31, xxxxxxxx wrote:
Hi Andreas,
Yeah sure! Let me know if you need anything else!
# Global Variables PLUGIN_VERSION = 'v1.0' PLUGIN_ID = 1000010 #TESTING ID GRP_COMBOXFILES = 1002 GRP_COMBOAXIS = 1003 GRP_ADDBTN = 1004 GRP_AUTOBTN = 1005 GRP_RUNBTN = 1006 OBJLIST = 1021 STATUS = 1031 INITIAL_WIDTH = 100 INITIAL_HEIGHT = 20 TREE_CHECK = 1 TREE_OBJ = 2 TREE_AXIS = 3 TREE_FUNC = 4 PATH = storage.GeGetStartupWritePath() + '/plugins/AO_Tools/eXpressoMachine/res/modules/' XNODESPATH = sys.path.append(storage.GeGetStartupWritePath() + '/plugins/AO_Tools/eXpressoMaker/res/modules') MAINBC = c4d.BaseContainer() ################## # Class which represent an object, aka an item in our list class PickObjs(object) : objSel = "" _selected = False def __init__(self, objSel) : self.objSel = objSel print self.objSel @property def IsSelected(self) : return self._selected def Select(self) : self._selected = True def Deselect(self) : self._selected = False def __repr__(self) : return str(self) def __str__(self) : return self.objSel class ListView(c4d.gui.TreeViewFunctions) : def __init__(self) : self.listOfObjs = list() # Store all objects we need to display in this list def IsResizeColAllowed(self, root, userdata, lColID) : return True def IsTristate(self, root, userdata) : return False def changeAxis(self, obj) : print obj # The user is allowed to move all columns. # TREEVIEW_MOVE_COLUMN must be set in the container of AddCustomGui. def IsMoveColAllowed(self, root, userdata, lColID) : return False # Return the first element in the hierarchy, or None if there is no element. def GetFirst(self, root, userdata) : rValue = None if not self.listOfObjs else self.listOfObjs[0] return rValue # Return a child of a node, since we only want a list, we return None everytime def GetDown(self, root, userdata, obj) : return None # Returns the next Object to display after arg:'obj' def GetNext(self, root, userdata, obj) : rValue = None currentObjIndex = self.listOfObjs.index(obj) nextIndex = currentObjIndex + 1 if nextIndex < len(self.listOfObjs) : rValue = self.listOfObjs[nextIndex] return rValue # Returns the previous Object to display before arg:'obj' def GetPred(self, root, userdata, obj) : rValue = None currentObjIndex = self.listOfObjs.index(obj) predIndex = currentObjIndex - 1 if 0 <= predIndex < len(self.listOfObjs) : rValue = self.listOfObjs[predIndex] return rValue # Return a unique ID for the element in the TreeView. def GetId(self, root, userdata, obj) : return hash(obj) # Called when the user selects an element. def Select(self, root, userdata, obj, mode) : if mode == c4d.SELECTION_NEW: for tex in self.listOfObjs: tex.Deselect() obj.Select() elif mode == c4d.SELECTION_ADD: obj.Select() elif mode == c4d.SELECTION_SUB: obj.Deselect() def IsSelected(self, root, userdata, obj) : """ Returns: True if *obj* is selected, False if not. """ return obj.IsSelected # Called when the user clicks on a checkbox for an object in a c4d.LV_CHECKBOX` column. def SetCheck(self, root, userdata, obj, column, checked, msg) : if checked: obj.Select() else: obj.Deselect() # Returns: (int) : Status of the checkbox in the specified *column* for *obj*. def IsChecked(self, root, userdata, obj, column) : if obj.IsSelected: return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED else: return c4d.LV_CHECKBOX_ENABLED # Returns the name to display for arg:'obj', only called for column of type LV_TREE def GetName(self, root, userdata, obj) : return str(obj) # Create all context menu values (Mouse Right Click) def CreateContextMenu(self, root, userdata, obj, lColumn, bc) : bc.RemoveData(900001) # Remove all option # Draw into a Cell, only called for column of type LV_USER def DrawCell(self, root, userdata, obj, col, drawinfo, bgColor) : if col == TREE_OBJ: name = obj.GetName() geUserArea = drawinfo["frame"] w = geUserArea.DrawGetTextWidth(name) h = geUserArea.DrawGetFontHeight() xpos = drawinfo["xpos"] ypos = drawinfo["ypos"] + drawinfo["height"] drawinfo["frame"].DrawText(name, xpos, ypos - h * 1.1) elif col == TREE_AXIS: name = obj.GetName() geUserArea = drawinfo["frame"] w = geUserArea.DrawGetTextWidth(name) h = geUserArea.DrawGetFontHeight() xpos = drawinfo["xpos"] ypos = drawinfo["ypos"] + drawinfo["height"] drawinfo["frame"].DrawText(name, xpos, ypos - h * 1.1) elif col == TREE_FUNC: name = obj.GetName() geUserArea = drawinfo["frame"] w = geUserArea.DrawGetTextWidth(name) h = geUserArea.DrawGetFontHeight() xpos = drawinfo["xpos"] ypos = drawinfo["ypos"] + drawinfo["height"] drawinfo["frame"].DrawText(name, xpos, ypos - h * 1.1) # Called when a delete event is received. def DeletePressed(self, root, userdata) : for obj in reversed(self.listOfObjs) : if obj.IsSelected: self.listOfObjs.remove(obj) # Main class for the plugin dialog (UI). class eXpressoMakerDialog(gui.GeDialog) : _treegui = None # Our CustomGui TreeView _listView = ListView() # Our Instance of c4d.gui.TreeViewFunctions # Create the dialog's layout def CreateLayout(self) : self.SetTitle('eXpresso Machine ' + PLUGIN_VERSION) # Title name ################### Main group container for all the widgets. self.GroupBegin(10000, c4d.BFV_SCALEFIT | c4d.BFH_SCALEFIT, 1, 1, 'MainGroup') ################ Combo box that gets the sub-modules from the modules folder. self.GroupBegin(10001, c4d.BFH_SCALEFIT, 1, 1, 'Function') self.GroupBorder(c4d.BORDER_GROUP_OUT) self.GroupBorderSpace(10, 10, 10, 10) self.AddComboBox(GRP_COMBOXFILES, c4d.BFH_SCALEFIT, INITIAL_WIDTH, INITIAL_HEIGHT) # Get all the modules in list and add it as a child of the combo box. filesSubID = 0 for file in listFiles() : self.AddChild(GRP_COMBOXFILES, filesSubID, file) filesSubID += 1 self.GroupEnd() ################ ################ Selection group container. self.GroupBegin(10002, c4d.BFV_SCALEFIT | c4d.BFH_SCALEFIT, 2, 1, 'Selection') self.GroupBorder(c4d.BORDER_GROUP_OUT) self.GroupBorderSpace(10, 10, 10, 10) ############# Main menu buttons group container. self.GroupBegin(10020, c4d.BFV_SCALEFIT, 1, 1, 'Menu') ########## Axis layout group container, combo box and button. self.GroupBegin(10021, c4d.BFH_SCALEFIT, 1, 1, 'Axis') self.GroupBorder(c4d.BORDER_GROUP_OUT) self.GroupBorderSpace(10, 10, 10, 10) self.AddComboBox(GRP_COMBOAXIS, c4d.BFV_SCALEFIT | c4d.BFH_SCALEFIT, 30, INITIAL_HEIGHT) # Get all the axis in list and add it as a child of the combo box. axisID = 0 global axisList axisList = ['X', 'Y', 'Z'] for axis in axisList: self.AddChild(GRP_COMBOAXIS, axisID, axis) axisID += 1 self.GroupEnd() ########## ########## Objects layout group container and function buttons. self.GroupBegin(10022, c4d.BFH_SCALEFIT, 1, 1, 'Objects') self.GroupBorder(c4d.BORDER_GROUP_OUT) self.GroupBorderSpace(10, 10, 10, 10) self.AddButton(GRP_AUTOBTN, c4d.BFV_SCALEFIT | c4d.BFH_SCALEFIT, INITIAL_WIDTH, INITIAL_HEIGHT, 'Auto-Find') self.AddButton(GRP_ADDBTN, c4d.BFV_SCALEFIT | c4d.BFH_SCALEFIT, INITIAL_WIDTH, INITIAL_HEIGHT, 'Add') self.GroupEnd() ########## self.GroupEnd() ############# ############# Object TreeView container, where list of object, axis and function selection will be displayed. Each line is selectable and mutable. self.GroupBegin(10023, c4d.BFV_SCALEFIT | c4d.BFH_SCALEFIT, 1, 1, 'Object List') treeViewGUI = c4d.BaseContainer() treeViewGUI.SetBool(c4d.TREEVIEW_BORDER, c4d.BORDER_THIN_IN) treeViewGUI.SetBool(c4d.TREEVIEW_BORDER, True) treeViewGUI.SetBool(c4d.TREEVIEW_HAS_HEADER, True) treeViewGUI.SetBool(c4d.TREEVIEW_HIDE_LINES, True) treeViewGUI.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True) treeViewGUI.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True) treeViewGUI.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True) treeViewGUI.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True) self._treegui = self.AddCustomGui(OBJLIST, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 400, 0, treeViewGUI) self.GroupEnd() ############# self.GroupEnd() ################ ################ Status group container, where informs the user about the function actions. self.GroupBegin(10003, c4d.BFH_SCALEFIT, 1, 1) self.AddStaticText(STATUS, c4d.BFV_SCALEFIT | c4d.BFH_SCALEFIT) self.GroupEnd() ################ ################ Run group container that includes the main button to run all of the list functions. self.GroupBegin(10004, c4d.BFH_SCALEFIT, 1, 1) self.AddSeparatorV(INITIAL_WIDTH, c4d.BFV_SCALEFIT | c4d.BFH_SCALEFIT) self.AddButton(GRP_RUNBTN, c4d.BFV_SCALEFIT | c4d.BFH_SCALEFIT, INITIAL_WIDTH, INITIAL_HEIGHT, 'Run') self.GroupEnd() ################ self.GroupEnd() ################### return True # Initialize default values def InitValues(self) : # Initialize the column layout for the TreeView. layout = c4d.BaseContainer() layout.SetLong(TREE_CHECK, c4d.LV_CHECKBOX) layout.SetLong(TREE_OBJ, c4d.LV_TREE) layout.SetLong(TREE_AXIS, c4d.LV_USER) layout.SetLong(TREE_FUNC, c4d.LV_USER) self._treegui.SetLayout(TREE_FUNC, layout) # Set the header titles. self._treegui.SetHeaderText(TREE_CHECK, "") self._treegui.SetHeaderText(TREE_OBJ, "Object") self._treegui.SetHeaderText(TREE_AXIS, "Axis") self._treegui.SetHeaderText(TREE_FUNC, "Function") self._treegui.Refresh() # Set TreeViewFunctions instance used by our CUSTOMGUI_TREEVIEW self._treegui.SetRoot(self._treegui, self._listView, None) return True # Iterate through all the scene objects that include the words 'PIVOT' and 'MOVE' and append it to a list. def getObjs(self, op, output) : while op: if 'PIVOT' in op.GetName().upper() or 'MOVE' in op.GetName().upper() : output.append(op) self.getObjs(op.GetDown(),output) op = op.GetNext() return output # Return the correct ID for the axis selected. def getAxis(self, axis) : selAxis = {'X' : 'c4d.VECTOR_X', # ID: 1000 'Y' : 'c4d.VECTOR_Y', # ID: 1001 'Z' : 'c4d.VECTOR_Z'} # ID: 1002 return selAxis[axis] '''NOT USED def CoreMessage(self, id, msg) : return gui.GeDialog.CoreMessage(self, id, msg) ''' # Built-In function to run events when triggered by the dialog widgets. def Command(self, id, msg) : fileSelectedIndex = self.GetInt32(GRP_COMBOXFILES) # Get the function combo box index for the value selected. axisSelectedIndex = self.GetInt32(GRP_COMBOAXIS) # Get the axis combo box index for the value selected. doc = c4d.documents.GetActiveDocument() # Get active document. # Event trigger for the axis combo box and button. if (id == GRP_COMBOAXIS) : if self._listView.listOfObjs != []: i = 0 for obj in self._listView.listOfObjs: print self._listView.changeAxis(obj) self.SetString(STATUS, 'Axis changed!') # Event run, show the event message to the user. else: self.SetString(STATUS, 'No objects to change the axis!') # If no objects are on list, show the event message to the user. # Event trigger for the add button. if (id == GRP_ADDBTN) : selObjs = doc.GetActiveObjects(0) # Get a list of selected objects. if selObjs != []: # Add data to our DataStructure (ListView) for obj in selObjs: newID = len(self._listView.listOfObjs) + 1 objName = obj.GetName() axisName = axisList[axisSelectedIndex].upper() funcName = str(files[fileSelectedIndex]) self._listView.listOfObjs.append([objName, axisName, funcName]) for objSel in self._listView.listOfObjs: for value in objSel: PickObjs(value) # Refresh the TreeView self._treegui.Refresh() self.SetString(STATUS, 'Object(s) added!') # Event run, show the event message to the user. else: self.SetString(STATUS, 'No objects selected!') # If no objects are on list, show the event message to the user. if (id == GRP_AUTOBTN) : parentObjs = doc.GetObjects() # Get a list of all parent objects of the scene. selObjs = [] if parentObjs != []: # Get all the objects that have the word 'PIVOT' or 'MOVE' on its name. # Case sensitivity is not a a problem, as the function converts the name to upper case. self.getObjs(parentObjs[0], selObjs) if selObjs != []: # Add data to our DataStructure (ListView) for obj in selObjs: newID = len(self._listView.listOfObjs) + 1 objName = PickObjs(obj.GetName(), axisList[axisSelectedIndex].upper(), str(files[fileSelectedIndex])) self._listView.listOfObjs.append(objName) # Refresh the TreeView self._treegui.Refresh() self.SetString(STATUS, 'Object(s) Added') # Event run, show the event message to the user. else: self.SetString(STATUS, 'Could not find any objects!') # If no objects are on list, show the event message to the user. else: self.SetString(STATUS, 'Could not find any objects!') # If no objects are on list, show the event message to the user. if (id == GRP_COMBOXFILES) : prevMsg = self.GetString(OBJLIST) # Get the list (message) from the multi-line widget. if prevMsg != '': self.SetString(STATUS, 'Function changed to ' + str(files[fileSelectedIndex])) # Event run, show the event message to the user. else: self.SetString(STATUS, 'No objects selected!') # If no objects are on list, show the event message to the user. if (id == GRP_RUNBTN) : prevMsg = self.GetString(OBJLIST) # Get the list (message) from the multi-line widget. if prevMsg != '': with localimport('res/modules') : lines = prevMsg.split('\n') # Split the line by the new line character into different sections. del lines[-1] # Delete the last empty line. lineIndex = 0 for line in lines: wordList = line.split() # Split the line into different sections. # Freeze the transformations for of the object. for id, value in MAINBC: if id == lineIndex: objBC = MAINBC.GetData(id) for id, value in objBC: print id, value if wordList[-1] not in sys.modules: __import__(wordList[-1]) # Call the correct model to run. else: reload(__import__(wordList[-1])) # If the module modified, reload the model and run it. lineIndex += 1 self.SetString(STATUS, 'Rig done!') # Event run, show the event message to the user. else: self.SetString(STATUS, 'No objects selected!') # If no objects are on list, show the event message to the user. return True # Return the relevant list of objects that contain the file extension '.py' # and do not start with the file name '__innit__' def listFiles() : global files files = [] if os.path.exists(PATH) : for file in os.listdir(PATH) : if file.endswith('.py') and os.path.splitext(file)[0] != '__init__': files.append(os.path.splitext(file)[0]) return files # Open the plugin in a new window when the main plugin function is called class eXpressoMakerPlugin(plugins.CommandData) : dialog = None # Creates the dialog def Execute(self, doc) : if self.dialog is None: self.dialog = eXpressoMakerDialog() return self.dialog.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaultw=300, defaulth=150, xpos=-1, ypos=-1) # Manages the dialog def RestoreLayout(self, sec_ref) : if self.dialog is None: self.dialog = eXpressoMakerDialog() return self.dialog.Restore(PLUGIN_ID, secret=sec_ref) if __name__ == '__main__': path, fn = os.path.split(__file__) bmp = bitmaps.BaseBitmap() # We need an instance of BaseBitmap class to use an image bmp.InitWith(os.path.join(path, 'res/icons/', 'icon.tif')) # The location where the menu image exists okyn = plugins.RegisterCommandPlugin(PLUGIN_ID, 'eXpresso Machine ' + PLUGIN_VERSION, 0, bmp, 'eXpresso Machine ' + PLUGIN_VERSION, eXpressoMakerPlugin()) if (okyn) : print 'eXpresso Machine ' + PLUGIN_VERSION + ' Loaded!' c4d.StatusSetText('eXpresso Machine ' + PLUGIN_VERSION + ' Loaded!')
Thank you very much Andreas!
-
On 18/04/2018 at 09:23, xxxxxxxx wrote:
Hi Andre,
terribly sorry, I need to ask for a bit more patience. I'll get back to you as soon as possible. Hopefully still this week.
-
On 19/04/2018 at 00:51, xxxxxxxx wrote:
Hi Andreas,
No need to apologise! I appreciate your help!
Thank you very much again!
-
On 20/04/2018 at 06:07, xxxxxxxx wrote:
Hi Andre,
I think, the main problem of your code is, that you are actually storing tuples in the tree content list, not "PickObjs" (as all the rest of your code assumes). This causes all kinds of problems all over the place. This you will need to either debug yourself or ask someone in our community for help.
This is also the reason, why you get the entire tuple shown in the first column.
GetName() delivers only the content of the LV_TREE column (basically the main column).
All LV_USER columns you need to draw yourself in DrawCell(). Which again doesn't work in your code, because the tuples stored in the list have no GetName() function. Also DrawCell() needs to draw only the LV_USER columns, no need to take care of TREE_OBJ in there.One general advice:
Pay attention to any errors on the Console. These can usually get you already a long way.I hope, this helps to get you started.
-
On 23/04/2018 at 05:55, xxxxxxxx wrote:
Hi Andreas,
Thank you immensely for your help!
I've managed to correct the code and make it do what I wanted, with your advice, but (apologies if I didn't understand) how can I access the the data for each "cell"? I will get the LV_TREE obj but not the LV_USER data.
I understand that the list is only appending the LV_TREE obj and not the other two, because it is returned by the function.
My question would be how to attach the other LV_USER values to the same list, so I can use it in a later stage?
If this is not possible, should I create a new list/baseContainer?Thank you so much!
Andre
-
On 23/04/2018 at 08:12, xxxxxxxx wrote:
Hi Andre,
I'm not sure I understand your question. if I fail to answer your question, please don't hesitate to ask again.
You need to differentiate two things. One is your data (i.e. a linked list of objects or an array of your PickObjects) and the other is the display in the TreeView.
The tree doesn't care for your data, but instead you need to tell it, what to display in the LV_TREE column (via GetName()) and for all other columns (via DrawCell()). In either function you get the obj parameter, which from the tree point of view is just an opaque reference (it's not used inside by the tree), which provides you with the means to pick whatever needed from your data.
Or to answer your question "how can I access the the data for each cell?" more directly: You don't. The tree does not store any data, it just displays your data.
Similar in the other direction. For example on left click onto the Axis column, you want to have a popup with axis options. Well, you again get the obj reference in MouseDown(). Then you can for example use ShowPopupDialog() and with the result you'd update your data or the entities referred to by your data. Of course you could achieve similar by using the tree's context menu directly (CreateContextMenu()).
Unfortunately here's also a small limitation of the Python API. The C++ API has PopupEditText(), which would allow for overlay text editing, roughly as shown in your screenshot, but that's currently missing in the Python API.
-
On 24/04/2018 at 01:06, xxxxxxxx wrote:
Hi Andreas,
You did answer it and you also gave me some alternative and ideas to use.
Thank you so much! It makes more sense, now.
I'm still getting the grips with Python, but it seems that C++ is the way to go as well.Appreciate your patience and help!
Have a great day!
Andre