Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
Hi,
All the examples I found so far for TreeView uses the "objects" in C4D such as the objects and materials.
I needed to create a TreeView that would represent directories and subdirectories (much the same way with the Content Browser) .
I can access the directories properly using this code based on this thread
import os def list_files(startpath): for root, dirs, files in os.walk(startpath): level = root.replace(startpath, '').count(os.sep) indent = ' ' * 4 * (level) print('{}{}/'.format(indent, os.path.basename(root))) subindent = ' ' * 4 * (level + 1)
Result: folder1/ sec_folder1_A/ ter_folder1_A/ sec_folder1_B/ folder2/ subfolder2_A/
My problem, for now, is creating Proper Hierarchical Data for Tree View so that I can execute GetFirst, GetDown, GetNext function properly (although I have recreate those functions, for another thread problem maybe haha).
GetFirst
GetDown
GetNext
1) What would be the proper format? list or dictionary? It seems like either are insufficient.
Dictionary: dir_dict = { 'folder1': {'sec_folder1_A':'ter_folder1_A', 'sec_folder1_B': 'None' }, 'folder2': {'sec_folder2_A': 'None'} } List: dir_list = [ [folder1, [ [sec_folder1_A, [ter_folder1_A]], sec_folder1_B ], [folder2, [subfolder2_A] ] ]
2) Even if its List or Dictionary, I'm actually at lost how to populate it as hierarchical data. Do you have any tips?
Thank you for looking at my problem
Regards, Ben
dict
TreeviewFunctions
TreeViewCustomGui
Cheers, zipit
Hi @zipit
Thanks for the response. Just a heads up, I'm still a novice Python user so forgive me if I'm asking the obvious below.
1 ================== RE: do not use something like an adjacency matrix or an inverted index I haven't encountered both but what you should I use other than those two? I didn't know there were "types" of dictionary.
RE:Why not write some node type with which you can both represent and access/modify your graph? I don't know how to write a node type. That's also the first time I have heard of it. This is what you are referring to right? https://developers.maxon.net/docs/Cinema4DPythonSDK/html/modules/c4d.plugins/BaseData/NodeData/index.html
2 ==================
Since creating node type is out of my league. I guess using dict will be the way to go. Need to read up on inverted index and adjacency matrix. Basically, both can help me "crawl" on my hierarchical data right? Such as performing GetNext, GetDown functions.
a
b,c, d
b: a, c: a, d: a
class Node(object): """A very simple node type for a tree/trie graph. """ def __init__(self, **kwargs): """The constructor for ``Node``. Args: **kwargs: Any non-graph related attributes of the node. """ # You might want to encapsulate your attributes in properties, so # that you can validate / process them, I took the lazy route. if "name" not in kwargs: kwargs["name"] = "Node" self.__dict__.update(kwargs) self.parent = None self.children = [] self.prev = None self.next = None self.down = None def __repr__(self): """The representation is: class, name, memory location. """ msg = "<{} named {} at {}>" hid = "0x{:0>16X}".format(id(self)) return msg.format(self.__class__.__name__, self.name, hid) def add(self, nodes): """Adds one or multiple nodes to the instance as children. Args: nodes (list[Node] or Node): The nodes to add. Raises: TypeError: When nodes contains non-Node elements. """ nodes = [nodes] if not isinstance(nodes, list) else nodes # last child of the instance, needed for linked list logic prev = self.children[-1] if self.children else None for node in nodes: if not isinstance(node, Node): raise TypeError(node) node.parent = self node.prev = prev if prev is not None: prev.next = node else: self.down = node self.children.append(node) prev = node def pretty_print(self, indent=0): """Pretty print the instance and its descendants. Args: indent (int, optional): Private. """ tab="\t" * indent a = self.prev.name if self.prev else None b = self.next.name if self.next else None c = self.down.name if self.down else None msg = "{tab}{node} (prev: {prev}, next: {next}, down: {down})" print msg.format(tab=tab, node=self, prev=a, next=b, down=c) for child in self.children: child.pretty_print(indent+1) def build_example_tree(): """ """ root = Node(name="root") node_0 = Node(name="node_0") node_00 = Node(name="node_00") node_01 = Node(name="node_01") node_02 = Node(name="node_02") node_1 = Node(name="node_1") node_10 = Node(name="node_10") root.add(nodes=[node_0, node_1]) node_0.add(nodes=[node_00, node_01, node_02]) node_1.add(nodes=node_10) return root root = build_example_tree() root.pretty_print()
@zipit
Thanks for the response and the sample code. I appreciate it a lot. So I guess, when you mentioned "Node Type", it's not necessarily specific to Cinema4D but a general concept.
So now, I just need to repopulate the Node Class with the directories. Will keep you updated on the progress.
This should keep me busy for the weekend.
Thanks again!
DISCLAIMER: If you think, the question below merits a separate thread, let me know, and I'll create another thread.
Thanks again for the reply. The code still not completed yet. I populated the class with the directories but I'm having a problem with "parenting" the nodes. Specifically, I'm having problem accessing the Node class by its name attribute.
This is the snippet of the problem:
current_index = folder_path_list.index(folder) parent_folder = folder_path_list[current_index-1] parent_node = #PROBLEM #get_class_node_base_on_its_name new_node = Node(name=folder) parent_node.add(nodes=new_node)
You can check the whole code below:
import os start_path = '.' # written by zipit class Node(object): """A very simple node type for a tree/trie graph. """ baseId = 90000 dct = {} def __init__(self, **kwargs): """The constructor for ``Node``. Args: **kwargs: Any non-graph related attributes of the node. """ # You might want to encapsulate your attributes in properties, so # that you can validate / process them, I took the lazy route. if "name" not in kwargs: kwargs["name"] = "Node" self.__dict__.update(kwargs) self.parent = None self.children = [] self.prev = None self.next = None self.down = None def __repr__(self): """The representation is: class, name, memory location. """ msg = "<{} named {} at {}>" hid = "0x{:0>16X}".format(id(self)) return msg.format(self.__class__.__name__, self.name, hid) def add(self, nodes): """Adds one or multiple nodes to the instance as children. Args: nodes (list[Node] or Node): The nodes to add. Raises: TypeError: When nodes contains non-Node elements. """ nodes = [nodes] if not isinstance(nodes, list) else nodes # last child of the instance, needed for linked list logic prev = self.children[-1] if self.children else None for node in nodes: if not isinstance(node, Node): raise TypeError(node) node.parent = self node.prev = prev if prev is not None: prev.next = node else: self.down = node self.children.append(node) prev = node def pretty_print(self, indent=0): """Pretty print the instance and its descendants. Args: indent (int, optional): Private. """ tab="\t" * indent a = self.prev.name if self.prev else None b = self.next.name if self.next else None c = self.down.name if self.down else None msg = "{tab}{node} (prev: {prev}, next: {next}, down: {down})" print (msg.format(tab=tab, node=self, prev=a, next=b, down=c)) for child in self.children: child.pretty_print(indent+1) created_dir = [] root_node = Node(name="images") for root, dirs, files in os.walk(start_path): root_normalized = root.replace(start_path, '') for dir in dirs: folder_path = os.path.join(root_normalized, dir) folder_path_list = folder_path.split(os.sep) folder_path_list = list(filter(None, folder_path_list)) # Remove empty strings for folder in folder_path_list: # Images node is already created before this loop. It is set as the root node if folder == "images": continue # Prevent creation of node if it was already created beforehand if folder in created_dir: continue current_index = folder_path_list.index(folder) parent_folder = folder_path_list[current_index-1] parent_node = #get_class_node_base_on_its_name new_node = Node(name=folder) parent_node.add(nodes=new_node) created_dir.append(folder) root_node.pretty_print()
The directory is still as above:
images/ folder1/ sec_folder1_A/ ter_folder1_A/ sec_folder1_B/ folder2/ subfolder2_A/
I am not quite sure what your actual question is. However, I think that you somehow missed the point of writing a node type. The advantage of it is that you can encapsulate any logic into your nodes and can operate on local and small scale. When you write some external functions which operate more or less non-object oriented and on the scale of the whole graph, you could also use a builtin data structure like a dictionary and ignore the whole OO-stuff. I do not want to start a discussion about functional vs. OO programming, use whatever you are comfortable with, but if you want to benefit from some custom node type, you have to implement a custom node type
Below you will find an example for how I would go about what you are probably trying to do. Please note that this is example code and not by any means something that should be used
import os class BaseNode(object): """The base node type. """ def __init__(self, **kwargs): """The constructor for BaseNode. Args: **kwargs: Any non-graph related attributes of the node. """ # You might want to encapsulate your attributes in properties, so # that you can validate / process them, I took the lazy route. if "name" not in kwargs: kwargs["name"] = "Node" self.__dict__.update(kwargs) self.children = [] self.up = None self.down = None self.prev = None self.next = None def __iter__(self): """Yields all nodes attached to the node. Yields: BaseNode: A node attached to this node. """ for child in self.children: yield child def __repr__(self): """The string representation of the node. """ msg = "<{} named {} at {}>" hid = "0x{:0>16X}".format(id(self)) return msg.format(self.__class__.__name__, self.name, hid) def _link_nodes(self): """Builds the links between the children of the instance and the instance. """ prev = None for node in self: node.up = self node.prev = prev if prev is not None: prev.next = node prev = node self.down = self.children[0] if self.children else None def add(self, nodes): """Adds one or multiple nodes to the instance as children. Args: nodes (list[BaseNode] or BaseNode): The nodes to add. Raises: TypeError: When nodes contains non-BaseNode elements. """ nodes = [nodes] if not isinstance(nodes, list) else nodes # last child of the instance, needed for linked list logic prev = self.children[-1] if self.children else None for node in nodes: if not isinstance(node, BaseNode): raise TypeError(node) self.children.append(node) self._link_nodes() def pretty_print(self, indent=0): """Pretty print the instance and its descendants. """ tab = "\t" * indent a = self.prev.name if self.prev else None b = self.next.name if self.next else None c = self.down.name if self.down else None msg = "{tab}{node} (prev: {prev}, next: {next}, down: {down})" print(msg.format(tab=tab, node=self, prev=a, next=b, down=c)) for child in self.children: child.pretty_print(indent+1) class PathNode(BaseNode): """The file-path specialization of a BaseNode. """ def __init__(self, path, **kwargs): """The constructor of PathNode. Args: path (str): The file/folder path of the node. **kwargs: Any other non-graph related attributes to be attached to the node. """ BaseNode.__init__(self, path=path) self._initialize_path_tree() def _initialize_path_tree(self): """Initializes a path tree with the path attribute of the instance. Recursively builds a PathNode tree for the folders and files that are descendants of the path attribute of the instance. """ # Validate the path attribute. if not "path" in self.__dict__: msg = "The node hasn't been initialized with a 'path' attribute." raise AttributeError(msg) if not os.path.exists(self.path): msg = "The path '{path}' does not exist or cannot be accessed." raise OSError(msg.format(path=self.path)) path = self.path root_path, element = os.path.split(path) # Node is a terminal node / file if os.path.isfile(path): name, extension = os.path.splitext(element) self.name = name self.extension = extension self.is_file = True # Node is a non-terminal node / a folder else: self.name = element self.extension = None self.is_file = False # Build the children paths = [os.path.join(path, item) for item in os.listdir(path)] for item in paths: self.add(PathNode(path=item)) self.sort() def get(self, name, inclusive=False): """Yields all nodes which have a matching name attribute. Note: This your "get_class_node_base_on_its_name" method. I am not quite sure, what you are trying to accomplish with it, so there are multiple things one could do differently. Args: name (str): The name to match against. Yields: BaseNode: A node that is a descendant of the instance and which fulfills the criteria. """ if self.name == name: yield self for item in self: for result in item.get(name=name): yield result def sort(self): """Sorts the children by folder/files, file types and names. """ if not self.children: return key_lambda = lambda x: (x.is_file, x.extension, x.name) self.children = sorted(self.children, key=key_lambda) self._link_nodes() for node in self: node.sort() def pretty_print(self, indent=0): """Pretty print the directory structure attached to this node. """ tab = "\t" * indent if self.is_file: msg = "{tab}{name}{ext}".format(tab=tab, name=self.name, ext=self.extension) else: msg = "{tab}[{name}]".format(tab=tab, name=self.name) print msg for child in self.children: child.pretty_print(indent+1) def demo(): """ """ path = os.path.dirname(__file__) root = PathNode(path=path) print "root:", root root.pretty_print() print "\nIterate the root node:" for node in root: print node print "\nGet node by name:" for item in root.get("plots"): print item print "\nGet node by name:" for item in root.get("todo"): print item if __name__ == "__main__": demo()
root: <PathNode named misc at 0x000000000309C0C8> [misc] [plots] plots_interpolation.py plots_projection.py plotters.py [scripts] svg_colors.py todo.md scribbles_1.py scribbles_2.py scribbles_3.py scribbles_4.py scribbles_5.py Iterate the root node: <PathNode named plots at 0x000000000309C208> <PathNode named scripts at 0x000000000309C548> <PathNode named todo at 0x000000000309C5C8> <PathNode named scribbles_1 at 0x000000000309C308> <PathNode named scribbles_2 at 0x000000000309C448> <PathNode named scribbles_3 at 0x000000000309C488> <PathNode named scribbles_4 at 0x000000000309C4C8> <PathNode named scribbles_5 at 0x000000000309C508> Get node by name: <PathNode named plots at 0x000000000309C208> Get node by name: <PathNode named todo at 0x000000000309C5C8> [Finished in 0.1s]
Hi sorry for the delay I completely missed the topic, you can find valuable information about how TreeView is working on using customgui listview, then for more advice especially about TreeViewFunctions.GetDown usage Insert object in Treeview, and in No multiple selection in Treeview not working?, you can find a more versatile "node" object which can be used as a root, while previously I used a list as a root.
Cheers, Maxime.
Thank you for your response.
With the previous help, I was able to represent the folder hierarchy in the treeview. You can see it here (image) Source file is here.
In summary, I didn't use your recent with the PathNode class but it helped me how to create the Get Parent Node function which was to store the nodes in a list, and compare the node name and the folder name.
PathNode
Get Parent Node
===================================
The code is working now but may I ask about this line. for item in self:
for item in self:
This is my first seeing a for loop for the self. I understand if it would be self.list_of_names or self.list_of_nodes but iterating for the self itself? How is it even possible?
self
self.list_of_names
self.list_of_nodes
I couldn't see any self.append(item) in the code.
self.append(item)
Please enlighten me sensei.
@m_adam
No worries. Thanks for the thread links. Will add them as reference.
This works, because I did implement __iter__ in BaseNode, which will be called when you try to iterate an object. For the same reason also for node in root does work. You can learn more about operator overloading in Python here. In a pythonic context they are called spcieal methods, important methods or dunder methods, but practically it's just operator overloading if you are willing to be a bit fuzzy with the term operator.
__iter__
BaseNode
for node in root
Thanks for the clarification. Have a great day ahead!