Advertisement
Guest User

TreeView C4D Python [Material] v1.2

a guest
Nov 18th, 2022
34
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 16.31 KB | None | 0 0
  1. """Example for setting the column width of a tree view by the width of its
  2. items.
  3.  
  4. The only changes which have been made in comparison to the original code can
  5. be found in TextureObject.__init__(), TextureObject.RandomString() and
  6. ListView.GetColumnWidth(). The tree view items #TextureObject now generate
  7. random #texturePath and #otherData strings and the treeview scales its columns
  8. to the length of these strings.
  9.  
  10. Adapted from:
  11. https://plugincafe.maxon.net/topic/10654/14102_using-customgui-listview/2
  12. """
  13. import c4d
  14. import random
  15. import sys
  16.  
  17. # Be sure to use a unique ID obtained from http://www.plugincafe.com/.
  18. PLUGIN_ID = 1000010 # TEST ID ONLY
  19.  
  20. # TreeView Column IDs.
  21. ID_CHECKBOX = 1
  22. ID_NAME = 2
  23. ID_OTHER = 3
  24. ID_LONGFILENAME = 4
  25.  
  26. # A tuple of characters to select from (a-z).
  27. CHARACTERS = tuple( chr ( n) for n in range(97, 122))
  28. doc = c4d.documents.GetActiveDocument()
  29.  
  30. class MaterialCollection:
  31. """Handles a collection of materials from a singular document for long-term referencing.
  32.  
  33. Will reestablish material references when possible.
  34. """
  35.  
  36. def __init__(self, collection: list[c4d.BaseMaterial]):
  37. """Initializes the collection with a list of materials.
  38. """
  39. if not isinstance(collection, list):
  40. raise TypeError(f"{collection = }")
  41.  
  42. # The list of materials, the list of markers for them, and their associated document.
  43. self._materials: list[c4d.BaseMaterial] = []
  44. self._markers: list[bytes] = []
  45. self._doc: c4d.documents.BaseDocument = None
  46.  
  47. for material in collection:
  48. # Sort out illegal types, "dead" materials, and materials not attached to a document.
  49. if not isinstance(material, c4d.BaseMaterial) or not material.IsAlive():
  50. raise TypeError(f"{material} is not alive or not a BaseMaterial.")
  51. if material.GetDocument() is None:
  52. raise RuntimeError("Dangling material cannot be managed.")
  53.  
  54. # Establish what the document of the materials is and that they all belong to the same
  55. # document. Technically, a BaseDocument can also be dead (when it has been closed),
  56. # but this is less likely to happen, so a stored reference to it can also become dead.
  57. if self._doc is None:
  58. self._doc = material.GetDocument()
  59. elif self._doc != material.GetDocument():
  60. raise RuntimeError("Can only manage materials attached to the same document.")
  61.  
  62. # Append the material and its UUID marker to the internal lists.
  63. self._materials.append(material)
  64. self._markers.append(MaterialCollection.GetUUID(material))
  65.  
  66. def __len__(self):
  67. """Returns the number of managed materials.
  68. """
  69. return len(self._materials)
  70.  
  71. def __getitem__(self, index: int) -> c4d.BaseMaterial:
  72. """Returns the material at #index.
  73. """
  74. if len(self._materials) - 1 < index:
  75. raise IndexError(f"The index '{index}' is out of bounds.")
  76.  
  77. # The item is still alive, we can return it directly.
  78. item: c4d.BaseMaterial = self._materials[index]
  79. if item.IsAlive():
  80. print ("Returning still valid material reference")
  81. return item
  82.  
  83. # The item is not alive anymore, try to find its new instance.
  84. if not self._doc.IsAlive():
  85. raise RuntimeError(
  86. f"{self._doc} is not valid anymore, cannot reestablish material references.")
  87.  
  88. # Get the marker identifying the material at #index.
  89. materialMarker: bytes = self._markers[index]
  90.  
  91. # Iterate over all materials in #_doc to find one with a matching marker.
  92. for material in self._doc.GetMaterials():
  93. otherMarker: bytes = MaterialCollection.GetUUID(material)
  94.  
  95. # We found a match, update the internal data, and return the material.
  96. if materialMarker == otherMarker:
  97. self._materials[index] = material
  98. print ("Returning re-established material reference")
  99. return material
  100.  
  101. # There is no material with this marker anymore.
  102. raise RuntimeError(
  103. f"The material at {index} has not valid anymore and cannot be re-established.")
  104.  
  105. def __iter__(self):
  106. """Iterates over all materials in the collection.
  107. """
  108. for index in range(len(self._materials)):
  109. yield self[index]
  110.  
  111. @staticmethod
  112. def GetUUID(atom: c4d.C4DAtom) -> bytes:
  113. """Retrieves the UUID of an atom as a bytes object.
  114.  
  115. Args:
  116. item (c4d.C4DAtom): The atom to get the UUID hash for.
  117.  
  118. Raises:
  119. RuntimeError: On type assertion failure.
  120.  
  121. Returns:
  122. bytes: The UUID of #atom.
  123. """
  124. if not isinstance(atom, c4d.C4DAtom):
  125. raise TypeError(f"{atom = }")
  126.  
  127. # Get the MAXON_CREATOR_ID marker uniquely identifying the atom.
  128. uuid: memoryview = atom.FindUniqueID(c4d.MAXON_CREATOR_ID)
  129. if not isinstance(uuid, memoryview):
  130. raise RuntimeError(f"Illegal non-marked atom: {atom}")
  131.  
  132. return bytes(uuid)
  133.  
  134. class TextureObject(object):
  135. """
  136. Class which represent a texture, aka an Item in our list
  137. """
  138. texturePath = "TexPath"
  139. otherData = "OtherData"
  140. _selected = False
  141.  
  142. def __init__(self, texturePath):
  143. self.texturePath = texturePath
  144. self.otherData += texturePath
  145.  
  146. @property
  147. def IsSelected(self):
  148. return self._selected
  149.  
  150. def Select(self):
  151. self._selected = True
  152.  
  153. def Deselect(self):
  154. self._selected = False
  155.  
  156. def __repr__(self):
  157. return str(self)
  158.  
  159. def __str__(self):
  160. return self.texturePath
  161.  
  162.  
  163. class ListView(c4d.gui.TreeViewFunctions):
  164.  
  165.  
  166. def __init__(self, collection):
  167.  
  168. self.listOfTexture = collection
  169.  
  170. def IsResizeColAllowed(self, root, userdata, lColID):
  171. return True
  172.  
  173. def IsTristate(self, root, userdata):
  174. return False
  175.  
  176. def GetColumnWidth(self, root, userdata, obj, col, area):
  177. """Measures the width of cells.
  178.  
  179. Although this function is called #GetColumnWidth and has a #col, it is
  180. not only executed by column but by cell. So, when there is a column
  181. with items requiring the width 5, 10, and 15, then there is no need
  182. for evaluating all items. Each item can return its ideal width and
  183. Cinema 4D will then pick the largest value.
  184.  
  185. Args:
  186. root (any): The root node of the tree view.
  187. userdata (any): The user data of the tree view.
  188. obj (any): The item for the current cell.
  189. col (int): The index of the column #obj is contained in.
  190. area (GeUserArea): An already initialized GeUserArea to measure
  191. the width of strings.
  192.  
  193. Returns:
  194. TYPE: Description
  195. """
  196. # The default width of a column is 80 units.
  197. width = 80
  198. # Replace the width with the text width. area is a prepopulated
  199. # user area which has already setup all the font stuff, we can
  200. # measure right away.
  201.  
  202. if col == ID_NAME:
  203. return area.DrawGetTextWidth(obj.texturePath) + 5
  204. if col == ID_OTHER:
  205. return area.DrawGetTextWidth(obj.otherData) + 5
  206. if col == ID_LONGFILENAME:
  207. return area.DrawGetTextWidth(obj.longfilename) + 5
  208.  
  209. return width
  210.  
  211. def IsMoveColAllowed(self, root, userdata, lColID):
  212. # The user is allowed to move all columns.
  213. # TREEVIEW_MOVE_COLUMN must be set in the container of AddCustomGui.
  214. return True
  215.  
  216. def GetFirst(self, root, userdata):
  217. """
  218. Return the first element in the hierarchy, or None if there is no element.
  219. """
  220. rValue = None if not self.listOfTexture else self.listOfTexture[0]
  221. return rValue
  222.  
  223. def GetDown(self, root, userdata, obj):
  224. """
  225. Return a child of a node, since we only want a list, we return None everytime
  226. """
  227. return None
  228.  
  229. def GetNext(self, root, userdata, obj):
  230. """
  231. Returns the next Object to display after arg:'obj'
  232. """
  233. rValue = None
  234. currentObjIndex = self.listOfTexture.index(obj)
  235. nextIndex = currentObjIndex + 1
  236. if nextIndex < len(self.listOfTexture):
  237. rValue = self.listOfTexture[nextIndex]
  238.  
  239. return rValue
  240.  
  241. def GetPred(self, root, userdata, obj):
  242. """
  243. Returns the previous Object to display before arg:'obj'
  244. """
  245. rValue = None
  246. currentObjIndex = self.listOfTexture.index(obj)
  247. predIndex = currentObjIndex - 1
  248. if 0 <= predIndex < len(self.listOfTexture):
  249. rValue = self.listOfTexture[predIndex]
  250.  
  251. return rValue
  252.  
  253. def GetId(self, root, userdata, obj):
  254. """
  255. Return a unique ID for the element in the TreeView.
  256. """
  257. return hash(obj)
  258.  
  259. def Select(self, root, userdata, obj, mode):
  260. """
  261. Called when the user selects an element.
  262. """
  263. if mode == c4d.SELECTION_NEW:
  264. for tex in self.listOfTexture:
  265. tex.Deselect()
  266. obj.Select()
  267. elif mode == c4d.SELECTION_ADD:
  268. obj.Select()
  269. elif mode == c4d.SELECTION_SUB:
  270. obj.Deselect()
  271.  
  272. def IsSelected(self, root, userdata, obj):
  273. """
  274. Returns: True if *obj* is selected, False if not.
  275. """
  276. return obj.IsSelected
  277.  
  278. def SetCheck(self, root, userdata, obj, column, checked, msg):
  279. """
  280. Called when the user clicks on a checkbox for an object in a
  281. `c4d.LV_CHECKBOX` column.
  282. """
  283. if checked:
  284. obj.Select()
  285. else:
  286. obj.Deselect()
  287.  
  288. def IsChecked(self, root, userdata, obj, column):
  289. """
  290. Returns: (int): Status of the checkbox in the specified *column* for *obj*.
  291. """
  292. if obj.IsSelected:
  293. return c4d.LV_CHECKBOX_CHECKED | c4d.LV_CHECKBOX_ENABLED
  294. else:
  295. return c4d.LV_CHECKBOX_ENABLED
  296.  
  297. def GetName(self, root, userdata, obj):
  298. """
  299. Returns the name to display for arg:'obj', only called for column of type LV_TREE
  300. """
  301. return str(obj) # Or obj.texturePath
  302.  
  303. def DrawCell(self, root, userdata, obj, col, drawinfo, bgColor):
  304. """
  305. Draw into a Cell, only called for column of type LV_USER
  306. """
  307. if col in (ID_OTHER, ID_LONGFILENAME):
  308. text = obj.otherData if col == ID_OTHER else obj.longfilename
  309. canvas = drawinfo["frame"]
  310. textWidth = canvas.DrawGetTextWidth(text)
  311. textHeight = canvas.DrawGetFontHeight()
  312. xpos = drawinfo["xpos"]
  313. ypos = drawinfo["ypos"] + drawinfo["height"]
  314.  
  315. if (drawinfo["width"] < textWidth):
  316. while (drawinfo["width"] < textWidth):
  317. if len(text) <= 4:
  318. text = "..."
  319. break
  320. text = text[:-4] + "..."
  321. textWidth = canvas.DrawGetTextWidth(text)
  322.  
  323. textWidth = canvas.DrawGetTextWidth(text)
  324. drawinfo["frame"].DrawText(text, xpos, ypos - int(textHeight * 1.1))
  325.  
  326. def DoubleClick(self, root, userdata, obj, col, mouseinfo):
  327. """
  328. Called when the user double-clicks on an entry in the TreeView.
  329.  
  330. Returns:
  331. (bool): True if the double-click was handled, False if the
  332. default action should kick in. The default action will invoke
  333. the rename procedure for the object, causing `SetName()` to be
  334. called.
  335. """
  336. c4d.gui.MessageDialog("You clicked on " + str(obj))
  337. return True
  338.  
  339. def DeletePressed(self, root, userdata):
  340. "Called when a delete event is received."
  341. for tex in reversed(self.listOfTexture):
  342. if tex.IsSelected:
  343. self.listOfTexture.remove(tex)
  344.  
  345. class TestDialog(c4d.gui.GeDialog):
  346. _treegui = None # Our CustomGui TreeView
  347.  
  348. collection: MaterialCollection = getattr(sys, "matCollection", None)
  349. if collection is None and doc.GetMaterials() != []:
  350. collection = MaterialCollection(doc.GetMaterials())
  351. setattr(sys, "matCollection", collection)
  352. print ("Stored material collection at sys.matCollection")
  353. elif collection is not None:
  354. print ("Retrieved stored material collection from sys.matCollection")
  355. else:
  356. print ("Document contains no materials.")
  357. _listView = ListView(collection) # Our Instance of c4d.gui.TreeViewFunctions
  358. _listView._doc = c4d.documents.GetActiveDocument()
  359.  
  360. def CreateLayout(self):
  361. # Create the TreeView GUI.
  362. customgui = c4d.BaseContainer()
  363. customgui.SetBool(c4d.TREEVIEW_BORDER, c4d.BORDER_THIN_IN)
  364. customgui.SetBool(c4d.TREEVIEW_HAS_HEADER, True) # True if the tree view may have a header line.
  365. customgui.SetBool(c4d.TREEVIEW_HIDE_LINES, True) # True if no lines should be drawn.
  366. customgui.SetBool(c4d.TREEVIEW_MOVE_COLUMN, True) # True if the user can move the columns.
  367. customgui.SetBool(c4d.TREEVIEW_RESIZE_HEADER, True) # True if the column width can be changed by the user.
  368. customgui.SetBool(c4d.TREEVIEW_FIXED_LAYOUT, True) # True if all lines have the same height.
  369. customgui.SetBool(c4d.TREEVIEW_ALTERNATE_BG, True) # Alternate background per line.
  370. customgui.SetBool(c4d.TREEVIEW_CURSORKEYS, True) # True if cursor keys should be processed.
  371. customgui.SetBool(c4d.TREEVIEW_NOENTERRENAME, False) # Suppresses the rename popup when the user presses enter.
  372.  
  373. self._treegui = self.AddCustomGui(1000, c4d.CUSTOMGUI_TREEVIEW, "", c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 300, 300, customgui)
  374. if not self._treegui:
  375. print("[ERROR]: Could not create TreeView")
  376. return False
  377.  
  378. self.AddButton(1001, c4d.BFH_CENTER, name="Add")
  379. return True
  380.  
  381. def InitValues(self):
  382. # Initialize the column layout for the TreeView.
  383. layout = c4d.BaseContainer()
  384. layout.SetLong(ID_CHECKBOX, c4d.LV_CHECKBOX)
  385. layout.SetLong(ID_NAME, c4d.LV_TREE)
  386. layout.SetLong(ID_LONGFILENAME, c4d.LV_USER)
  387. layout.SetLong(ID_OTHER, c4d.LV_USER)
  388. self._layout = layout
  389. self._treegui.SetLayout(4, layout)
  390.  
  391. # Set the header titles.
  392. self._treegui.SetHeaderText(ID_CHECKBOX, "Check")
  393. self._treegui.SetHeaderText(ID_NAME, "Name")
  394. self._treegui.SetHeaderText(ID_LONGFILENAME, "Long Filename")
  395. self._treegui.SetHeaderText(ID_OTHER, "Other")
  396.  
  397. self._treegui.Refresh()
  398.  
  399. # Set TreeViewFunctions instance used by our CUSTOMGUI_TREEVIEW
  400.  
  401. self._treegui.SetRoot(self._treegui, self._listView, None)
  402. return True
  403.  
  404. def Command(self, id, msg):
  405. # Click on button
  406. if id == 1001:
  407.  
  408. '''
  409. # Add data to our DataStructure (ListView)
  410. newID = int(len(self._listView.listOfTexture) + 1)
  411. # print(newID)
  412. # newID = "T{}".format(newID)
  413. # print(newID)
  414. # tex = TextureObject(newID)
  415. tex = TextureObject()
  416. tex.texturePath = "Some new data"
  417. tex.longfilename = TextureObject.RandomString(20, 40)
  418. self._listView.listOfTexture.append(tex)
  419. self._treegui.Refresh()
  420. '''
  421. pass
  422.  
  423. return True
  424.  
  425. class MenuCommand(c4d.plugins.CommandData):
  426. dialog = None
  427.  
  428. def Execute(self, doc):
  429. if self.dialog is None:
  430. self.dialog = TestDialog()
  431. return self.dialog.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaulth=600, defaultw=600)
  432.  
  433. def RestoreLayout(self, sec_ref):
  434. if self.dialog is None:
  435. self.dialog = TestDialog()
  436. return self.dialog.Restore(PLUGIN_ID, secret=sec_ref)
  437.  
  438. def main():
  439. c4d.plugins.RegisterCommandPlugin(
  440. PLUGIN_ID, "Python TreeView Example REVISED", 0, None, "Python TreeView Example", MenuCommand())
  441.  
  442.  
  443. if __name__ == "__main__":
  444. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement