The _node module

This is about the mechanism used for the vertices, edges and faces of halfedge/Surface objects.

Calling NodeClass will create a class tied to another object s (stored in surf of nodes), typically a Surface object. The class name will be the given name concatenated with id(obj). Attributes that the class instances should have can be passed as a tuple of strings. The default value is always None for these.

The created class has a length and is iterable:

>>> import ddg.halfedge._node as _node
>>> s = object()
>>> nodelist = _node.NodeClass(s, "Blub", ("one_attribute", "another_attribute"))
>>> nodelist
<class 'ddg.halfedge._node.Blub...'>
>>> len(nodelist)
0
>>> list(nodelist)
[]

We can add nodes by simply calling the class. This will return an instance of the class, as well as add it to the list.

>>> node = nodelist()
>>> node
<ddg.halfedge._node.Blub... object at 0x...>
>>> node.surf is s
True
>>> len(nodelist)
1
>>> list(nodelist)
[<ddg.halfedge._node.Blub... object at 0x...>]
>>> print(node.one_attribute)
None
>>> print(node.another_attribute)
None

The implementation is basically that of a circular doubly linked list: All nodes have attributes _pred and _succ that point to another node or a sentinel node, implemented as a SimpleNamespace object, that marks the start and end of the list. The sentinel node is stored in nodelist._instance_list.

>>> node._pred
namespace(_succ=<ddg.halfedge._node.Blub... object at 0x...>, _pred=<ddg.halfedge._node.Blub... object at 0x...>)
>>> node._succ
namespace(_succ=<ddg.halfedge._node.Blub... object at 0x...>, _pred=<ddg.halfedge._node.Blub... object at 0x...>)
>>> node._pred is node._succ
True

Nodes also have indices:

>>> node2 = nodelist()
>>> node.index
0
>>> node2.index
1

Nodes can be removed from the list. This changes indices. Removing a node unlinks it from the doubly linked list, sets its surf to None and removes its other attributes.

>>> nodelist.remove(node)
>>> node2.index
0
>>> node.one_attribute
Traceback (most recent call last):
...
AttributeError: 'Blub...' object has no attribute 'one_attribute'...
>>> print(node.surf)
None
>>> len(nodelist)
1
>>> list(nodelist)
[<ddg.halfedge._node.Blub... object at 0x...>]

As an implementation side note, the len is just incremented and decremented manually when adding and removing nodes.

Attributes can also be added to all nodes simultaneously, with a default value. This is implemented using a descriptor class NodeAttribute (meaning it implements __get__ and __set__) that has a dictionary _values with the nodes as keys. The descriptor na is stored in a class attribute attribute_name. When accessing node.attribute_name, it actually returns na._values[node]. The descriptor itself can also be used like a dictionary, i.e. na[node] also returns the same value.

>>> na = nodelist.add_attribute("new_attribute", 0)
>>> na
<ddg.halfedge._node.NodeAttribute object at 0x...>
>>> nodelist.new_attribute is na
True
>>> node2.new_attribute
0
>>> node2.new_attribute = 2
>>> node2.new_attribute
2
>>> na._values[node2]
2
>>> na[node2]
2

The final component of the module is the NodeIter class. This is just a simple iterator that starts at _instance_list and traverses the list using _succ until it hits _instance_list again.