Package springbots :: Module springbot
[hide private]

Source Code for Module springbots.springbot

  1  """ 
  2  This modules implements the springbot, a creature builded with 
  3  spring and nodes. 
  4  """ 
  5   
  6  import sys 
  7  from warnings import warn 
  8   
  9  # Rotinas para manuseamento de XML 
 10  import xml.dom.minidom 
 11  from xml.parsers.expat import ExpatError 
 12  import exceptions 
 13   
 14  # Importa as partes integrantes dos springbots 
 15  from gear import * 
 16   
 17  from math import sqrt, pi, fabs 
 18   
 19  try: 
 20      import pygame 
 21      HAS_PYGAME = True 
 22  except ImportError: 
 23      HAS_PYGAME = False 
 24   
 25  # 
 26  # Springbot class 
 27  # 
28 -class Springbot(object):
29 """ 30 Springbot: Creature builded with nodes, which are masses, connected by 31 springs which act like links between nodes pulling them together. 32 Springs may also behave in a cicle muscle pulling and pushing the nodes it 33 connects giving the creature some movement. 34 """ 35
36 - def __init__(self, parent=None, name="Unnamed", startangle=0):
37 """ 38 Creates a brand new creature or a descendent from a parent 39 """ 40 41 self.nodes = [] 42 self.springs = [] 43 self._node_count_id = 0 44 45 if not parent: 46 self._info = {"name": name, "angle": startangle} 47 else: 48 # Adiciona nodes 49 for node in parent.nodes: 50 newnode = Node((node.pos.x, node.pos.y)) 51 newnode.id = node.id 52 self.add(newnode) 53 54 # Adiciona springs 55 for spring in parent.springs: 56 # Determina referencia do spring 57 for node in self.nodes: 58 if node.id == spring.a.id: 59 A = node 60 for node in self.nodes: 61 if node.id == spring.b.id: 62 B = node 63 self.add(Spring(A, B, spring.amplitude, spring.offset, spring.normal)) 64 65 # copia outros atributos 66 self._node_count_id = parent._node_count_id 67 self._info = dict(parent._info)
68
69 - def __getitem__(self, key):
70 """ 71 Gets an info inten like a dictionary 72 """ 73 return self._info[key] if key in self._info else ''
74
75 - def __setitem__(self, key, value):
76 """ 77 Gets an info inten like a dictionary 78 """ 79 self._info[key] = value 80 return value
81
82 - def __iter__(self):
83 """ 84 Iterate between _info values 85 """ 86 return self._info.__iter__()
87
88 - def refresh(self, atr=AIR_RESISTANCE, grav=GRAVITY, elast=ELASTICITY, visc=0, moving=True):
89 """ 90 Refreshes creature's state 91 """ 92 for node in self.nodes: 93 node.refresh(atr, grav, elast) 94 95 for spring in self.springs: 96 spring.refresh(elast, self['angle'] if moving else None, visc) 97 98 if moving: 99 self['angle'] += ANGLE_STEP
100
101 - def getNode(self, id):
102 """ 103 Get node from id, None if id does not exists 104 """ 105 for node in self.nodes: 106 if node.id == id: 107 return node 108 else: 109 return None
110
111 - def loadXML(self, file=sys.stdin):
112 """ 113 Reads a xml into this springbot 114 """ 115 self.__init__(load_xml(file, limit=1)[0]) 116 return self
117
118 - def storeXML(self, file=sys.stdout):
119 """ 120 Store this springbot into a xml file 121 """ 122 store_xml([self], file)
123
124 - def colide(self, other, radius=RADIUS):
125 """ 126 Colides this springbot to another 127 """ 128 for node in self.nodes: 129 for node2 in other.nodes: 130 node.colide(node2, radius)
131
132 - def colideWall(self, limit, side, atr_normal=0.6, atr_surface=0.5, min_vel=0.99, radius=RADIUS):
133 """ 134 Colides this springbot with a straigt wall 135 """ 136 colision_strenght = 0 137 for node in self.nodes: 138 colision_strenght += node.colideWall(limit, side, atr_normal, atr_surface, min_vel, radius) 139 140 return colision_strenght/len(self.nodes) if len(self.nodes) > 0 else 0
141
142 - def __len__(self):
143 """ 144 Lenght(number of nodes) 145 """ 146 return len(self.nodes)
147
148 - def add(self, objeto):
149 """ 150 Adds an object: node or spring 151 """ 152 if objeto.__class__ == Node: 153 objeto.parent = self 154 155 if not hasattr(objeto, "id"): 156 self._node_count_id += 1 157 objeto.id = self._node_count_id 158 159 self.nodes.append(objeto) 160 161 elif objeto.__class__ == Spring: 162 163 # Verifica se ja nao existe uma spring entre esses nodes 164 for spring in self.springs: 165 if (spring.a is objeto.a or spring.b is objeto.a) and (spring.a is objeto.b or spring.b is objeto.b): 166 break 167 else: 168 objeto.parent = self 169 self.springs.append(objeto)
170
171 - def remove(self, objeto):
172 """ 173 Remove an object: node or spring 174 """ 175 if objeto.__class__ == Node: 176 # Verifica quais springs referenciam este objeto, e as deleta tambem 177 removelist = set() 178 for spring in self.springs: 179 if spring.a is objeto or spring.b is objeto: 180 removelist.add(spring) 181 for spring in removelist: 182 self.springs.remove(spring) 183 self.nodes.remove(objeto) 184 185 elif objeto.__class__ == Spring: 186 self.springs.remove(objeto)
187
188 - def massCenter(self):
189 """ 190 Calculates the center of mass 191 """ 192 cx, cy = 0, 0 193 total = 0 194 for node in self.nodes: 195 total += 1 196 cx += node.pos.x 197 cy += node.pos.y 198 199 return cx / total, cy / total
200
201 - def boundingBox(self, radius=RADIUS):
202 """ 203 Calculates its bounding box limits 204 """ 205 if len(self.nodes) == 0: 206 return 0, 0, 0, 0 207 208 min_x, max_x = self.nodes[0].pos.x, self.nodes[0].pos.x 209 min_y, max_y = self.nodes[0].pos.y, self.nodes[0].pos.y 210 211 for node in self.nodes: 212 min_x = min(min_x, node.pos.x) 213 min_y = min(min_y, node.pos.y) 214 max_x = max(max_x, node.pos.x) 215 max_y = max(max_y, node.pos.y) 216 217 return min_x-radius, min_y-radius, max_x+radius, max_y+radius
218
219 - def unconnected(self):
220 """ 221 Gets all unconnected nodes 222 """ 223 # Testa caso trivial 224 if len(self.nodes) == 0: 225 return [] 226 227 allsprings = self.springs[:] 228 atualsprings = [] 229 230 allnodes = self.nodes[1:] 231 atualnodes = [self.nodes[0]] # Inicia atualnodes com um elemento 232 233 while len(atualnodes) > 0: 234 atual = atualnodes.pop() 235 236 # Seleciona todas springs que ligam ao node 237 for spring in allsprings: 238 if spring.a is atual or spring.b is atual: 239 atualsprings.append(spring) 240 241 # Remove do allsprings todas as springs que estao no atual e 242 # adiciona ao atualnodes todos os nodes ligados por essas springs 243 for spring in atualsprings: 244 allsprings.remove(spring) 245 if spring.a in allnodes: 246 allnodes.remove(spring.a) 247 atualnodes.append(spring.a) 248 elif spring.b in allnodes: 249 allnodes.remove(spring.b) 250 atualnodes.append(spring.b) 251 252 # Zera atualsprings 253 atualsprings = [] 254 255 # Se resta algum node em allnode, entao o springbot eh desconexo 256 return allnodes
257
258 - def removeUnconnected(self):
259 """ 260 Remove all nodes and springs unconnected 261 """ 262 uncon_nodes = self.unconnected() 263 264 # Remove os nodos nao conectados 265 for node in uncon_nodes: 266 self.nodes.remove(node) 267 268 # Remove todas as springs que pertencem a nodos nao conectados 269 uncon_springs = [] 270 for spring in self.springs: 271 if spring.a in uncon_nodes or spring.b in uncon_nodes: 272 uncon_springs.append(spring) 273 274 for spring in uncon_springs: 275 self.springs.remove(spring)
276
277 - def __repr__(self):
278 return "<Springbot %s: %d nodes, %d springs>" % (self['name'], len(self.nodes), len(self.springs))
279
280 - def centerGround(self, height):
281 """ 282 Center springbot width and touch ground 283 """ 284 # Selects springbot's bouding box 285 x1, y1, x2, y2 = self.boundingBox() 286 287 # Calculates horizontal center offset 288 cx = - ((x2+x1)/2) 289 290 # Moves springbot to touch the ground under it 291 # and moves sprinng bot to width center 292 for node in self.nodes: 293 node.pos.y -= (y2 - height) + RADIUS 294 node.pos.x += cx 295 296 return self
297 298
299 - def draw(self, screen, ticks=None, track_x=False, track_y=False, 300 backgroundcolor=(20,10,0), showText=True, extrainfo=None):
301 """ 302 Draws Springbot using pygame engine. 303 Use this function to show the springbot 304 """ 305 if not HAS_PYGAME: 306 raise exceptions.NotImplementedError("No pygame module found") 307 308 width, height = screen.get_size() 309 310 311 # Gets the position center 312 x1, y1, x2, y2 = self.boundingBox() 313 314 zm = min(min(width/((x2-x1)+50.0), height/((y2-y1)+50.0)), 1.0) 315 316 if track_x: 317 cxp = (x2+x1)/2 318 cx = cxp*zm - (width/2) 319 else: 320 cxp = cx = -width/2 321 322 if track_y: 323 cyp = (y2+y1)/2 324 cy = cyp*zm - (height/2) 325 else: 326 cyp = cy = 0 327 328 329 if showText: 330 # Create Font 331 font = pygame.font.Font(None, 20) 332 333 # limpa tela 334 screen.fill((0,0,0)) 335 336 siz_x, siz_y = width/10, height/10 337 for x in xrange(0,width+100,siz_x): 338 for y in xrange(0,height+100,siz_y): 339 pygame.draw.rect(screen, backgroundcolor, 340 (((x - cxp) % (width+siz_x) - siz_x, (y - cyp) % (height+siz_y) - siz_y), 341 (siz_x-10,siz_y-10))) 342 343 # Desenha springs 344 for spring in self.springs: 345 length = (spring.a.pos - spring.b.pos).length() 346 fator = (length - spring.normal)/length if length > 0 else 0 347 348 if fator <= 0: 349 color = (255, 255-min(-fator * 255, 255), 255-min(-fator * 255, 255)) 350 elif fator > 0: 351 color = (255-min(fator * 255, 255), 255, 255-min(fator * 255, 255)) 352 353 pygame.draw.line(screen, color, (spring.a.pos.x*zm - cx, spring.a.pos.y*zm - cy), 354 (spring.b.pos.x*zm - cx, spring.b.pos.y*zm - cy), int(3*zm)) 355 356 # Desenha nodes 357 velx, vely = 0, 0 358 for node in self.nodes: 359 velx += node.vel.x 360 vely += node.vel.y 361 362 pygame.draw.circle(screen, (0,0,0), (int(node.pos.x*zm - cx), int(node.pos.y*zm - cy)), int(RADIUS*zm), 0) 363 pygame.draw.circle(screen, (10,255,255), (int(node.pos.x*zm - cx), int(node.pos.y*zm - cy)), 364 int(RADIUS*zm), int(2*zm)) 365 366 velx /= len(self.nodes) 367 vely /= len(self.nodes) 368 369 if showText: 370 # Render springbot's name and info 371 nametext = font.render("%s%s" % (self['name'], ": " + extrainfo if extrainfo else "."), False, (255,255,255)) 372 speedtext = font.render("speed: %.2f m/c" % (sqrt(velx**2 + vely**2)), False, (255,255,255)) 373 374 screen.blit(nametext, (5,5)) 375 screen.blit(speedtext, (5,20)) 376 377 if ticks: 378 tickstext = font.render("clock: %d c" % (ticks), False, (255,255,255)) 379 screen.blit(tickstext, (5,35))
380 381 382 # # 383 # ########################################################################### # 384 # # 385
386 -def store_xml(springbots, outfile=sys.stdout):
387 """ 388 Writes a set os springbots into a xml 389 """ 390 closeoutfile = (type(outfile) is str) 391 if closeoutfile: 392 outfile = open(outfile, 'w') 393 394 outfile.write('<?xml version="1.0" encoding="UTF-8"?>\n') 395 outfile.write('<!DOCTYPE springbots SYSTEM "http://springbots.sourceforge.net/springbots.dtd">\n') 396 outfile.write('<springbots>\n') 397 398 for n, springbot in enumerate(springbots): 399 x1, y1, x2, y2 = springbot.boundingBox() 400 cx, cy = (x1+x2)/2, (y1+y1)/2 401 402 outfile.write('\t<springbot ') 403 404 for key, value in springbot._info.iteritems(): 405 outfile.write('%s="%s" ' % (key, str(value))) 406 407 outfile.write('>\n') 408 409 for node in springbot.nodes: 410 outfile.write('\t\t<node pos="%d,%d" id="S%dN%d" />\n' % (node.pos.x-cx, node.pos.y-cy, n, node.id)) 411 412 for spring in springbot.springs: 413 outfile.write('\t\t<spring from="S%dN%d" to="S%dN%d" ' % (n, spring.a.id, n, spring.b.id)) 414 if round(spring.amplitude, 2) != 0: 415 outfile.write('offset="%.3f" amplitude="%.3f" ' % (spring.offset, spring.amplitude)) 416 if fabs(spring.normal - sqrt(sum((spring.a.pos - spring.b.pos)**2))) > 1: 417 outfile.write('normal="%.3f" ' % (spring.normal)) 418 outfile.write('/>\n') 419 420 outfile.write("\t</springbot>\n") 421 422 outfile.write("</springbots>\n") 423 424 if closeoutfile: 425 outfile.close() 426 else: 427 outfile.flush()
428 429 # ############################################################################ # 430
431 -def isfloat(s):
432 """ 433 Tests if a string is representing a float 434 """ 435 if s.isdigit() or s == '.': return False 436 spl = s.split('.') 437 if len(spl) > 2: return False 438 for p in spl: 439 if p and not p.isdigit(): return False 440 return True
441 442 # ############################################################################ # 443
444 -def load_xml(arq=sys.stdin, limit=None):
445 """ 446 Reads an xml into a set of springbots 447 """ 448 449 try: 450 doc = xml.dom.minidom.parse(arq) 451 except ExpatError: 452 warn("springbot.loadXML: XML parse error at file %s." % 453 (arq.name if type(arq) == file else str(arq))) 454 return [] 455 456 # Lista de springbots 457 springbots = [] 458 459 for cnode in doc.childNodes: 460 if cnode.nodeType is xml.dom.minidom.Node.ELEMENT_NODE: 461 springbotsNode = cnode 462 break 463 else: 464 warn("springbot.load_xml: There is no <springbots> XML element in file %s." % 465 (arq.name if type(arq) == file else str(arq))) 466 return [] 467 468 for springbotNode in springbotsNode.childNodes: 469 470 if str(springbotNode.nodeName) == "springbot": 471 # Cria novo springbot 472 newspringbot = Springbot() 473 474 for key, value in springbotNode.attributes.items(): 475 newspringbot[key] = float(value) if isfloat(value) else \ 476 (int(value) if value.isdigit() else value) 477 478 # Percorre estrutura do arquivo e cria nodes e springs 479 for cnode in springbotNode.childNodes: 480 if str(cnode.nodeName) == "node": 481 newnode = Node(pos=tuple((int(x) for x in str(cnode.attributes["pos"].value).split(",")))) 482 newnode.id = int(str(cnode.attributes["id"].value).split('N')[-1]) 483 newspringbot._node_count_id = max(newspringbot._node_count_id, newnode.id) 484 newspringbot.add(newnode) 485 486 elif str(cnode.nodeName) == "spring": 487 id_a = int(str(cnode.attributes["from"].value).split('N')[-1]) 488 id_b = int(str(cnode.attributes["to"].value).split('N')[-1]) 489 490 # Procura A 491 for node in newspringbot.nodes: 492 if node.id == id_a: 493 a = node 494 break 495 else: 496 raise exceptions.IndexError("node id %d(from) not found\n" % (id_a)) 497 498 # Procura id B 499 for node in newspringbot.nodes: 500 if node.id == id_b: 501 b = node 502 break 503 else: 504 raise exceptions.IndexError("node id %d(to) not found\n" % (id_b)) 505 506 507 # Cria spring 508 offs = float(str(cnode.attributes["offset"].value)) if cnode.hasAttribute("offset") else pi 509 ampl = float(str(cnode.attributes["amplitude"].value)) if cnode.hasAttribute("amplitude") else 0.0 510 norm = float(str(cnode.attributes["normal"].value)) if cnode.hasAttribute("normal") else None 511 newspringbot.add(Spring(a, b, offset=offs, amplitude=ampl, normal=norm)) 512 513 # Adiciona novo springbot a lista 514 springbots.append(newspringbot) 515 516 # Verifica se atingiu limite 517 if limit is not None and len(springbots) >= limit: 518 break 519 520 return springbots
521