Last active
April 2, 2020 22:06
-
-
Save NiklasRosenstein/632e39a9b4dda391fe54 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Example of using the Cinema 4D Tree View GUI in Python. | |
import c4d | |
import os | |
import weakref | |
# Be sure to use a unique ID obtained from http://www.plugincafe.com/. | |
PLUGIN_ID = 9912399 | |
PLUGIN_NAME = "Python TreeView Example" | |
PLUGIN_HELP = "Show the current scene hierarchy in a tree." | |
# Bit for the Checkbox in the GUI. Kind of a bad practice to use this | |
# but reduces complexity for this example. | |
BIT_CHECKED = 256 | |
S | |
# TreeView Column IDs. | |
ID_CHECKBOX = 1 | |
ID_ICON = 2 | |
ID_TREEVIEW = 3 | |
class Hierarchy(c4d.gui.TreeViewFunctions): | |
def __init__(self, dlg): | |
# Avoid a cyclic reference. | |
self._dlg = weakref.ref(dlg) | |
def GetFirst(self, root, userdata): | |
""" | |
Return the first element in the hierarchy. This can be any Python | |
object. With Cinema 4D nodes, it is easy to implement, but it could | |
as well be a Python integer which you can use as an index in a list. | |
Returns: | |
(any or None): The first element in the hierarchy or None. | |
""" | |
doc = c4d.documents.GetActiveDocument() | |
return doc.GetFirstObject() | |
def GetDown(self, root, userdata, obj): | |
""" | |
Returns: | |
(any or None): The child element going from *obj* or None. | |
""" | |
return obj.GetDown() | |
def GetNext(self, root, userdata, obj): | |
""" | |
Returns: | |
(any or None): The next element going from *obj* or None. | |
""" | |
return obj.GetNext() | |
def GetPred(self, root, userdata, obj): | |
""" | |
Returns: | |
(any or None): The previous element going from *obj* or None. | |
""" | |
return obj.GetPred() | |
def GetName(self, root, userdata, obj): | |
""" | |
Returns: | |
(str): The name to display for *obj*. | |
""" | |
return obj.GetName() | |
def Open(self, root, userdata, obj, opened): | |
""" | |
Called when the user opens or closes the children of *obj* in | |
the Tree View. | |
""" | |
doc = c4d.documents.GetActiveDocument() | |
doc.StartUndo() | |
doc.AddUndo(c4d.UNDOTYPE_CHANGE_SELECTION, obj) | |
if opened: | |
obj.SetBit(c4d.BIT_OFOLD) | |
else: | |
obj.DelBit(c4d.BIT_OFOLD) | |
doc.EndUndo() | |
c4d.EventAdd() | |
def IsOpened(self, root, userdata, obj): | |
""" | |
Returns: | |
(bool): True if *obj* is opened (expaneded), False if it is | |
closed (collapsed). | |
""" | |
return obj.GetBit(c4d.BIT_OFOLD) | |
def DeletePressed(self, root, userdata): | |
""" | |
Called when the user right-click Deletes or presses the Delete key. | |
Should delete all selected elements. | |
""" | |
def delete_recursive(op): | |
if op is None: return | |
if op.GetBit(c4d.BIT_ACTIVE) == True: | |
doc.AddUndo(c4d.UNDOTYPE_DELETE, op) | |
op.Remove() | |
return | |
for child in op.GetChildren(): | |
delete_recursive(child) | |
doc = c4d.documents.GetActiveDocument() | |
doc.StartUndo() | |
for obj in doc.GetObjects(): | |
delete_recursive(obj) | |
doc.EndUndo() | |
c4d.EventAdd() | |
def Select(self, root, userdata, obj, mode): | |
""" | |
Called when the user selects an element. | |
""" | |
doc = c4d.documents.GetActiveDocument() | |
doc.StartUndo() | |
def deselect_recursive(op): | |
if op is None: return | |
doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, op) | |
op.DelBit(c4d.BIT_ACTIVE) | |
for child in op.GetChildren(): | |
deselect_recursive(child) | |
if mode == c4d.SELECTION_SUB: | |
doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj) | |
obj.DelBit(c4d.BIT_ACTIVE) | |
else: | |
if mode == c4d.SELECTION_NEW: | |
deselect_recursive(obj.GetDocument().GetFirstObject()) | |
doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj) | |
obj.SetBit(c4d.BIT_ACTIVE) | |
doc.EndUndo() | |
c4d.EventAdd() | |
def IsSelected(self, root, userdata, obj): | |
""" | |
Returns: | |
(bool): True if *obj* is selected, False if not. | |
""" | |
return obj.GetBit(c4d.BIT_ACTIVE) | |
def DoubleClick(self, root, userdata, obj, col, mouseinfo): | |
""" | |
Called when the user double-clicks on an entry in the TreeView. | |
Returns: | |
(bool): True if the double-click was handled, False if the | |
default action should kick in. The default action will invoke | |
the rename procedure for the object, causing `SetName()` to be | |
called. | |
""" | |
c4d.gui.MessageDialog("You clicked on " + obj.GetName()) | |
return True | |
def IsResizeColAllowed(self, root, userdata, lColID): | |
return True | |
def IsTristate(self, root, userdata): | |
return False | |
def GetDragType(self, root, userdata, obj): | |
""" | |
Returns: | |
(int): The drag datatype. | |
""" | |
return c4d.DRAGTYPE_ATOMARRAY | |
def DragStart(self, root, userdata, obj): | |
""" | |
Returns: | |
(int): Bitmask specifying options for the drag, whether it is | |
allowed, etc. | |
""" | |
return c4d.TREEVIEW_DRAGSTART_ALLOW | c4d.TREEVIEW_DRAGSTART_SELECT | |
def GetId(self, root, userdata, obj): | |
""" | |
Return a unique ID for the element in the TreeView. | |
""" | |
return obj.GetUniqueID() | |
def SetName(self, root, userdata, obj, name): | |
""" | |
Called when the user renames the element. `DoubleClick()` must return | |
False for this to work. | |
""" | |
doc.StartUndo() | |
doc.AddUndo(c4d.UNDOTYPE_CHANGE_SMALL, obj) | |
doc.EndUndo() | |
obj.SetName(name) | |
c4d.EventAdd() | |
def DrawCell(self, root, userdata, obj, col, drawinfo, bgColor): | |
""" | |
Called for a cell with layout type `c4d.LV_USER` or `c4d.LV_USERTREE` | |
to draw the contents of a cell. | |
""" | |
if col == ID_ICON: | |
icon = obj.GetIcon() | |
drawinfo["frame"].DrawBitmap( | |
icon["bmp"], drawinfo["xpos"], drawinfo["ypos"], | |
16, 16, icon["x"], icon["y"], icon["w"], icon["h"], c4d.BMP_ALLOWALPHA) | |
def SetCheck(self, root, userdata, obj, column, checked, msg): | |
""" | |
Called when the user clicks on a checkbox for an object in a | |
`c4d.LV_CHECKBOX` column. | |
""" | |
if checked: | |
obj.SetBit(BIT_CHECKED) | |
else: | |
obj.DelBit(BIT_CHECKED) | |
def checked_collect(op): | |
if op is None: return | |
elif isinstance(op, c4d.documents.BaseDocument): | |
for obj in op.GetObjects(): | |
for x in checked_collect(obj): | |
yield x | |
else: | |
if op.GetBit(BIT_CHECKED): | |
yield op | |
for child in op.GetChildren(): | |
for x in checked_collect(child): | |
yield x | |
status = ', '.join(obj.GetName() for obj in checked_collect(obj.GetDocument())) | |
self._dlg().SetString(1001, "Checked: " + status) | |
self._dlg()._treegui.Refresh() | |
def IsChecked(self, root, userdata, obj, column): | |
""" | |
Returns: | |
(int): Status of the checkbox in the specified *column* for *obj*. | |
""" | |
val = obj.GetBit(BIT_CHECKED) | |
if val == True: | |
return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED | |
else: | |
return c4d.LV_CHECKBOX_ENABLED | |
def HeaderClick(self, root, userdata, column, channel, is_double_click): | |
""" | |
Called when the TreeView header was clicked. | |
Returns: | |
(bool): True if the event was handled, False if not. | |
""" | |
c4d.gui.MessageDialog("You clicked on the '%i' header." % (column)) | |
return True | |
def AcceptDragObject(self, root, userdata, obj, dragtype, dragobject): | |
""" | |
Called when a drag & drop operation hovers over the TreeView to check | |
if the drag can be accepted. | |
Returns: | |
(int, bool) | |
""" | |
if dragtype != c4d.DRAGTYPE_ATOMARRAY: | |
return 0 | |
return c4d.INSERT_BEFORE | c4d.INSERT_AFTER | c4d.INSERT_UNDER, True | |
def GenerateDragArray(self, root, userdata, obj): | |
""" | |
Return: | |
(list of c4d.BaseList2D): Generate a list of objects that can be | |
dragged from the TreeView for the `c4d.DRAGTYPE_ATOMARRAY` type. | |
""" | |
if obj.GetBit(c4d.BIT_ACTIVE): | |
return [obj, ] | |
def InsertObject(self, root, userdata, obj, dragtype, dragobject, insertmode, bCopy): | |
""" | |
Called when a drag is dropped on the TreeView. | |
""" | |
if dragtype != c4d.DRAGTYPE_ATOMARRAY: | |
return # Shouldnt happen, we catched that in AcceptDragObject | |
for op in dragobject: | |
op.Remove() | |
if insertmode == c4d.INSERT_BEFORE: | |
op.InsertBefore(obj) | |
elif insertmode == c4d.INSERT_AFTER: | |
op.InsertAfter(obj) | |
elif insertmode == c4d.INSERT_UNDER: | |
op.InsertUnder(obj) | |
return | |
def GetColumnWidth(self, root, userdata, obj, col, area): | |
return 80 # All have the same initial width | |
def IsMoveColAllowed(self, root, userdata, lColID): | |
# The user is allowed to move all columns. | |
# TREEVIEW_MOVE_COLUMN must be set in the container of AddCustomGui. | |
return True | |
def GetColors(self, root, userdata, obj, pNormal, pSelected): | |
""" | |
Retrieve colors for the TreeView elements. | |
Returns: | |
(int or c4d.Vector, int or c4d.Vector): The colors for the normal | |
and selected state of the element. | |
""" | |
usecolor = obj[c4d.ID_BASEOBJECT_USECOLOR] | |
if usecolor == c4d.ID_BASEOBJECT_USECOLOR_ALWAYS: | |
pNormal = obj[c4d.ID_BASEOBJECT_COLOR] | |
return pNormal, pSelected | |
# Context menu entry for "Hello World!" | |
ID_HELLOWORLD = 355435345 | |
def CreateContextMenu(self, root, userdata, obj, lColumn, bc): | |
""" | |
User clicked with the right mouse button on an entry so | |
we can here enhance the menu | |
""" | |
bc.SetString(self.ID_HELLOWORLD, "Hello World!") | |
def ContextMenuCall(self, root, userdata, obj, lColumn, lCommand): | |
""" | |
The user executes an entry of the context menu. | |
Returns: | |
(bool): True if the event was handled, False if not. | |
""" | |
if lCommand == self.ID_HELLOWORLD: | |
obj.SetName("Hello World!") | |
c4d.EventAdd() | |
return True | |
return False | |
def SelectionChanged(self, root, userdata): | |
""" | |
Called when the selected elements in the TreeView changed. | |
""" | |
print "The selection changed" | |
def Scrolled(self, root, userdata, h, v, x, y): | |
""" | |
Called when the TreeView is scrolled. | |
""" | |
self._dlg().SetString(1002, ("H: %i V: %i X: %i Y: %i" % (h, v, x, y))) | |
class TestDialog(c4d.gui.GeDialog): | |
_treegui = None | |
def CreateLayout(self): | |
# Create the TreeView GUI. | |
customgui = c4d.BaseContainer() | |
customgui.SetBool(c4d.TREEVIEW_BORDER, True) | |
customgui.SetBool(c4d.TREEVIEW_HAS_HEADER, True) | |
customgui.SetBool(c4d.TREEVIEW_HIDE_LINES, False) | |
customgui.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True) | |
customgui.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True) | |
customgui.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True) | |
customgui.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True) | |
self._treegui = self.AddCustomGui( | |
1000, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, | |
300, 300, customgui) | |
if not self._treegui: | |
print "[ERROR]: Could not create TreeView" | |
return False | |
self.AddMultiLineEditText(1001, c4d.BFH_SCALEFIT | c4d.BFV_SCALE | c4d.BFV_BOTTOM, 0, 40) | |
self.AddEditText(1002, c4d.BFH_SCALEFIT, 0, 25) | |
return True | |
def CoreMessage(self, id, msg): | |
if id == c4d.EVMSG_CHANGE: | |
# Update the treeview on each change in the document. | |
self._treegui.Refresh() | |
return True | |
def InitValues(self): | |
# Initialize the column layout for the TreeView. | |
layout = c4d.BaseContainer() | |
layout.SetLong(ID_CHECKBOX, c4d.LV_CHECKBOX) | |
layout.SetLong(ID_ICON, c4d.LV_USER) | |
layout.SetLong(ID_TREEVIEW, c4d.LV_TREE) | |
self._treegui.SetLayout(3, layout) | |
# Set the header titles. | |
self._treegui.SetHeaderText(ID_CHECKBOX, "Check") | |
self._treegui.SetHeaderText(ID_ICON, "Icon") | |
self._treegui.SetHeaderText(ID_TREEVIEW, "Name") | |
self._treegui.Refresh() | |
# Don't need to store the hierarchy, due to SetRoot() | |
data_model = Hierarchy(self) | |
self._treegui.SetRoot(None, data_model, None) | |
self.SetString(1002, "Scroll values") | |
return True | |
class MenuCommand(c4d.plugins.CommandData): | |
dialog = None | |
def Execute(self, doc): | |
""" | |
Just create the dialog when the user clicked on the entry | |
in the plugins menu to open it | |
""" | |
if self.dialog is None: | |
self.dialog = TestDialog() | |
return self.dialog.Open( | |
c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=600, defaultw=600) | |
def RestoreLayout(self, sec_ref): | |
""" | |
Same for this method. Just allocate it when the dialog is needed. | |
""" | |
if self.dialog is None: | |
self.dialog = TestDialog() | |
return self.dialog.Restore(PLUGIN_ID, secret=sec_ref) | |
def main(): | |
c4d.plugins.RegisterCommandPlugin( | |
PLUGIN_ID, PLUGIN_NAME, 0, None, PLUGIN_HELP, MenuCommand()) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment