Python Tutorials | (back to the list of tutorials) |
We start with reroduction-base agents to design networking agents and later it's integrated with particle-based agents. The code below defines a reproduction-base agent who a new agent in a random location in a specified distance and make connection to it. One agent makes just one child agent and as a result, it becomes just a single string of agents. The agent inherets IPointAgent which has a property of point and a methodd pos().
add_library('igeo') def setup() : size(480,360,IG.GL) NodeAgent(IVec(0,0,0)) class NodeAgent(IPointAgent) : def __init__(self, p) : IPointAgent.__init__(self,p) def update(self) : if self.time() == 0 : #just once when it's created dir = IRand.dir(10) #random direction with length 10 pos2 = self.pos().cp().add(dir) #new position by adding dir ICurve(self.pos(), pos2) #link line NodeAgent(pos2) #child agent
The agent in the next code creates a child agent in a different timing from the previous one. The previous code creates a child agent just once in the first time frame after it's created. On the other hand next one tries to create a child agent every time frame in a random probability of 1%.
add_library('igeo')
def setup() :
size(480,360,IG.GL)
NodeAgent(IVec(0,0,0))
class NodeAgent(IPointAgent) :
def __init__(self, p) :
IPointAgent.__init__(self,p)
def update(self) :
if IRand.pct(1) : #random probability of 1%
dir = IRand.dir(10)
pos2 = self.pos().cp().add(dir)
ICurve(self.pos(), pos2)
NodeAgent(pos2)
As a result of this algorithm, one agent could create multiple child agents over time and branches of linked lines. The topological form of the network generated here is a tree structure and there is no loop in the network.
The next code generates the same network topology in a tree structure but the geometrical layout is controlled by orienting the direction to put a child agent towards the specified vector (1,0,0) (x-axis). The direction is calculated by the method IRand.dir(orientation, length, angle_range) and this method returns a vector towards a random direction within the angle range from the specified orientation and the vector length is also specified. As a result, it generates branches towards x-axis direction.
add_library('igeo') def setup() : size(480,360,IG.GL) NodeAgent(IVec(0,0,0)) class NodeAgent(IPointAgent) : def __init__(self, p) : IPointAgent.__init__(self,p) def update(self) : if IRand.pct(1) : #random probability of 1% #random vector towards 1,0,0 within PI/4 range dir = IRand.dir(IVec(1,0,0), 10, PI/4) pos2 = self.pos().cp().add(dir) ICurve(self.pos(), pos2) NodeAgent(pos2)
In the interact method, an agent checks all existing agents and pick ones whose type is NodeAgent. Then after excluding itself, it measures the distance between the agent and the other. If it's closer than the parameter linkThreshold, then draws a line as a link in 0.1% probability.
add_library('igeo') def setup() : size(480,360,IG.GL) NodeAgent(IVec(0,0,0)) class NodeAgent(IPointAgent) : linkLength = 10 #distance of child agent linkThreshold = 10 #distance threshold to connect existing agent def __init__(self, p) : IPointAgent.__init__(self,p) # connecting to existing agent def interact(self, agents) : for agent in agents : if isinstance(agent, NodeAgent) : #check type of agent if agent is not this : #exclude itself if agent.pos().dist(self.pos()) < NodeAgent.linkThreshold : #closer than threshold if IRand.pct(0.1) : #in a probability of 0.1% ICurve(agent.pos(), self.pos()).clr(1.0,0,0) #red line def update(self) : if IRand.pct(1) : #random probability of 1% #random vector towards 1,0,0 within PI/4 range dir = IRand.dir(NodeAgent.linkLength) pos2 = self.pos().cp().add(dir) ICurve(self.pos(), pos2) NodeAgent(pos2)
The next code simply add the interact method to the branching agent oriented towards x-axis direction in the previous section.
add_library('igeo') def setup() : size(480,360,IG.GL) NodeAgent(IVec(0,0,0)) class NodeAgent(IPointAgent) : linkLength = 10 #distance of child agent linkThreshold = 10 #distance threshold to connect existing agent def __init__(self, p) : IPointAgent.__init__(self,p) # connecting to existing agent def interact(self, agents) : for agent in agents : if isinstance(agent, NodeAgent) : #check type of agent if agent is not this : #exclude itself if agent.pos().dist(self.pos()) < NodeAgent.linkThreshold : #closer than threshold if IRand.pct(0.1) : #in a probability of 0.1% ICurve(agent.pos(), self.pos()).clr(1.0,0,0) #red line def update(self) : if IRand.pct(1) : #random probability of 1% #random vector towards 1,0,0 within PI/4 range dir = IRand.dir(IVec(1,0,0), NodeAgent.linkLength, PI/4) pos2 = self.pos().cp().add(dir) ICurve(self.pos(), pos2) NodeAgent(pos2)
The next code modifies the previous one a little bit by adding a new property dir for each agent and let it set the property when it's created at the constructor NodeAgent(IVec p, IVec d). Then this property dir is used as orientation of the direction to create a new child.
In setup() method, two instances of NodeAgent are created with different starting locations and opposite direction for the input of dir. When two networks grow out of two different parents towards opposite direction, two networks are interconnected by the algorithm defined in interact method.
add_library('igeo') def setup() : size(480,360,IG.GL) NodeAgent(IVec(0,0,0), IVec(1,0,0)) NodeAgent(IVec(100,0,0), IVec(-1,0,0)) class NodeAgent(IPointAgent) : linkLength = 10 #distance of child agent linkThreshold = 10 #distance threshold to connect existing agent def __init__(self, p, d) : IPointAgent.__init__(self,p) self.dir = d # connecting to existing agent def interact(self, agents) : for agent in agents : if isinstance(agent, NodeAgent) : #check type of agent if agent is not this : #exclude itself if agent.pos().dist(self.pos()) < NodeAgent.linkThreshold : #closer than threshold if IRand.pct(0.1) : #in a probability of 0.1% ICurve(agent.pos(), self.pos()).clr(1.0,0,0) #red line def update(self) : if IRand.pct(1) : #random probability of 1% #random vector towards 1,0,0 within PI/4 range dir2 = IRand.dir(self.dir, NodeAgent.linkLength, PI/4) pos2 = self.pos().cp().add(dir2) ICurve(self.pos(), pos2) NodeAgent(pos2, self.dir)
add_library('igeo') def setup() : size(480, 360, IG.GL) NodeAgent(IVec(0, 0, 0)) class NodeAgent(IParticle) : linkLength = 10 linkThreshold = 8.5 tension = 10 # tensile force of link repulsion = 200 # repulsion force of node growthTime = 500 # network growth stops at this time def __init__(self, p) : IParticle.__init__(self, p) self.fric(0.1) # 10% friction def interact(self, agents) : for agent in agents : if isinstance(agent, NodeAgent) : if agent is not self : if IG.time() < NodeAgent.growthTime : if agent.pos().dist(self.pos()) < NodeAgent.linkThreshold : if IRand.pct(0.1) : ITensionLine(agent, self, NodeAgent.tension).clr(1.0, 0, 0) dif = agent.pos().dif(self.pos()) #vector to the other node if dif.len() > 0 : dif.len(NodeAgent.repulsion/dif.len2() ) #the closer, the larger agent.push(dif) def update(self) : if IG.time() < NodeAgent.growthTime : #only for limited time if IRand.pct(1) : dir = IRand.dir(NodeAgent.linkLength) pos2 = self.pos().cp().add(dir) child = NodeAgent(pos2) ITensionLine(self, child, NodeAgent.tension)
The code below is the directed version of node agent and two networks run against each other with their roots fixed by fix() method inside setup() method.
add_library('igeo') def setup() : size(480, 360, IG.GL) NodeAgent(IVec(0, 0, 0), IVec(1,0,0)).fix().clr(0,0,1.0) NodeAgent(IVec(100, 0, 0), IVec(-1,0,0)).fix().clr(0,0,1.0) class NodeAgent(IParticle) : linkLength = 20 linkThreshold = 8.5 tension = 10 # tensile force of link repulsion = 200 # repulsion force of node growthTime = 600 # network growth stops at this time def __init__(self, p, d) : IParticle.__init__(self, p) self.dir = d self.fric(0.1) # 10% friction def interact(self, agents) : for agent in agents : if isinstance(agent, NodeAgent) : if agent is not self : if IG.time() < NodeAgent.growthTime : if agent.pos().dist(self.pos()) < NodeAgent.linkThreshold : if IRand.pct(0.1) : ITensionLine(agent, self, NodeAgent.tension).clr(1.0, 0, 0) dif = agent.pos().dif(self.pos()) #vector to the other node if dif.len() > 0 : dif.len(NodeAgent.repulsion/dif.len2() ) #the closer, the larger agent.push(dif) def update(self) : if IG.time() < NodeAgent.growthTime : #only for limited time if IRand.pct(1) : dir2 = IRand.dir(self.dir, NodeAgent.linkLength, PI/4) pos2 = self.pos().cp().add(dir2) child = NodeAgent(pos2, self.dir) ITensionLine(self, child, NodeAgent.tension)
The procedure to generate this type of network is also researched and Barabasi-Albert model is known to produce the power law distribution in degree of nodes and it's based on preferential attachment process
We try to simulate the behavior to generate a network with similar degree distribution to those types of networks. For this purpose, we calculate the probability to make a connection by a degree. We also need to keep track of connected nodes to calculate a degree, which is a total number of connected nodes. Connected nodes and links are stored in ArrayList properties called children and links. The part of code to make a connection and to calculate a degree is implemented as methods of connect(NodeAgnet node) and degree().
add_library('igeo') def setup() : size(480, 360, IG.GL) NodeAgent(IVec(0, 0, 0)) class NodeAgent(IParticle) : linkLength = 10 linkThreshold = 25 tension = 10 repulsion = 200 growthTime = 350 def __init__(self, p) : IParticle.__init__(self,p) self.fric(0.1) self.children = [] #to keep track of connected nodes self.links = [] #to keep track of links # adding a method to make connection def connect(self, node) : if node not in self.children : #only when not connected yet self.children.append(node) node.children.append(self) link = ITensionLine(self, node, NodeAgent.tension) self.links.append(link) node.links.append(link) self.clr(self.degree()*0.1, 0, 0) node.clr(node.degree()*0.1, 0, 0) # adding a method to return degree (number of connection) def degree(self) : return len(self.children) def interact(self, agents) : for agent in agents : if isinstance(agent, NodeAgent) : if agent is not self : if IG.time() < NodeAgent.growthTime : if agent.pos().dist(self.pos()) < NodeAgent.linkThreshold : probability = sqrt(agent.degree()-5)*0.05 if IRand.pct(probability) : #higher degree is more probable self.connect(agent) dif = agent.pos().dif(self.pos()) if dif.len() > 0 : dif.len(NodeAgent.repulsion*self.degree()/dif.len2()) agent.push(dif) def update(self) : if IG.time() < NodeAgent.growthTime : probability = sqrt(self.degree()+1) if IRand.pct(probability) : #higher degree is more probable dir = IRand.dir(NodeAgent.linkLength) pos2 = self.pos().cp().add(dir) child = NodeAgent(pos2) self.connect(child)
To do this, it's best to save the network in Processing into a Rhino file once as points and lines. Then you can edit it by removing / adding points and lines. The following script can read a Rhino 3DM file (version 4) and convert all points into NodeAgent and check all lines to find which nodes are connected. It can also check which node should be fixed by a layer name. If points are in "fix" layer, the node made out of these points are fixed. You can change the layer name in the script as well. Note that the script assumes all curve in the edited model are lines and if there are NURBS curves or polylines, the result might be unexpected. If you add new lines and if it contains polylines, they should be exploded in Rhino.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.open("network1.3dm") #put all lines, all points, and points in "fix" layer. buildNetwork(IG.curves(),IG.points(),IG.layer("fix").points()) def buildNetwork(lines, points, fixPoints) : pnum = len(points) lnum = len(lines) fnum = len(fixPoints) agents = [] # creating node agents by points for i in range(pnum) : agents.append(NodeAgent(points[i].pos())) # linking agents by lines for i in range(lnum) : pt1 = lines[i].cp(0) pt2 = lines[i].cp(1) tolerance = 0.1 found = False for j in range(pnum) : if found : break if agents[j].pos().dist(pt1) < tolerance : for k in range(pnum) : if found : break if agents[k].pos().dist(pt2) < tolerance : agents[j].connect(agents[k]) found = True lines[i].del() # fixing agents for i in range(pnum) : for j in range(fnum) : if points[i] == fixPoints[j] : agents[i].fix().clr(0,1.0,1.0) points[i].del() class NodeAgent(IParticle) : linkLength = 10 linkThreshold = 25 tension = 10 repulsion = 200 growthTime = 0 # no growth def __init__(self, p) : IParticle.__init__(self,p) self.fric(0.1) self.children = [] #to keep track of connected nodes self.links = [] #to keep track of links # adding a method to make connection def connect(self, node) : if node not in self.children : #only when not connected yet self.children.append(node) node.children.append(self) link = ITensionLine(self, node, NodeAgent.tension) self.links.append(link) node.links.append(link) self.clr(self.degree()*0.1, 0, 0) node.clr(node.degree()*0.1, 0, 0) # adding a method to return degree (number of connection) def degree(self) : return len(self.children) def interact(self, agents) : for agent in agents : if isinstance(agent, NodeAgent) : if agent is not self : if IG.time() < NodeAgent.growthTime : if agent.pos().dist(self.pos()) < NodeAgent.linkThreshold : probability = sqrt(agent.degree()-5)*0.05 if IRand.pct(probability) : #higher degree is more probable self.connect(agent) dif = agent.pos().dif(self.pos()) if dif.len() > 0 : dif.len(NodeAgent.repulsion*self.degree()/dif.len2()) agent.push(dif) def update(self) : if IG.time() < NodeAgent.growthTime : probability = sqrt(self.degree()+1) if IRand.pct(probability) : #higher degree is more probable dir = IRand.dir(NodeAgent.linkLength) pos2 = self.pos().cp().add(dir) child = NodeAgent(pos2) self.connect(child)
You can edit a network by deleting points, adding points, deleting lines , drawing lines, moving points in "fix" layer in Rhino. When you reconstruct the edited model in Processing, you see new geometric network form under the new topology you made.
The following example does not only reconstruct a network but also adds some forces to deform the geometric network form. The following code adds IGravity upwards to simulate influence of the force in the tensile line network.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.open("network2.3dm") #put all lines, all points, and points in "fix" layer. buildNetwork(IG.curves(),IG.points(),IG.layer("fix").points()) IGravity(0, 0, 0.2); # upward gravity def buildNetwork(lines, points, fixPoints) : pnum = len(points) lnum = len(lines) fnum = len(fixPoints) agents = [] # creating node agents by points for i in range(pnum) : agents.append(NodeAgent(points[i].pos())) # linking agents by lines for i in range(lnum) : pt1 = lines[i].cp(0) pt2 = lines[i].cp(1) tolerance = 0.1 found = False for j in range(pnum) : if found : break if agents[j].pos().dist(pt1) < tolerance : for k in range(pnum) : if found : break if agents[k].pos().dist(pt2) < tolerance : agents[j].connect(agents[k]) found = True lines[i].del() # fixing agents for i in range(pnum) : for j in range(fnum) : if points[i] == fixPoints[j] : agents[i].fix().clr(0,1.0,1.0) points[i].del() class NodeAgent(IParticle) : linkLength = 10 linkThreshold = 25 tension = 10 repulsion = 200 growthTime = 0 # no growth def __init__(self, p) : IParticle.__init__(self,p) self.fric(0.1) self.children = [] #to keep track of connected nodes self.links = [] #to keep track of links # adding a method to make connection def connect(self, node) : if node not in self.children : #only when not connected yet self.children.append(node) node.children.append(self) link = ITensionLine(self, node, NodeAgent.tension) self.links.append(link) node.links.append(link) self.clr(self.degree()*0.1, 0, 0) node.clr(node.degree()*0.1, 0, 0) # adding a method to return degree (number of connection) def degree(self) : return len(self.children) def interact(self, agents) : for agent in agents : if isinstance(agent, NodeAgent) : if agent is not self : if IG.time() < NodeAgent.growthTime : if agent.pos().dist(self.pos()) < NodeAgent.linkThreshold : probability = sqrt(agent.degree()-5)*0.05 if IRand.pct(probability) : #higher degree is more probable self.connect(agent) dif = agent.pos().dif(self.pos()) if dif.len() > 0 : dif.len(NodeAgent.repulsion*self.degree()/dif.len2()) agent.push(dif) def update(self) : if IG.time() < NodeAgent.growthTime : probability = sqrt(self.degree()+1) if IRand.pct(probability) : #higher degree is more probable dir = IRand.dir(NodeAgent.linkLength) pos2 = self.pos().cp().add(dir) child = NodeAgent(pos2) self.connect(child)
If you can model surfaces and solids only by points and lines without checking the connection, you don't need to reconstruct the network but if you use information of connection like number of connection in each node or direction of lines connected to a node, you need to reconstruct a whole network once and then use the node and connection information to model new geometries.
The following code add a sphere at each node and its radius is calculated by a number of connection of the node. A node with more connection gets a larger sphere. The lines are simply converted into cylindrical pipe surfaces with a constant pipe radius.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.open("network3.3dm") IG.duration(0); # no force simulation #put all lines, all points, and points in "fix" layer. buildNetwork(IG.curves(),IG.points(),IG.layer("fix").points()) def buildNetwork(lines, points, fixPoints) : pnum = len(points) lnum = len(lines) fnum = len(fixPoints) agents = [] # creating node agents by points for i in range(pnum) : agents.append(NodeAgent(points[i].pos())) # linking agents by lines for i in range(lnum) : pt1 = lines[i].cp(0) pt2 = lines[i].cp(1) tolerance = 0.1 found = False for j in range(pnum) : if found : break if agents[j].pos().dist(pt1) < tolerance : for k in range(pnum) : if found : break if agents[k].pos().dist(pt2) < tolerance : agents[j].connect(agents[k]) found = True lines[i].del() # fixing agents for i in range(pnum) : for j in range(fnum) : if points[i] == fixPoints[j] : agents[i].fix().clr(0,1.0,1.0) points[i].del() # adding geometry at nodes for agent in agents : ISphere(agent.pos(), agent.degree()) agent.hide() # hide points # adding geometry on links for line in lines : IG.pipe(line.cp(0), line.cp(1), 0.5) class NodeAgent(IParticle) : linkLength = 10 linkThreshold = 25 tension = 10 repulsion = 200 growthTime = 0 # no growth def __init__(self, p) : IParticle.__init__(self,p) self.fric(0.1) self.children = [] #to keep track of connected nodes self.links = [] #to keep track of links # adding a method to make connection def connect(self, node) : if node not in self.children : #only when not connected yet self.children.append(node) node.children.append(self) link = ITensionLine(self, node, NodeAgent.tension) self.links.append(link) node.links.append(link) self.clr(self.degree()*0.1, 0, 0) node.clr(node.degree()*0.1, 0, 0) # adding a method to return degree (number of connection) def degree(self) : return len(self.children)
The following code add a polygon mesh polyhedron at each node. Each vertex of the polyhedron is on the connection line and the size of polyhedron is proportional to a number of connection. The lines have triangular mesh stick with a constant width.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.open("network3.3dm") IG.duration(0); # no force simulation #put all lines, all points, and points in "fix" layer. buildNetwork(IG.curves(),IG.points(),IG.layer("fix").points()) def buildNetwork(lines, points, fixPoints) : pnum = len(points) lnum = len(lines) fnum = len(fixPoints) agents = [] # creating node agents by points for i in range(pnum) : agents.append(NodeAgent(points[i].pos())) # linking agents by lines for i in range(lnum) : pt1 = lines[i].cp(0) pt2 = lines[i].cp(1) tolerance = 0.1 found = False for j in range(pnum) : if found : break if agents[j].pos().dist(pt1) < tolerance : for k in range(pnum) : if found : break if agents[k].pos().dist(pt2) < tolerance : agents[j].connect(agents[k]) found = True lines[i].del() # fixing agents for i in range(pnum) : for j in range(fnum) : if points[i] == fixPoints[j] : agents[i].fix().clr(0,1.0,1.0) points[i].del() # adding geometry at nodes for agent in agents : deg = agent.degree() if deg > 0 : vertices = [] for j in range(deg) : childPos = agent.children[j].pos() # set vertex distance by degree vertices.append(childPos.dif(agent.pos()).len(deg).add(agent.pos())) if deg == 3 : nml = vertices[0].nml(vertices[1], vertices[2]) center = agent.pos().cp() center.add(nml.len(vertices[0].dist(center)/2)) vertices2 = [] for j in range(3) : vertices2.append(vertices[j]) vertices2.append(center) vertices = vertices2 elif deg == 2 : dif = vertices[1].dif(vertices[0]) t = dif.cross(IG.zaxis) if t.len() == 0 : t = dif.cross(IG.yaxis) t.len(dif.len()/4) vertices2 = [] for j in range(2) : vertices2.append(vertices[j]) vertices2.append(agent.pos().cp().add(t)) t.rot(dif, PI*2/3) vertices2.append(agent.pos().cp().add(t)) t.rot(dif, PI*2/3) vertices2.append(agent.pos().cp().add(t)) vertices = vertices2 elif deg == 1 : dif = vertices[0].dif(agent.pos()) t = dif.cross(IG.zaxis) if t.len() == 0 : t = dif.cross(IG.yaxis) t.len(dif.len()/2) vertices2 = [] vertices2.append(vertices[0]) vertices2.append(agent.pos().dup().add(t)) t.rot(dif, PI*2/3) vertices2.append(agent.pos().dup().add(t)) t.rot(dif, PI*2/3) vertices2.append(agent.pos().dup().add(t)) vertices = vertices2 IMesh.polyhedron(vertices) agent.hide() # hide points # adding geometry on links for line in lines : IG.meshPolygonStick(line.cp(0), line.cp(1), 1, 3) class NodeAgent(IParticle) : linkLength = 10 linkThreshold = 25 tension = 10 repulsion = 200 growthTime = 0 # no growth def __init__(self, p) : IParticle.__init__(self,p) self.fric(0.1) self.children = [] #to keep track of connected nodes self.links = [] #to keep track of links # adding a method to make connection def connect(self, node) : if node not in self.children : #only when not connected yet self.children.append(node) node.children.append(self) link = ITensionLine(self, node, NodeAgent.tension) self.links.append(link) node.links.append(link) self.clr(self.degree()*0.1, 0, 0) node.clr(node.degree()*0.1, 0, 0) # adding a method to return degree (number of connection) def degree(self) : return len(self.children)
The following code is very similar to the previous one but this one add a polygon mesh polyhedron whose vertices are at the location of other connected nodes.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.open("network3.3dm") IG.duration(0); # no force simulation #put all lines, all points, and points in "fix" layer. buildNetwork(IG.curves(),IG.points(),IG.layer("fix").points()) def buildNetwork(lines, points, fixPoints) : pnum = len(points) lnum = len(lines) fnum = len(fixPoints) agents = [] # creating node agents by points for i in range(pnum) : agents.append(NodeAgent(points[i].pos())) # linking agents by lines for i in range(lnum) : pt1 = lines[i].cp(0) pt2 = lines[i].cp(1) tolerance = 0.1 found = False for j in range(pnum) : if found : break if agents[j].pos().dist(pt1) < tolerance : for k in range(pnum) : if found : break if agents[k].pos().dist(pt2) < tolerance : agents[j].connect(agents[k]) found = True lines[i].del() # fixing agents for i in range(pnum) : for j in range(fnum) : if points[i] == fixPoints[j] : agents[i].fix().clr(0,1.0,1.0) points[i].del() # adding geometry at nodes for agent in agents : deg = agent.degree() if deg > 0 : vertices = [] for j in range(deg) : childPos = agent.children[j].pos() # set vertex distance by degree vertices.append(childPos) if deg == 3 : nml = vertices[0].nml(vertices[1], vertices[2]) center = agent.pos().cp() center.add(nml.len(vertices[0].dist(center)/2)) vertices2 = [] for j in range(3) : vertices2.append(vertices[j]) vertices2.append(center) vertices = vertices2 elif deg == 2 : dif = vertices[1].dif(vertices[0]) t = dif.cross(IG.zaxis) if t.len() == 0 : t = dif.cross(IG.yaxis) t.len(dif.len()/4) vertices2 = [] for j in range(2) : vertices2.append(vertices[j]) vertices2.append(agent.pos().cp().add(t)) t.rot(dif, PI*2/3) vertices2.append(agent.pos().cp().add(t)) t.rot(dif, PI*2/3) vertices2.append(agent.pos().cp().add(t)) vertices = vertices2 elif deg == 1 : dif = vertices[0].dif(agent.pos()) t = dif.cross(IG.zaxis) if t.len() == 0 : t = dif.cross(IG.yaxis) t.len(dif.len()/2) vertices2 = [] vertices2.append(vertices[0]) vertices2.append(agent.pos().dup().add(t)) t.rot(dif, PI*2/3) vertices2.append(agent.pos().dup().add(t)) t.rot(dif, PI*2/3) vertices2.append(agent.pos().dup().add(t)) vertices = vertices2 IMesh.polyhedron(vertices) agent.hide() # hide points # adding geometry on links for line in lines : IG.meshPolygonStick(line.cp(0), line.cp(1), 1, 3) class NodeAgent(IParticle) : linkLength = 10 linkThreshold = 25 tension = 10 repulsion = 200 growthTime = 0 # no growth def __init__(self, p) : IParticle.__init__(self,p) self.fric(0.1) self.children = [] #to keep track of connected nodes self.links = [] #to keep track of links # adding a method to make connection def connect(self, node) : if node not in self.children : #only when not connected yet self.children.append(node) node.children.append(self) link = ITensionLine(self, node, NodeAgent.tension) self.links.append(link) node.links.append(link) self.clr(self.degree()*0.1, 0, 0) node.clr(node.degree()*0.1, 0, 0) # adding a method to return degree (number of connection) def degree(self) : return len(self.children)
The following code uses an input Rhino file. The rhino file contains one polygon mesh and this mesh is copied to each node and scaled by a factor porportional to a number of connection, and rotated towards one of link directions. Triangular truss like geometries are modeled programatically out of input lines but the truss width is calculated also by a number of connection of end point nodes.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.open("network3.3dm") IG.open("mesh_part1.3dm") IG.duration(0); # no force simulation #put all lines, all points, and points in "fix" layer. buildNetwork(IG.curves(),IG.points(),IG.layer("fix").points(),IG.mesh(0)) def buildNetwork(lines, points, fixPoints, nodeGeometry) : pnum = len(points) lnum = len(lines) fnum = len(fixPoints) agents = [] # creating node agents by points for i in range(pnum) : agents.append(NodeAgent(points[i].pos())) # linking agents by lines linkedNodePairs = [] for i in range(lnum) : pt1 = lines[i].cp(0) pt2 = lines[i].cp(1) tolerance = 0.1 found = False for j in range(pnum) : if found : break if agents[j].pos().dist(pt1) < tolerance : for k in range(pnum) : if found : break if agents[k].pos().dist(pt2) < tolerance : agents[j].connect(agents[k]) linkedNodePairs.append([agents[j], agents[k]]) found = True lines[i].del() # fixing agents for i in range(pnum) : for j in range(fnum) : if points[i] == fixPoints[j] : agents[i].fix().clr(0,1.0,1.0) points[i].del() # adding geometry at nodes for agent in agents : deg = agent.degree() if deg > 0 : nodeMesh = nodeGeometry.cp() nodeMesh.add(agent.pos().dif(nodeGeometry.center())) nodeMesh.scale(agent.pos(), agent.degree()*1.5) angle = agent.children[0].pos().dif(agent.pos()).angle(IG.xaxis, IG.zaxis) nodeMesh.rot(agent.pos(), IG.zaxis, angle) nodeMesh.clr(agent.degree()*0.03) agent.hide() # hide points nodeGeometry.del() # adding geometry on links for nodePair in linkedNodePairs : pt1 = nodePair[0].pos() pt2 = nodePair[1].pos() deg1 = nodePair[0].degree() deg2 = nodePair[1].degree() dir = pt2.dif(pt1) sideDir = dir.cross(IG.zaxis) if dir.isParallel(IG.zaxis) : sideDir = IVec(1,0,0) trussLineNum = 3 radius1 = deg1*0.8 radius2 = deg2*0.8 trussLines = [] for j in range(trussLineNum) : linePt1 = pt1.cp().add(sideDir.cp().rot(dir,2*PI*j/trussLineNum).len(radius1)) linePt2 = pt2.cp().add(sideDir.cp().rot(dir,2*PI*j/trussLineNum).len(radius2)) trussLines.append(ICurve(linePt1, linePt2)) trussSpacing = 10 length = dir.len() trussSegNum = (int)(length/trussSpacing) if trussSegNum == 0 : trussSegNum = 1 trussRadius = 0.3 for j in range(trussLineNum) : for k in range(trussSegNum+1) : trussPt1 = trussLines[j].pt( 1.0*k/trussSegNum ) trussPt2 = trussLines[(j+1)%trussLineNum].pt( 1.0*k/trussSegNum ) IG.meshSquareStick(trussPt1, trussPt2, trussRadius) if k < trussSegNum : trussPt3 = trussLines[j].pt( 1.0*(k+1)/trussSegNum ) IG.meshSquareStick(trussPt3, trussPt2, trussRadius) IG.meshSquareStick(trussLines[j].pt(0), trussLines[j].pt(1), trussRadius) class NodeAgent(IParticle) : linkLength = 10 linkThreshold = 25 tension = 10 repulsion = 200 growthTime = 0 # no growth def __init__(self, p) : IParticle.__init__(self,p) self.fric(0.1) self.children = [] #to keep track of connected nodes self.links = [] #to keep track of links # adding a method to make connection def connect(self, node) : if node not in self.children : #only when not connected yet self.children.append(node) node.children.append(self) link = ITensionLine(self, node, NodeAgent.tension) self.links.append(link) node.links.append(link) self.clr(self.degree()*0.1, 0, 0) node.clr(node.degree()*0.1, 0, 0) # adding a method to return degree (number of connection) def degree(self) : return len(self.children)
The next code reconstruct a network and convert network link lines as curve tangent fields. Boid agents are created at nodes which have many connections and at fixed nodes, and they fly around the network influenced by the force field made by the network.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.open("network3.3dm") IG.bg(0) #put all lines, all points, and points in "fix" layer. buildNetwork(IG.curves(),IG.points(),IG.layer("fix").points()) def buildNetwork(lines, points, fixPoints) : pnum = len(points) lnum = len(lines) fnum = len(fixPoints) agents = [] # creating node agents by points for i in range(pnum) : agents.append(NodeAgent(points[i].pos())) # linking agents by lines for i in range(lnum) : pt1 = lines[i].cp(0) pt2 = lines[i].cp(1) tolerance = 0.1 found = False for j in range(pnum) : if found : break if agents[j].pos().dist(pt1) < tolerance : for k in range(pnum) : if found : break if agents[k].pos().dist(pt2) < tolerance : agents[j].connect(agents[k]) found = True lines[i].del() # fixing agents for i in range(pnum) : for j in range(fnum) : if points[i] == fixPoints[j] : agents[i].fix().clr(0,1.0,1.0) points[i].del() # making force field tangentField = ICompoundField() attractorField = ICompoundField() for line in lines : tangentField.add(ICurveTangentField(line).intensity(200).gauss(50).bidirectional(True)) attractorField.add(ICurveAttractorField(line).intensity(100).gauss(100)) for agent in agents : if agent.degree() > 6 : particleNum = agent.degree()*5 for j in range(particleNum) : particlePos = agent.pos().cp().add(IRand.dir(2.0)) particleVel = IRand.dir(30) particle = IParticleTrajectory(particlePos, particleVel) particle.fric(0.1).clr(1.0,0.5) elif agent.fixed() : particleNum = agent.degree()*5 for j in range(particleNum) : particlePos = agent.pos().cp().add(IRand.dir(2.0)) particleVel = IRand.dir(IG.zaxis, 50, PI/4) particle = IParticleTrajectory(particlePos, particleVel) particle.fric(0.1).clr(1.0,0.5) agent.del() # not to move class NodeAgent(IParticle) : linkLength = 10 linkThreshold = 25 tension = 10 repulsion = 200 growthTime = 0 # no growth def __init__(self, p) : IParticle.__init__(self,p) self.fric(0.1) self.children = [] #to keep track of connected nodes self.links = [] #to keep track of links # adding a method to make connection def connect(self, node) : if node not in self.children : #only when not connected yet self.children.append(node) node.children.append(self) link = ITensionLine(self, node, NodeAgent.tension) self.links.append(link) node.links.append(link) self.clr(self.degree()*0.1, 0, 0) node.clr(node.degree()*0.1, 0, 0) # adding a method to return degree (number of connection) def degree(self) : return len(self.children)
The following code let swarm agents build more geometry by drawing lines to close neighbors.
add_library('igeo') def setup() : size(480, 360, IG.GL) IG.open("network3.3dm") #put all lines, all points, and points in "fix" layer. buildNetwork(IG.curves(),IG.points(),IG.layer("fix").points()) def buildNetwork(lines, points, fixPoints) : pnum = len(points) lnum = len(lines) fnum = len(fixPoints) agents = [] # creating node agents by points for i in range(pnum) : agents.append(NodeAgent(points[i].pos())) # linking agents by lines for i in range(lnum) : pt1 = lines[i].cp(0) pt2 = lines[i].cp(1) tolerance = 0.1 found = False for j in range(pnum) : if found : break if agents[j].pos().dist(pt1) < tolerance : for k in range(pnum) : if found : break if agents[k].pos().dist(pt2) < tolerance : agents[j].connect(agents[k]) found = True lines[i].del() # fixing agents for i in range(pnum) : for j in range(fnum) : if points[i] == fixPoints[j] : agents[i].fix().clr(0,1.0,1.0) points[i].del() # making force field tangentField = ICompoundField() attractorField = ICompoundField() for line in lines : tangentField.add(ICurveTangentField(line).intensity(200).gauss(50).bidirectional(True)) attractorField.add(ICurveAttractorField(line).intensity(100).gauss(100)) for agent in agents : if agent.degree() > 6 : particleNum = agent.degree()*5 for j in range(particleNum) : particlePos = agent.pos().cp().add(IRand.dir(2.0)) particleVel = IRand.dir(30) particle = MyParticle(particlePos, particleVel) particle.fric(0.1).clr(1.0,0.5) elif agent.fixed() : particleNum = agent.degree()*5 for j in range(particleNum) : particlePos = agent.pos().cp().add(IRand.dir(2.0)) particleVel = IRand.dir(IG.zaxis, 50, PI/4) particle = MyParticle(particlePos, particleVel) particle.fric(0.1).clr(1.0,0.5) agent.del() # not to move class MyParticle(IParticle) : timeInterval = 2 def __init__(self, pos, vel) : IParticle.__init__(self,pos,vel) self.prevPos = pos.cp() self.hide() # hide point def interact(self, agents) : for agent in agents : if isinstance(agent, MyParticle) : if agent is not self : if self.time()%MyParticle.timeInterval==0 and self.time() > 10 : if agent.pos().dist(self.pos()) < 3 and agent.pos().dist(self.pos()) > 1 : ICurve(agent.pos().cp(), self.pos().cp()).clr(0.2,0.7,0) def update(self) : if self.time()%MyParticle.timeInterval==0 and self.time() > 0 : ICurve(self.prevPos, self.pos().cp()).clr(0,0.5,0) self.prevPos = self.pos().cp() class NodeAgent(IParticle) : linkLength = 10 linkThreshold = 25 tension = 10 repulsion = 200 growthTime = 0 # no growth def __init__(self, p) : IParticle.__init__(self,p) self.fric(0.1) self.children = [] #to keep track of connected nodes self.links = [] #to keep track of links # adding a method to make connection def connect(self, node) : if node not in self.children : #only when not connected yet self.children.append(node) node.children.append(self) link = ITensionLine(self, node, NodeAgent.tension) self.links.append(link) node.links.append(link) self.clr(self.degree()*0.1, 0, 0) node.clr(node.degree()*0.1, 0, 0) # adding a method to return degree (number of connection) def degree(self) : return len(self.children)
You can put mesh pipes sticks but it would be heavy process to run at the same time and in this case, you'd separate the process by saving the lines once and run another script to put mesh geometry.
You can also combine other geometries produced by a network in a diffent way together.