# PYTHON STANDARD LIBRARY IMPORTS ---------------------------------------------
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from collections import OrderedDict
# DUNDER ----------------------------------------------------------------------
__all__ = [
"KnitNetworkBase"
]
# THIRD PARTY MODULE IMPORTS --------------------------------------------------
import networkx as nx
# LOCAL MODULE IMPORTS --------------------------------------------------------
from cockatoo.environment import RHINOINSIDE
# RHINO IMPORTS ---------------------------------------------------------------
if RHINOINSIDE:
import rhinoinside
rhinoinside.load()
from Rhino.Geometry import Curve as RhinoCurve
from Rhino.Geometry import Line as RhinoLine
from Rhino.Geometry import LineCurve as RhinoLineCurve
from Rhino.Geometry import Polyline as RhinoPolyline
else:
from Rhino.Geometry import Curve as RhinoCurve
from Rhino.Geometry import Line as RhinoLine
from Rhino.Geometry import LineCurve as RhinoLineCurve
from Rhino.Geometry import Polyline as RhinoPolyline
# CLASS DECLARATION -----------------------------------------------------------
[docs]class KnitNetworkBase(nx.Graph):
"""
Abstract datastructure for representing a network (graph) consisting of
nodes with special attributes aswell as 'warp' edges, 'weft' edges and
contour edges which are neither 'warp' nor 'weft'.
Used as a base class for sharing behaviour between the KnitNetwork,
KnitMappingNetwork and KnitDiNetwork classes.
Inherits from :class:`networkx.Graph`.
For more info, see *NetworkX* [13]_.
References
----------
.. [13] Hagberg, Aric A.; Schult, Daniel A.; Swart, Pieter J.
*Exploring Network Structure, Dynamics, and Function using
NetworkX* In: *Varoquaux, Vaught et al. (Hg.) 2008 - Proceedings
of the 7th Python in Science Conference* pp. 11-15
See: `NetworkX 1.5 <https://networkx.github.io/documentation/
networkx-1.5/>`_
"""
# REPRESENTATION OF NETWORK -----------------------------------------------
def __str__(self):
"""
Return the graph name if it is set, otherwise return a textual
description of the network.
Returns
-------
name : str
The name of the graph or a textual description of the network.
"""
if self.name != "":
return self.name
else:
return self.ToString()
def __repr__(self):
"""
Return a textual description of the network.
Returns
-------
description : str
A textual description of the network.
"""
if self.name != "":
name = self.name
else:
name = "KnitNetworkBase"
nn = len(self.nodes())
ce = len(self.contour_edges)
wee = len(self.weft_edges)
wae = len(self.warp_edges)
data = ("({} Nodes, {} Contours, {} Weft, {} Warp)")
data = data.format(nn, ce, wee, wae)
return name + data
[docs] def ToString(self):
"""
Return a textual description of the network.
Returns
-------
description : str
A textual description of the network.
Notes
-----
Used for overloading the Grasshopper display in data parameters.
"""
return repr(self)
[docs] def prepare_for_graphviz(self):
"""
Creates a new graph with attributes for visualising this network
using GraphViz.
Based on code by Anders Holden Deleuran
"""
# Set render variables
nodeFontSize = 10
edgeFontSize = 3.75
arrowSize = 0.4
# shapes
circle = "circle"
# colors
black = "black"
white = "white"
red = "red"
blue = "blue"
col_regular = "black"
col_start_leaf = "seagreen"
col_start_leaf_end = "orange"
col_start_end = "darkgreen"
col_end = "blue"
col_leaf = "cyan"
col_end_leaf = "magenta"
col_increase_end = "purple"
col_decrease_end = "darkorchid4"
col_increase = "red"
col_decrease = "darkred"
font = "Helvetica"
# choose graph type for new graph depending on current graph
if isinstance(self, nx.MultiGraph):
DotGraph = nx.MultiDiGraph()
else:
DotGraph = nx.DiGraph()
# get all nodes and all edges
network_nodes = self.nodes(data=True)
network_edges = self.edges(data=True)
# process all nodes and add them to the dot graph
for node in network_nodes:
ndata = node[1]
# END BUT NOT LEAF
if ndata["end"] and not ndata["leaf"]:
if not ndata["increase"] and not ndata["decrease"]:
if ndata["start"]:
node_type = "S"
node_color = col_start_end
node_txt_color = black
else:
node_type = "E"
node_color = col_end
node_txt_color = white
elif ndata["increase"] and not ndata["decrease"]:
node_type = "Ei"
node_color = col_increase_end
node_txt_color = black
elif not ndata["increase"] and ndata["decrease"]:
node_type = "Ed"
node_color = col_decrease_end
node_txt_color = black
node_shape = circle
# LEAF BUT NOT END
elif ndata["leaf"] and not ndata["end"]:
if ndata["start"]:
node_type = "SL"
node_color = col_start_leaf
else:
node_type = "L"
node_color = col_leaf
node_txt_color = black
node_shape = circle
# END AND LEAF
elif ndata["leaf"] and ndata["end"]:
if ndata["start"]:
node_type = "SEL"
node_color = col_start_leaf_end
else:
node_type = "EL"
node_color = col_end_leaf
node_txt_color = black
node_shape = circle
# NO END NO LEAF
elif not ndata["leaf"] and not ndata["end"]:
# INCREASE
if ndata["increase"] and not ndata["decrease"]:
node_type = "i"
node_color = col_increase
# DECREASE
elif not ndata["increase"] and ndata["decrease"]:
node_type = "d"
node_color = col_decrease
else:
node_type = "R"
node_color = col_regular
node_txt_color = white
node_shape = circle
if node[1]["segment"]:
node_label = str(node[0]) + "\n" + node_type + "\n" + \
str(node[1]["segment"])
else:
node_label = str(node[0]) + node_type
# make pos attribute for orthogonal layouting
if ndata["z"] > 0.0:
node_pos = (str(ndata["x"]) + ", " +
str(ndata["y"]) + ", " +
str(ndata["z"]))
else:
node_pos = (str(ndata["x"]) + ", " +
str(ndata["y"]))
node_attributes = {"pos": node_pos,
"label": node_label,
"shape": node_shape,
"fontname": font,
"style": "filled",
"fillcolor": node_color,
"fontcolor": node_txt_color,
"fontsize": nodeFontSize,
"margin": 0.0001}
DotGraph.add_node(node[0], attr_dict=node_attributes)
# make edge types and labels and add them to the graph
for edge in network_edges:
padding = " "
if edge[2]["weft"]:
edge_type = "WP"
edge_color = blue
elif edge[2]["warp"]:
edge_type = "WT"
edge_color = red
elif not edge[2]["weft"] and not edge[2]["warp"]:
edge_type = "C"
edge_color = black
edge_info = str(edge[0]) + ">" + str(edge[1])
edge_segment = edge[2]["segment"]
if edge_segment:
edge_label = (padding + edge_info + edge_type + "\n" +
str(edge_segment))
else:
edge_label = padding + edge_info + edge_type
DotGraph.add_edge(
edge[0],
edge[1],
label=edge_label,
fontname=font,
fontcolor=black,
color=edge_color,
fontsize=edgeFontSize,
arrowsize=arrowSize)
return DotGraph
[docs] def prepare_for_gephi(self):
"""
Creates a new graph with attributes for visualising this network
using Gephi.
Based on code by Anders Holden Deleuran
"""
# colors
black = "black"
blue = "blue"
red = "red"
green = "green"
orange = "orange"
# node shapes
circle = "circle"
if isinstance(self, nx.MultiGraph):
GephiGraph = nx.MultiDiGraph()
else:
GephiGraph = nx.DiGraph()
network_nodes = self.nodes(data=True)
network_edges = self.edges(data=True)
# add all nodes to the render graph
for node in network_nodes:
if node[1]["end"] and not node[1]["leaf"]:
node_type = "end"
node_color = red
node_shape = circle
elif node[1]["leaf"] and not node[1]["end"]:
node_type = "leaf"
node_color = green
node_shape = circle
elif node[1]["leaf"] and node[1]["end"]:
node_type = "end leaf"
node_color = orange
node_shape = circle
else:
node_type = "regular"
node_color = black
node_shape = circle
nodeAttrs = {"color": node_color,
"shape": node_shape,
"type": node_type}
GephiGraph.add_node(node[0], attr_dict=nodeAttrs)
# ad all edges to the render graph
for edge in network_edges:
if edge[2]["weft"]:
edge_type = "weft"
edge_color = blue
elif edge[2]["warp"]:
edge_type = "warp"
edge_color = red
elif not edge[2]["weft"] and not edge[2]["warp"]:
continue
edgeAttrs = {"color": edge_color,
"type": edge_type}
GephiGraph.add_edge(edge[0], edge[1], attr_dict=edgeAttrs)
return GephiGraph
# NODE CREATION -----------------------------------------------------------
[docs] def node_from_point3d(self, node_index, pt, position=None, num=None,
leaf=False, start=False, end=False, segment=None,
increase=False, decrease=False, color=None):
"""
Creates a network node from a Rhino Point3d and attributes.
Parameters
----------
node_index : hashable
The index of the node in the network. Usually an integer is used.
pt : :class:`Rhino.Geometry.Point3d`
A RhinoCommon Point3d object.
position : hashable, optional
The 'position' attribute of the node identifying the underlying
contour edge of the network.
Defaults to ``None``.
num : int, optional
The 'num' attribute of the node representing its index in the
underlying contour edge of the network.
Defaults to ``None``.
leaf : bool, optional
The 'leaf' attribute of the node identifying it as a node on the
first or last course of the knitting pattern.
Defaults to ``False``.
start : bool, optional
The 'start' attribute of the node identifying it as the start of
a course.
Defaults to ``False``.
end : bool, optional
The 'end' attribute of the node identifying it as the end of a
segment or course.
Defaults to ``False``.
segment : :obj:`tuple` of :obj:`int`, optional
The 'segment' attribute of the node identifying its position
between two 'end' nodes.
Defaults to ``None``.
increase : bool, optional
The 'increase' attribute identifying the node as an increase
(needed for translation from dual to 2d knitting pattern).
Defaults to ``False``.
decrease : bool, optional
The 'decrease' attribute identifying the node as a decrease
(needed for translation from dual to 2d knitting pattern).
Defaults to ``False``.
color : :obj:`System.Drawing.Color`, optional
The 'color' attribute of the node, representing the color of the
pixel when translating the network to a 2d knitting pattern.
Defaults to ``None``.
"""
# extract node coordinates
nodeX = pt.X
nodeY = pt.Y
nodeZ = pt.Z
# compile node attributes
node_attributes = {"x": nodeX,
"y": nodeY,
"z": nodeZ,
"position": position,
"num": num,
"leaf": leaf,
"start": start,
"end": end,
"segment": segment,
"increase": increase,
"decrease": decrease,
"geo": pt,
"color": color}
# add the node to the network instance
self.add_node(node_index, attr_dict=node_attributes)
# NODE GEOMETRY -----------------------------------------------------------
[docs] def node_geometry(self, node_index):
"""
Gets the geometry from the 'geo' attribute of the supplied node.
Parameters
----------
node_index : hashable
The unique identifier of the node, an int in most cases.
Returns
-------
geometry : data
The data of the 'geo' attribute of the specified node or ``None``
if the node is not present or has no 'geo' attribute.
"""
try:
return self.node[node_index]["geo"]
except KeyError:
return None
[docs] def node_coordinates(self, node_index):
"""
Gets the node coordinates from the 'x', 'y' and 'z' attributes of the
supplied node.
Parameters
----------
node_index : hashable
The unique identifier of the node, an int in most cases.
Returns
-------
xyz : :obj:`tuple` of :obj:`int`
The XYZ coordinates of the node as a 3-tuple.
"""
try:
node_data = self.node[node_index]
NodeX = node_data["x"]
NodeY = node_data["y"]
NodeZ = node_data["z"]
return (NodeX, NodeY, NodeZ)
except KeyError:
return None
# PROPERTIES --------------------------------------------------------------
def _get_total_positions(self):
"""
Gets the number of total positions (i.e. contours) inside the network.
"""
total = max([n[1]["position"] for n in self.nodes_iter(data=True)])+1
return total
total_positions = property(
_get_total_positions,
None,
None,
"The total number of positions (i.e. contours) " +
"inside the network")
# NODES ON POSITION CONTOURS ----------------------------------------------
[docs] def nodes_on_position(self, position, data=False):
"""
Gets the nodes on a given position (i.e. contour) by returning all
nodes which share the given value as their 'position' attribute.
Parameters
----------
position : hashable
The index of the position.
data : bool, optional
If ``True``, found nodes will be returned with their attribute
data.
Defaults to ``False``.
Returns
-------
nodes : :obj:`list`
The nodes sharing the supplied 'position' attribute.
"""
nodes = [(n, d) for n, d in self.nodes_iter(data=True)
if d["position"] == position]
nodes.sort(key=lambda x: x[1]["num"])
if not data:
nodes = [n[0] for n in nodes]
return nodes
[docs] def all_nodes_by_position(self, data=False):
"""
Gets all the nodes of the network, ordered by the values of their
'position' attribute.
Parameters
----------
data : bool, optional
If ``True``, found nodes will be returned with their attribute
data.
Defaults to ``False``.
Returns
-------
nodes : :obj:`list` of :obj:`list`
All nodes grouped by their 'position' attribute
"""
allPositionNodes = sorted(
[(n, d) for n, d in self.nodes_iter(data=True)
if d["position"] != None],
key=lambda x: x[1]["position"])
posdict = OrderedDict()
for n in allPositionNodes:
if n[1]["position"] not in posdict:
posdict[n[1]["position"]] = [n]
else:
posdict[n[1]["position"]].append(n)
anbp = []
for key in posdict:
posnodes = sorted(posdict[key], key=lambda x: x[1]["num"])
if data:
anbp.append(posnodes)
else:
anbp.append([pn[0] for pn in posnodes])
return anbp
# NODES ON SEGMENT CONTOURS -----------------------------------------------
[docs] def nodes_on_segment(self, segment, data=False):
"""
Gets all nodes on a given segment by finding all nodes which share the
specified value as their 'segment' attribute, ordered by the value of
their 'num' attribute.
Parameters
----------
segment : hashable
The identifier of the segment to look for.
data : bool, optional
If ``True``, found nodes will be returned with their attribute
data.
Defaults to ``False``.
Returns
-------
nodes : :obj:`list`
List of nodes sharing the supplied value as their 'segment'
attribute, ordered by their 'num' attribute.
"""
nodes = [(n, d) for n, d in self.nodes_iter(data=True)
if d["segment"] == segment]
nodes.sort(key=lambda x: x[1]["num"])
if data:
return nodes
else:
return [n[0] for n in nodes]
# LEAF NODES --------------------------------------------------------------
def _get_leaf_nodes(self):
"""
Gets all 'leaf' nodes of the network.
Returns
-------
nodes : :obj:`list`
List of all nodes for which the attribute 'leaf' is ``True``
"""
leaves = [(n, d) for n, d in self.nodes_iter(data=True)
if d["leaf"] == True]
return leaves
leaf_nodes = property(_get_leaf_nodes, None, None,
"All 'leaf' nodes of the network.")
[docs] def leaves_on_position(self, position, data=False):
"""
Gets all 'leaf' nodes which share the supplied value as their
'position' attribute.
Parameters
----------
position : hashable
The index / identifier of the position
data : bool, optional
If ``True``, found nodes will be returned with their attribute
data.
Defaults to ``False``.
Returns
-------
nodes : :obj:`list`
List of all nodes for which the attribute 'leaf' is ``True`` and
which share the supplied value as their 'position' attribute
"""
leaves = [(n, d) for n, d
in self.nodes_on_position(position, data=True)
if d["leaf"]]
if not data:
leaves = [n[0] for n in leaves]
return leaves
[docs] def all_leaves_by_position(self, data=False):
"""
Gets all 'leaf' nodes ordered by their 'position' attribute.
Parameters
----------
data : bool, optional
If ``True``, found nodes will be returned with their attribute
data.
Defaults to ``False``.
Returns
-------
nodes : :obj:`list` of :obj:`list`
All nodes for which the attribute 'leaf' is true, grouped by their
'position' attribute
"""
allPositionLeaves = sorted(
[(n, d) for n, d in self.nodes_iter(data=True)
if d["position"] != None and d["leaf"]],
key=lambda x: x[1]["position"])
posdict = OrderedDict()
for n in allPositionLeaves:
if n[1]["position"] not in posdict:
posdict[n[1]["position"]] = [n]
else:
posdict[n[1]["position"]].append(n)
albp = []
for key in posdict:
posleaves = sorted(posdict[key], key=lambda x: x[1]["num"])
if data:
albp.append(posleaves)
else:
albp.append([pl[0] for pl in posleaves])
return albp
# END NODES ---------------------------------------------------------------
def _get_end_nodes(self):
"""
Gets all 'end' nodes of the network.
"""
ends = [(n, d) for n, d in self.nodes_iter(data=True)
if d["end"]]
return ends
end_nodes = property(_get_end_nodes, None, None,
"All 'end' nodes of the network")
[docs] def ends_on_position(self, position, data=False):
"""
Gets all 'end' nodes which share the supplied value as their 'position'
attribute.
Parameters
----------
position : hashable
The index / identifier of the position
data : bool, optional
If ``True``, found nodes will be returned with their attribute
data.
Defaults to ``False``.
Returns
-------
nodes : :obj:`list`
List of all nodes for which the attribute 'end' is ``True`` and
which share the supplied value as their 'position' attribute
"""
ends = [(n, d) for n, d in self.nodes_on_position(position, data=True)
if d["end"]]
if not data:
return [n[0] for n in ends]
return ends
[docs] def all_ends_by_position(self, data=False):
"""
Gets all 'end' nodes ordered by their 'position' attribute.
Parameters
----------
data : bool, optional
If ``True``, found nodes will be returned with their attribute
data.
Defaults to ``False``.
Returns
-------
nodes : :obj:`list` of :obj:`list`
All nodes for which the attribute 'end' is true, grouped by their
'position' attribute
"""
allPositionEnds = sorted(
[(n, d) for n, d in self.nodes_iter(data=True)
if d["position"] != None and d["end"]],
key=lambda x: x[1]["position"])
posdict = OrderedDict()
for n in allPositionEnds:
if n[1]["position"] not in posdict:
posdict[n[1]["position"]] = [n]
else:
posdict[n[1]["position"]].append(n)
aebp = []
for key in posdict:
posends = sorted(posdict[key], key=lambda x: x[1]["num"])
if data:
aebp.append(posends)
else:
aebp.append([pe[0] for pe in posends])
return aebp
# POSITION CONTOUR METHODS ------------------------------------------------
[docs] def geometry_at_position_contour(self, position, as_crv=False):
"""
Gets the contour polyline at a given position by making a polyline
from all nodes which share the specified 'position' attribute.
Parameters
----------
position : hashable
The index / identifier of the position
as_crv : bool, optional
If ``True``, will return a PolylineCurve instead of a Polyline.
Defaults to ``False``.
Returns
-------
contour : :obj:`Rhino.Geometry.Polyline`
The contour as a Polyline if ``as_crv`` is ``False``.
contour : :obj:`Rhino.Geometry.PolylineCurve`
The contour as a PolylineCurve if ``as_crv`` is ``True``.
"""
points = [n[1]["geo"] for n in self.nodes_on_position(position, True)]
Contour = RhinoPolyline(points)
if as_crv:
Contour = Contour.ToPolylineCurve()
return Contour
[docs] def longest_position_contour(self):
"""
Gets the longest contour 'position', geometry andgeometric length.
Returns
-------
contour_data : :obj:`tuple`
3-tuple of the 'position' identifier, the contour geometry and its
length.
"""
longestLength = 0
longestContour = None
longestPosition = None
for i in range(self.total_positions):
contour = self.geometry_at_position_contour(i, True)
cl = contour.GetLength()
if cl > longestLength:
longestLength = cl
longestContour = contour.Duplicate()
longestPosition = i
contour.Dispose()
return (longestPosition, longestContour, longestLength)
# EDGE CREATION METHODS ---------------------------------------------------
[docs] def create_contour_edge(self, from_node, to_node):
"""
Creates an edge neither 'warp' nor 'weft' between two nodes in the
network.
Parameters
----------
from_node : :obj:`tuple`
2-tuple of (node_identifier, node_data) that represents the edges'
source node.
to_node : :obj:`tuple`
2-tuple of (node_identifier, node_data) that represents the edges'
target node.
Returns
-------
success : bool
``True`` if the edge has been successfully created,
``False`` otherwise.
"""
# get node indices
fromNode = from_node[0]
toNode = to_node[0]
# get geometry from nodes
fromGeo = from_node[1]["geo"]
toGeo = to_node[1]["geo"]
# create edge geometry
edgeGeo = RhinoLine(fromGeo, toGeo)
# create edge attribute
edgeAttrs = {"warp": False,
"weft": False,
"segment": None,
"geo": edgeGeo}
try:
self.add_edge(fromNode, toNode, attr_dict=edgeAttrs)
except Exception:
return False
return True
[docs] def create_weft_edge(self, from_node, to_node, segment=None):
"""
Creates a 'weft' edge between two nodes in the network.
Parameters
----------
from_node : :obj:`tuple`
2-tuple of (node_identifier, node_data) that represents the edges'
source node.
to_node : :obj:`tuple`
2-tuple of (node_identifier, node_data) that represents the edges'
target node.
segment : :obj:`tuple`
3-tuple that will be used to set the 'segment' attribute of the
'weft' edge.
Returns
-------
success : bool
``True`` if the edge has been successfully created.
``False`` otherwise.
"""
# get node indices
fromNode = from_node[0]
toNode = to_node[0]
# get geometry from nodes
fromGeo = from_node[1]["geo"]
toGeo = to_node[1]["geo"]
# create edge geometry
edgeGeo = RhinoLine(fromGeo, toGeo)
# create edge attribute
edgeAttrs = {"warp": False,
"weft": True,
"segment": segment,
"geo": edgeGeo}
try:
self.add_edge(fromNode, toNode, attr_dict=edgeAttrs)
except Exception:
return False
return True
[docs] def create_warp_edge(self, from_node, to_node):
"""
Creates a 'warp' edge between two nodes in the network.
Parameters
----------
from_node : :obj:`tuple`
2-tuple of (node_identifier, node_data) that represents the edges'
source node.
to_node : :obj:`tuple`
2-tuple of (node_identifier, node_data) that represents the edges'
target node.
Returns
-------
success : bool
``True`` if the edge has been successfully created.
``False`` otherwise.
"""
# get node indices
fromNode = from_node[0]
toNode = to_node[0]
# get geometry from nodes
fromGeo = from_node[1]["geo"]
toGeo = to_node[1]["geo"]
# create edge geometry
edgeGeo = RhinoLine(fromGeo, toGeo)
# create edge attribute
edgeAttrs = {"warp": True,
"weft": False,
"segment": None,
"geo": edgeGeo}
try:
self.add_edge(fromNode, toNode, attr_dict=edgeAttrs)
except Exception:
return False
return True
[docs] def create_segment_contour_edge(self, from_node, to_node,
segment_value, segment_geo):
"""
Creates a mapping edge between two 'end' nodes in the network. The
geometry of this edge will be a polyline built from all the given
former 'weft' edges. returns True if the edge has been successfully
created.
Parameters
----------
from_node : :obj:`tuple`
2-tuple of (node_identifier, node_data) that represents the edges'
source node.
to_node : :obj:`tuple`
2-tuple of (node_identifier, node_data) that represents the edges'
target node.
segment_value : :obj:`tuple` of :obj:`int`
3-tuple that will be used to set the 'segment' attribute of the
'weft' edge.
segment_geo : :obj:`list` of :class:`Rhino.Geometry.Line`
the geometry of all 'weft' edges that make this segment contour
edge
Returns
-------
success : bool
``True`` if the edge has been successfully created,
``False`` otherwise
"""
# get node indices
fromNode = from_node[0]
toNode = to_node[0]
# join geo together
segment_geo = [RhinoLineCurve(ln) for ln in segment_geo]
edgeGeo = RhinoCurve.JoinCurves(segment_geo)
if len(edgeGeo) > 1:
errMsg = ("Segment geometry could not be joined into " +
"one single curve for segment {}!".format(segment_value))
print(errMsg)
return False
edgeGeo = edgeGeo[0].ToPolyline()
if not edgeGeo[0] == from_node[1]["geo"]:
edgeGeo.Reverse()
# create edge attribute
edgeAttrs = {"warp": False,
"weft": False,
"segment": segment_value,
"geo": edgeGeo}
self.add_node(fromNode, attr_dict=from_node[1])
self.add_node(toNode, attr_dict=to_node[1])
try:
self.add_edge(fromNode, toNode, attr_dict=edgeAttrs)
except Exception:
return False
return True
# EDGE METHODS ------------------------------------------------------------
[docs] def edge_geometry_direction(self, u, v):
"""
Returns a given edge in order with reference to the direction of the
associated geometry (line).
Parameters
----------
u : hashable
Hashable identifier of the edges source node.
v : hashable
Hashable identifier of the edges target node.
Returns
-------
edge : 2-tuple
2-tuple of (u, v) or (v, u) depending on the directions
"""
# get geometry data of the edge
edge_geo = self[u][v]["geo"]
# compare start and endpoint and return nodes in order accordingly
if (edge_geo.From == self.node[u]["geo"]
and edge_geo.To == self.node[v]["geo"]):
return (u, v)
else:
return (v, u)
# EDGE PROPERTIES ---------------------------------------------------------
def _get_contour_edges(self):
"""
Get all contour edges of the network that are neither 'weft' nor
'warp'.
"""
contour_edges = [(f, t, d) for f, t, d in self.edges_iter(data=True)
if not d["weft"] and not d["warp"]]
for i, ce in enumerate(contour_edges):
if ce[0] > ce[1]:
contour_edges[i] = (ce[1], ce[0], ce[2])
return contour_edges
contour_edges = property(_get_contour_edges, None, None,
"The contour edges of the network marked " +
"neither 'weft' nor 'warp'.")
def _get_weft_edges(self):
"""
Get all 'weft' edges of the network.
"""
weft_edges = [(f, t, d) for f, t, d in self.edges_iter(data=True)
if d["weft"] and not d["warp"]]
for i, we in enumerate(weft_edges):
if we[0] > we[1]:
weft_edges[i] = (we[1], we[0], we[2])
return weft_edges
weft_edges = property(_get_weft_edges, None, None,
"The edges of the network marked 'weft'.")
def _get_warp_edges(self):
"""
Get all 'warp' edges of the network.
"""
warp_edges = [(f, t, d) for f, t, d in self.edges_iter(data=True)
if not d["weft"] and d["warp"]]
for i, we in enumerate(warp_edges):
if we[0] > we[1]:
warp_edges[i] = (we[1], we[0], we[2])
return warp_edges
warp_edges = property(_get_warp_edges, None, None,
"The edges of the network marked 'warp'.")
def _get_segment_contour_edges(self):
"""
Get all contour edges of the network marked neither 'warp' nor 'weft'
that have a 'segment' attribute assigned sorted by the 'segment'
attribute.
"""
segment_contour_edges = [(f, t, d) for f, t, d
in self.edges_iter(data=True)
if not d["weft"] and
not d["warp"]]
segment_contour_edges = [sce for sce in segment_contour_edges
if sce[2]["segment"]]
for i, sce in enumerate(segment_contour_edges):
if sce[0] > sce[1]:
segment_contour_edges[i] = (sce[1], sce[0], sce[2])
# sort them by their 'segment' attributes value
segment_contour_edges.sort(key=lambda x: x[2]["segment"])
return segment_contour_edges
segment_contour_edges = property(
_get_segment_contour_edges,
None,
None,
"The edges of the network marked neither 'warp' " +
"nor 'weft' and which have a 'segment' attribute " +
"assigned to them.")
# NODE EDGE METHODS -------------------------------------------------------
[docs] def node_weft_edges(self, node, data=False):
"""
Gets the 'weft' edges connected to a given node.
Parameters
----------
node : hashable
Hashable identifier of the node to check for 'weft' edges.
data : bool, optional
If ``True``, the edges will be returned as 3-tuples with their
associated attribute data.
Defaults to ``False``.
Returns
-------
edges : :obj:`list`
List of 'weft' edges connected to the given node. Each item in the
list will be either a 2-tuple of (u, v) identifiers or a 3-tuple
of (u, v, d) where d is the attribute data of the edge, depending
on the data parameter.
"""
weft_edges = [(s, e, d) for s, e, d in
self.edges_iter(node, data=True) if d["weft"]]
if data:
return weft_edges
else:
return [(e[0], e[1]) for e in weft_edges]
[docs] def node_warp_edges(self, node, data=False):
"""
Gets the 'warp' edges connected to the given node.
Parameters
----------
node : hashable
Hashable identifier of the node to check for 'warp' edges.
data : bool, optional
If ``True``, the edges will be returned as 3-tuples with their
associated attribute data.
Defaults to ``False``.
Returns
-------
edges : :obj:`list`
List of 'warp' edges connected to the given node. Each item in the
list will be either a 2-tuple of (u, v) identifiers or a 3-tuple
of (u, v, d) where d is the attribute data of the edge, depending
on the data parameter.
"""
warp_edges = [(s, e, d) for s, e, d in
self.edges_iter(node, data=True) if d["warp"]]
if data:
return warp_edges
else:
return [(e[0], e[1]) for e in warp_edges]
[docs] def node_contour_edges(self, node, data=False):
"""
Gets the edges marked neither 'warp' nor 'weft' connected to the
given node.
Parameters
----------
node : hashable
Hashable identifier of the node to check for edges marked neither
'warp' nor 'weft'.
data : bool, optional
If ``True``, the edges will be returned as 3-tuples with their
associated attribute data.
Defaults to ``False``.
Returns
-------
edges : :obj:`list`
List of edges marked neither 'warp' nor 'weft' connected to the
given node. Each item in the list will be either a 2-tuple of
(u, v) identifiers or a 3-tuple of (u, v, d) where d is the
attribute data of the edge, depending on the data parameter.
"""
contour_edges = [(s, e, d) for s, e, d in
self.edges_iter(node, data=True)
if not d["warp"] and not d["weft"]]
if data:
return contour_edges
else:
return [(e[0], e[1]) for e in contour_edges]
# SEGMENT CONTOUR END NODE METHODS ----------------------------------------
[docs] def end_node_segments_by_start(self, node, data=False):
"""
Get all the edges with a 'segment' attribute marked neither 'weft' nor
'warp' and share a given 'end' node at the start, sorted by the values
of their 'segment' attribute.
Parameters
----------
node : hashable
Hashable identifier of the node to check for connected segments.
data : bool, optional
If ``True``, the edges will be returned as 3-tuples with their
associated attribute data.
Defaults to ``False``.
Returns
-------
edges : :obj:`list`
List of edges. Each item will be either a 2-tuple of (u, v)
identifiers or a 3-tuple of (u, v, d) where d is the attribute data
of the edge, depending on the data parameter.
"""
connected_segments = [(s, e, d) for s, e, d
in self.edges_iter(node, data=True) if
not d["warp"] and not d["weft"]]
connected_segments = [cs for cs in connected_segments
if cs[2]["segment"]]
connected_segments = [cs for cs in connected_segments
if cs[2]["segment"][0] == node]
connected_segments.sort(key=lambda x: x[2]["segment"])
if data:
return connected_segments
else:
return [(cs[0], cs[1]) for cs in connected_segments]
[docs] def end_node_segments_by_end(self, node, data=False):
"""
Get all the edges with a 'segment' attribute marked neither 'weft' nor
'warp' and share a given 'end' node at the end, sorted by the values
of their 'segment' attribute.
Parameters
----------
node : hashable
Hashable identifier of the node to check for connected segments.
data : bool, optional
If ``True``, the edges will be returned as 3-tuples with their
associated attribute data.
Defaults to ``False``.
Returns
-------
edges : list
List of edges. Each item will be either a 2-tuple of (u, v)
identifiers or a 3-tuple of (u, v, d) where d is the attribute data
of the edge, depending on the data parameter.
"""
connected_segments = [(s, e, d) for s, e, d
in self.edges_iter(node, data=True) if
not d["warp"] and not d["weft"]]
connected_segments = [cs for cs in connected_segments
if cs[2]["segment"]]
connected_segments = [cs for cs in connected_segments
if cs[2]["segment"][1] == node]
connected_segments.sort(key=lambda x: x[2]["segment"])
if data:
return connected_segments
else:
return [(cs[0], cs[1]) for cs in connected_segments]
# MAIN ------------------------------------------------------------------------
if __name__ == '__main__':
pass