Python Tutorials | (back to the list of tutorials) |
Reproduction Based Agent and BranchingWe 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)
Connecting Existing Adjacent AgentsIn 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)
Network Layout by Repulsion Force![]()
![]()
![]()
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)
Preferential AttachmentThe 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)
Reconstructing Network from FileTo 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)
Adding Geometry on Nodes and LinksIf 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)
Swarm and NetworkThe 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.
HOME
FOR PROCESSING
DOWNLOAD
DOCUMENTS
TUTORIALS (Java /
Python)
GALLERY
SOURCE CODE(GitHub)
PRIVACY POLICY
ABOUT/CONTACT