.. _node: The _node module ---------------- This is about the mechanism used for the vertices, edges and faces of halfedge/:py:class:`~ddg.halfedge.Surface` objects. Calling ``NodeClass`` will create a class tied to another object `s` (stored in `surf` of nodes), typically a :py:class:`~ddg.halfedge.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: .. doctest:: >>> import ddg.halfedge._node as _node >>> s = object() >>> nodelist = _node.NodeClass(s, "Blub", ("one_attribute", "another_attribute")) >>> nodelist >>> 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. .. doctest:: >>> node = nodelist() >>> node >>> node.surf is s True >>> len(nodelist) 1 >>> list(nodelist) [] >>> 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``. .. doctest:: >>> node._pred namespace(_succ=, _pred=) >>> node._succ namespace(_succ=, _pred=) >>> node._pred is node._succ True Nodes also have indices: .. doctest:: >>> 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. .. doctest:: >>> 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) [] 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. .. doctest:: >>> na = nodelist.add_attribute("new_attribute", 0) >>> na >>> 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.