def __init__(self, screensize=(800, 600), fullscreen=False, caption="Topology Viewer", particleTypes=None, initialTopology=None, laws=None, simCyclesPerRedraw=1, border=0, extraDrawing=None, showGrid=True, transparency=None, position=None): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super(TopologyViewer3D, self).__init__() glutInit(sys.argv) tracker = _cat.coordinatingassistanttracker.getcat() self.display = OpenGLDisplay(width=screensize[0], height=screensize[1], fullscreen=fullscreen, title=caption) self.display.activate() OpenGLDisplay.setDisplayService(self.display, tracker) self.link((self, "display_signal"), (self.display, "notify")) self.link((self.display, "signal"), (self, "control")) self.border = border if particleTypes == None: self.particleTypes = { "-": CuboidParticle3D, "cuboid": CuboidParticle3D, "sphere": SphereParticle3D, "teapot": TeapotParticle3D } else: self.particleTypes = particleTypes self.hitParticles = [] self.multiSelectMode = False self.selectedParticles = [] self.grabbed = False self.rotationMode = False if laws == None: self.laws = Kamaelia.Support.Particles.SimpleLaws(bondLength=2) else: self.laws = laws self.physics = ParticleSystemX(self.laws, [], 0) self.biggestRadius = 0 # Do interaction self.simCyclesPerRedraw = simCyclesPerRedraw self.lastIdleTime = time.time() # Tell if new node is added; if true, new id needs adding to OpenGLDisplay list self.isNewNode = False # For hierarchy structure self.maxLevel = 0 self.currentLevel = 0 self.currentParentParticleID = '' self.viewerOldPos = Vector() self.levelViewerPos = {} self.currentDisplayedPhysics = ParticleSystemX(self.laws, [], 0) # For double click self.lastClickPos = (0, 0) self.lastClickTime = time.time() self.dClickRes = 0.3
def __init__(self, screensize = (800,600), fullscreen = False, caption = "Topology Viewer", particleTypes = None, initialTopology = None, laws = None, simCyclesPerRedraw = 1, border = 0, extraDrawing = None, showGrid = True, transparency = None, position = None): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super(TopologyViewer3D, self).__init__() glutInit(sys.argv) tracker = _cat.coordinatingassistanttracker.getcat() self.display = OpenGLDisplay(width=screensize[0], height=screensize[1],fullscreen=fullscreen, title=caption) self.display.activate() OpenGLDisplay.setDisplayService(self.display, tracker) self.link((self,"display_signal"), (self.display,"notify")) self.link((self.display,"signal"), (self,"control")) self.border = border if particleTypes == None: self.particleTypes = {"-":CuboidParticle3D, "cuboid":CuboidParticle3D, "sphere":SphereParticle3D, "teapot":TeapotParticle3D} else: self.particleTypes = particleTypes self.hitParticles = [] self.multiSelectMode = False self.selectedParticles = [] self.grabbed = False self.rotationMode = False if laws==None: self.laws = Kamaelia.Support.Particles.SimpleLaws(bondLength=2) else: self.laws = laws self.physics = ParticleSystemX(self.laws, [], 0) self.biggestRadius = 0 # Do interaction self.simCyclesPerRedraw = simCyclesPerRedraw self.lastIdleTime = time.time() # Tell if new node is added; if true, new id needs adding to OpenGLDisplay list self.isNewNode = False # For hierarchy structure self.maxLevel = 0 self.currentLevel = 0 self.currentParentParticleID = '' self.viewerOldPos = Vector() self.levelViewerPos = {} self.currentDisplayedPhysics = ParticleSystemX(self.laws, [], 0) # For double click self.lastClickPos = (0,0) self.lastClickTime = time.time() self.dClickRes = 0.3
class TopologyViewer3D(Axon.AdaptiveCommsComponent.AdaptiveCommsComponent): """\ TopologyViewer3D(...) -> new TopologyViewer3D component. A component that takes incoming topology (change) data and displays it live using pygame OpenGL. A simple physics model assists with visual layout. Particle types, appearance and physics interactions can be customised. """ Inboxes = { "inbox": "Topology (change) data describing an Axon system", "control": "Shutdown signalling", "alphacontrol": "Alpha (transparency) of the image (value 0..255)", "callback": "for the response after a displayrequest", "events": "Place where we recieve events from the outside world", "displaycontrol": "Replies from Pygame Display service", } Outboxes = { "signal": "NOT USED", "outbox": "Notification and topology output", "display_signal": "Requests to Pygame Display service", } def __init__(self, screensize=(800, 600), fullscreen=False, caption="Topology Viewer", particleTypes=None, initialTopology=None, laws=None, simCyclesPerRedraw=1, border=0, extraDrawing=None, showGrid=True, transparency=None, position=None): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super(TopologyViewer3D, self).__init__() glutInit(sys.argv) tracker = _cat.coordinatingassistanttracker.getcat() self.display = OpenGLDisplay(width=screensize[0], height=screensize[1], fullscreen=fullscreen, title=caption) self.display.activate() OpenGLDisplay.setDisplayService(self.display, tracker) self.link((self, "display_signal"), (self.display, "notify")) self.link((self.display, "signal"), (self, "control")) self.border = border if particleTypes == None: self.particleTypes = { "-": CuboidParticle3D, "cuboid": CuboidParticle3D, "sphere": SphereParticle3D, "teapot": TeapotParticle3D } else: self.particleTypes = particleTypes self.hitParticles = [] self.multiSelectMode = False self.selectedParticles = [] self.grabbed = False self.rotationMode = False if laws == None: self.laws = Kamaelia.Support.Particles.SimpleLaws(bondLength=2) else: self.laws = laws self.physics = ParticleSystemX(self.laws, [], 0) self.biggestRadius = 0 # Do interaction self.simCyclesPerRedraw = simCyclesPerRedraw self.lastIdleTime = time.time() # Tell if new node is added; if true, new id needs adding to OpenGLDisplay list self.isNewNode = False # For hierarchy structure self.maxLevel = 0 self.currentLevel = 0 self.currentParentParticleID = '' self.viewerOldPos = Vector() self.levelViewerPos = {} self.currentDisplayedPhysics = ParticleSystemX(self.laws, [], 0) # For double click self.lastClickPos = (0, 0) self.lastClickTime = time.time() self.dClickRes = 0.3 def main(self): """\ """ # create display request for itself self.size = Vector(0, 0, 0) disprequest = { "OGL_DISPLAYREQUEST": True, "objectid": id(self), "callback": (self, "callback"), "events": (self, "events"), "size": self.size } # send display request self.send(disprequest, "display_signal") # wait for response on displayrequest and get identifier of the viewer while not self.dataReady("callback"): yield 1 self.identifier = self.recv("callback") self.addListenEvents([ pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION, pygame.KEYDOWN, pygame.KEYUP ]) pygame.key.set_repeat(100, 100) while True: # process incoming messages if self.dataReady("inbox"): message = self.recv("inbox") self.doCommand(message) #print message # wait for response on displayrequest and get identifier of the particle if self.isNewNode: while not self.dataReady("callback"): yield 1 self.physics.particles[-1].identifier = self.recv( "callback") self.isNewNode = False else: self.lastIdleTime = 0 yield 1 if self.lastIdleTime + 1.0 < time.time(): #print [particle.pos for particle in self.physics.particles] avoidedList = [] avoidedList.extend(self.hitParticles) avoidedList.extend(self.selectedParticles) #self.currentDisplayedPhysics.particleDict[ident].breakAllBonds() self.currentDisplayedPhysics.particles = [] if self.physics.particles != []: for particle in self.physics.particles: if self.currentParentParticleID == '': if ':' not in particle.ID: self.currentDisplayedPhysics.add(particle) particle.oldpos = particle.initialpos elif particle.ID.find( self.currentParentParticleID ) == 0 and particle.ID.count(':') == self.currentLevel: self.currentDisplayedPhysics.add(particle) particle.oldpos = particle.initialpos self.currentDisplayedPhysics.run(self.simCyclesPerRedraw, avoidedList=avoidedList) #print [particle.pos for particle in self.physics.particles] # Draw particles if new or updated for particle in self.currentDisplayedPhysics.particles: if particle.needRedraw: self.drawParticles(particle) #particle.needRedraw = False self.handleEvents() # Perform transformation for particle in self.currentDisplayedPhysics.particles: transform_update = particle.applyTransforms() if transform_update is not None: self.send(transform_update, "display_signal") #print transform_update #print [particle.pos for particle in self.physics.particles] self.lastIdleTime = time.time() else: yield 1 if self.dataReady("control"): msg = self.recv("control") if isinstance(msg, Axon.Ipc.shutdownMicroprocess): self.quit(msg) def quit(self, msg=Axon.Ipc.shutdownMicroprocess()): print 'Shut down...' self.send(msg, "signal") self.scheduler.stop() def draw(self): """\ Invoke draw() and save its commands to a newly generated displaylist. The displaylist name is then sent to the display service via a "DISPLAYLIST_UPDATE" request. """ pass def drawParticles(self, *particles): for particle in particles: # display list id displaylist = glGenLists(1) # draw object to its displaylist glNewList(displaylist, GL_COMPILE) particle.draw() glEndList() #print displaylist dl_update = { "DISPLAYLIST_UPDATE": True, "objectid": id(particle), "displaylist": displaylist } self.send(dl_update, "display_signal") #print particle def addListenEvents(self, events): """\ Sends listening request for pygame events to the display service. The events parameter is expected to be a list of pygame event constants. """ for event in events: self.send({ "ADDLISTENEVENT": event, "objectid": id(self) }, "display_signal") def removeListenEvents(self, events): """\ Sends stop listening request for pygame events to the display service. The events parameter is expected to be a list of pygame event constants. """ for event in events: self.send({ "REMOVELISTENEVENT": event, "objectid": id(self) }, "display_signal") def handleEvents(self): """ Handle events. """ while self.dataReady("events"): event = self.recv("events") if event.type == pygame.MOUSEBUTTONDOWN or pygame.MOUSEMOTION and self.grabbed: if not self.rotationMode: for particle in self.hitParticles: p1 = Vector(*particle.pos).copy() p1.x += 10 p2 = Vector(*particle.pos).copy() p2.y += 10 #z = Intersect.ray_Plane(Vector(0,0,0), event.direction, [Vector(*particle.pos)-self.display.viewerposition, p1, p2]) z = Intersect.ray_Plane(Vector( 0, 0, 0), event.direction, [ Vector(*particle.pos) - Vector(0, 0, self.display.viewerposition.z), p1 - Vector(0, 0, self.display.viewerposition.z), p2 - Vector(0, 0, self.display.viewerposition.z) ]) newpoint = event.direction * z if event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: # Handle double click clickPos = event.pos currentTime = time.time() elapsedTime = currentTime - self.lastClickTime if clickPos == self.lastClickPos and elapsedTime < self.dClickRes: if self.currentLevel < self.maxLevel and len( self.selectedParticles) == 1: hasChildParticles = False for particle in self.physics.particles: if particle.ID.find( self.selectedParticles[0].ID ) == 0 and particle.ID != self.selectedParticles[ 0].ID: hasChildParticles = True break if hasChildParticles: self.currentParentParticleID = self.selectedParticles[ 0].ID self.gotoDisplayLevel(1) else: print 'Warning: The particle you double-clicked has no children!' else: if self.currentLevel == self.maxLevel: print "Warning: max hierarchy level has reached!" if len(self.selectedParticles) != 1: print "Tips: To extend a node, please double-click the node you want to extend" else: if not self.rotationMode: for particle in self.currentDisplayedPhysics.particles: if particle.identifier in event.hitobjects: #particle.oldpos = particle.oldpos - self.display.viewerposition self.grabbed = True #particle.scaling = Vector(0.9,0.9,0.9) self.hitParticles.append(particle) self.selectParticle(particle) #print str(id(particle))+'hit' #print self.hitParticles # If click places other than particles in non multiSelectMode, deselect all if not self.hitParticles and not self.multiSelectMode: self.deselectAll() self.lastClickPos = clickPos self.lastClickTime = currentTime if event.button == 3: if self.currentLevel > 0: items = self.currentParentParticleID.split(':') items.pop() self.currentParentParticleID = ':'.join(items) self.gotoDisplayLevel(-1) else: print "Warning: The first hierarchy level has reached!" if event.button == 4: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles for particle in particles: posVector = Vector(*particle.pos) posVector.z -= 1 particle.pos = posVector.toTuple() if event.button == 5: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles for particle in particles: posVector = Vector(*particle.pos) posVector.z += 1 particle.pos = posVector.toTuple() if event.type == pygame.MOUSEBUTTONUP: if event.button == 1: for particle in self.hitParticles: self.grabbed = False particle.oldpoint = None #particle.scaling = Vector(1,1,1) self.hitParticles.pop( self.hitParticles.index(particle)) #print self.hitParticles if event.type == pygame.MOUSEMOTION: if not self.rotationMode and self.grabbed: for particle in self.hitParticles: try: if particle.oldpoint is not None: #print particle.pos diff = newpoint - particle.oldpoint amount = (diff.x, diff.y) particle.pos = (Vector(*particle.pos) + Vector(*amount)).toTuple() except NameError: pass # Redraw the link so that the link can move with the particle for p in particle.bondedFrom: p.needRedraw = True elif self.rotationMode: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAnglex = float(event.rel[1]) * math.pi / 180 dAngley = -float(event.rel[0]) * math.pi / 180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = ( relativePosVector.z * relativePosVector.z + relativePosVector.y * relativePosVector.y)**0.5 newAnglex = (math.atan2(relativePosVector.z, relativePosVector.y) + dAnglex) particle.pos = (posVector.x, radius * math.cos(newAnglex) + centrePoint.y, radius * math.sin(newAnglex) + centrePoint.z) posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = ( relativePosVector.z * relativePosVector.z + relativePosVector.x * relativePosVector.x)**0.5 newAngley = (math.atan2(relativePosVector.z, relativePosVector.x) + dAngley) particle.pos = (radius * math.cos(newAngley) + centrePoint.x, posVector.y, radius * math.sin(newAngley) + centrePoint.z) particle.drotation.y = float(event.rel[0]) particle.drotation.x = float(event.rel[1]) particle.drotation %= 360 try: for particle in self.hitParticles: particle.oldpoint = newpoint except NameError: pass # Keyboard events handling if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: self.quit() elif event.key == pygame.K_BACKSPACE: if self.currentLevel > 0: items = self.currentParentParticleID.split(':') items.pop() self.currentParentParticleID = ':'.join(items) self.gotoDisplayLevel(-1) else: print "Warning: The first hierarchy level has reached!" elif event.key == pygame.K_RETURN: if self.currentLevel < self.maxLevel and len( self.selectedParticles) == 1: hasChildParticles = False for particle in self.physics.particles: if particle.ID.find( self.selectedParticles[0].ID ) == 0 and particle.ID != self.selectedParticles[ 0].ID: hasChildParticles = True break if hasChildParticles: self.currentParentParticleID = self.selectedParticles[ 0].ID self.gotoDisplayLevel(1) else: print 'Warning: The particle you double-clicked has no children!' else: if self.currentLevel == self.maxLevel: print "Warning: max hierarchy level has reached!" if len(self.selectedParticles) != 1: print "Tips: To extend a node, please click to select the node (only one) you want to extend first." elif event.key == pygame.K_LSHIFT or event.key == pygame.K_RSHIFT: self.multiSelectMode = True elif event.key == pygame.K_LCTRL or event.key == pygame.K_RCTRL: self.rotationMode = True elif event.key == pygame.K_PAGEUP: self.display.viewerposition.z -= 0.5 elif event.key == pygame.K_PAGEDOWN: self.display.viewerposition.z += 0.5 elif event.key == pygame.K_w: self.display.viewerposition.y += 0.5 elif event.key == pygame.K_s: self.display.viewerposition.y -= 0.5 elif event.key == pygame.K_a: self.display.viewerposition.x -= 0.5 elif event.key == pygame.K_d: self.display.viewerposition.x += 0.5 elif event.key == pygame.K_UP: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = -20 * math.pi / 180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = ( relativePosVector.z * relativePosVector.z + relativePosVector.y * relativePosVector.y)**0.5 newAngle = (math.atan2(relativePosVector.z, relativePosVector.y) + dAngle) particle.pos = (posVector.x, radius * math.cos(newAngle) + centrePoint.y, radius * math.sin(newAngle) + centrePoint.z) particle.drotation = Vector(dAngle * 180 / math.pi, 0, 0) elif event.key == pygame.K_DOWN: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = 20 * math.pi / 180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = ( relativePosVector.z * relativePosVector.z + relativePosVector.y * relativePosVector.y)**0.5 newAngle = (math.atan2(relativePosVector.z, relativePosVector.y) + dAngle) particle.pos = (posVector.x, radius * math.cos(newAngle) + centrePoint.y, radius * math.sin(newAngle) + centrePoint.z) particle.drotation = Vector(dAngle * 180 / math.pi, 0, 0) elif event.key == pygame.K_LEFT: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = 20 * math.pi / 180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = ( relativePosVector.z * relativePosVector.z + relativePosVector.x * relativePosVector.x)**0.5 newAngle = (math.atan2(relativePosVector.z, relativePosVector.x) + dAngle) particle.pos = (radius * math.cos(newAngle) + centrePoint.x, posVector.y, radius * math.sin(newAngle) + centrePoint.z) particle.drotation = Vector(0, -dAngle * 180 / math.pi, 0) elif event.key == pygame.K_RIGHT: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = -20 * math.pi / 180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = ( relativePosVector.z * relativePosVector.z + relativePosVector.x * relativePosVector.x)**0.5 newAngle = (math.atan2(relativePosVector.z, relativePosVector.x) + dAngle) particle.pos = (radius * math.cos(newAngle) + centrePoint.x, posVector.y, radius * math.sin(newAngle) + centrePoint.z) particle.drotation = Vector(0, -dAngle * 180 / math.pi, 0) elif event.key == pygame.K_COMMA: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = 20 * math.pi / 180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = ( relativePosVector.x * relativePosVector.x + relativePosVector.y * relativePosVector.y)**0.5 newAngle = (math.atan2(relativePosVector.y, relativePosVector.x) + dAngle) particle.pos = (radius * math.cos(newAngle) + centrePoint.x, radius * math.sin(newAngle) + centrePoint.y, posVector.z) particle.drotation = Vector(0, 0, dAngle * 180 / math.pi) elif event.key == pygame.K_PERIOD: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = -20 * math.pi / 180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = ( relativePosVector.x * relativePosVector.x + relativePosVector.y * relativePosVector.y)**0.5 newAngle = (math.atan2(relativePosVector.y, relativePosVector.x) + dAngle) particle.pos = (radius * math.cos(newAngle) + centrePoint.x, radius * math.sin(newAngle) + centrePoint.y, posVector.z) particle.drotation = Vector(0, 0, dAngle * 180 / math.pi) #print self.display.viewerposition # Scroll if self.display.viewerposition changes if self.display.viewerposition.copy() != self.viewerOldPos: self.scroll() self.viewerOldPos = self.display.viewerposition.copy() # for particle in self.currentDisplayedPhysics.particles: # particle.oldpoint = None if event.type == pygame.KEYUP: if event.key == pygame.K_LSHIFT or event.key == pygame.K_RSHIFT: self.multiSelectMode = False elif event.key == pygame.K_LCTRL or event.key == pygame.K_RCTRL: self.rotationMode = False def scroll(self): # Scroll the surface by resetting gluLookAt glMatrixMode(GL_PROJECTION) glLoadIdentity() self.display.setProjection() def gotoDisplayLevel(self, dlevel): # Save current level's viewer position self.levelViewerPos[ self.currentLevel] = self.display.viewerposition.copy() # Display next level self.currentLevel += dlevel # Reset viewer position to previous try: self.display.viewerposition = self.levelViewerPos[ self.currentLevel].copy() except KeyError: self.display.viewerposition = self.levelViewerPos[ self.currentLevel] = Vector() # Remove current displayed particles for particle in self.currentDisplayedPhysics.particles: self.display.ogl_displaylists.pop(id(particle)) self.display.ogl_transforms.pop(id(particle)) self.currentDisplayedPhysics.removeByID( *self.currentDisplayedPhysics.particleDict.keys()) def doCommand(self, msg): """\ Proceses a topology command tuple: [ "ADD", "NODE", <id>, <name>, <positionSpec>, <particle type> ] [ "DEL", "NODE", <id> ] [ "ADD", "LINK", <id from>, <id to> ] [ "DEL", "LINK", <id from>, <id to> ] [ "DEL", "ALL" ] [ "GET", "ALL" ] """ #print 'doCommand' if len(msg) >= 2: cmd = msg[0].upper(), msg[1].upper() if cmd == ("ADD", "NODE") and len(msg) == 6: if msg[2] in [p.ID for p in self.physics.particles]: print "Node exists, please use a new node ID!" else: if self.particleTypes.has_key(msg[5]): #print 'ADD NODE begin' ptype = self.particleTypes[msg[5]] ident = msg[2] name = msg[3] posSpec = msg[4] pos = self._generatePos(posSpec) #print pos particle = ptype(position=pos, ID=ident, name=name) particle.originaltype = msg[5] #self.particles.append(particle) #print self.particles[0] self.addParticle(particle) self.isNewNode = True #print id(particle) #print 'ADD NODE end' elif cmd == ("DEL", "NODE") and len(msg) == 3: ident = msg[2] self.removeParticle(ident) elif cmd == ("ADD", "LINK") and len(msg) == 4: src = msg[2] dst = msg[3] self.makeBond(src, dst) elif cmd == ("DEL", "LINK") and len(msg) == 4: src = msg[2] dst = msg[3] self.breakBond(src, dst) elif cmd == ("DEL", "ALL") and len(msg) == 2: self.removeParticle(*self.physics.particleDict.keys()) self.currentLevel = 0 self.currentParentParticleID = '' elif cmd == ("GET", "ALL") and len(msg) == 2: topology = [("DEL", "ALL")] topology.extend(self.getTopology()) self.send(("TOPOLOGY", topology), "outbox") elif cmd == ("UPDATE_NAME", "NODE") and len(msg) == 4: node_id = msg[2] new_name = msg[3] self.updateParticleLabel(node_id, new_name) self.send(("UPDATE_NAME", "NODE", node_id, new_name), "outbox") elif cmd == ("GET_NAME", "NODE") and len(msg) == 3: node_id = msg[2] name = self.getParticleLabel(node_id) self.send(("GET_NAME", "NODE", node_id, name), "outbox") else: print "Command Error: please check your command format!" else: print "Command Error: not enough parameters!" def _generatePos(self, posSpec): """\ generateXY(posSpec) -> (x,y,z) or raises ValueError posSpec == "randompos" or "auto" -> random (x,y,z) within the surface (specified border distance in from the edege) posSpec == "(XXX,YYY,ZZZ)" -> specified x,y,z (positive or negative integers) """ posSpec = posSpec.lower() if posSpec == "randompos" or posSpec == "auto": # FIXME: need to consider camera/ viewer setting zLim = self.display.nearPlaneDist, self.display.farPlaneDist z = -1 * random.randrange( int((zLim[1] - zLim[0]) / 20) + self.border, int((zLim[1] - zLim[0]) / 8) - self.border, 1) yLim = z * math.tan( self.display.perspectiveAngle * math.pi / 360.0), -z * math.tan( self.display.perspectiveAngle * math.pi / 360.0) xLim = yLim[0] * self.display.aspectRatio, yLim[ 1] * self.display.aspectRatio y = random.randrange( int(yLim[0]) + self.border, int(yLim[1]) - self.border, 1) x = random.randrange( int(xLim[0]) + self.border, int(xLim[1]) - self.border, 1) #print x,y,z return x, y, z else: match = re.match("^([+-]?\d+),([+-]?\d+),([+-]?\d+)$", posSpec) if match: x = int(match.group(1)) y = int(match.group(2)) z = int(match.group(3)) return x, y, z raise ValueError("Unrecognised position specification") def addParticle(self, *particles): """Add particles to the system""" for p in particles: if p.radius > self.biggestRadius: self.biggestRadius = p.radius pLevel = p.ID.count(':') if self.maxLevel < pLevel: self.maxLevel = pLevel # create display request for every particle added disprequest = { "OGL_DISPLAYREQUEST": True, "objectid": id(p), "callback": (self, "callback"), "events": (self, "events"), "size": p.size } # send display request self.send(disprequest, "display_signal") self.physics.add(*particles) def removeParticle(self, *ids): """\ Remove particle(s) specified by their ids. Also breaks any bonds to/from that particle. """ for ident in ids: self.physics.particleDict[ident].breakAllBonds() try: self.display.ogl_objects.remove( id(self.physics.particleDict[ident])) self.display.ogl_names.pop(id( self.physics.particleDict[ident])) self.display.ogl_displaylists.pop( id(self.physics.particleDict[ident])) self.display.ogl_transforms.pop( id(self.physics.particleDict[ident])) except KeyError: pass # if self.selected == self.physics.particleDict[id]: # self.selectParticle(None) self.physics.removeByID(*ids) for ident in ids: try: self.currentDisplayedPhysics.removeByID(ident) except KeyError: pass #print self.currentDisplayedPhysics.particles #print self.physics.particles def selectParticle(self, particle): """Select the specified particle.""" if self.multiSelectMode: if particle not in self.selectedParticles: particle.select() self.selectedParticles.append(particle) self.send("('SELECT', 'NODE', '" + particle.name + "')", "outbox") else: particle.deselect() self.selectedParticles.remove(particle) self.send("('DESELECT', 'NODE', '" + particle.name + "')", "outbox") else: self.deselectAll() self.selectedParticles = [] particle.select() self.selectedParticles.append(particle) self.send("('SELECT', 'NODE', '" + particle.name + "')", "outbox") def deselectAll(self): """Deselect all particles.""" for particle in self.selectedParticles: particle.deselect() self.selectedParticles = [] def makeBond(self, source, dest): """Make a bond from source to destination particle, specified by IDs""" self.physics.particleDict[source].makeBond(self.physics.particleDict, dest) self.physics.particleDict[source].needRedraw = True def breakBond(self, source, dest): """Break a bond from source to destination particle, specified by IDs""" self.physics.particleDict[source].breakBond(self.physics.particleDict, dest) self.physics.particleDict[source].needRedraw = True def updateParticleLabel(self, node_id, new_name): """\ updateParticleLabel(node_id, new_name) -> updates the given nodes name & visual label if it exists node_id - an id for an already existing node new_name - a string (may include spaces) defining the new node name """ for p in self.physics.particles: if p.ID == node_id: p.set_label(new_name) p.needRedraw = True return def getParticleLabel(self, node_id): """\ getParticleLabel(node_id) -> particle's name Returns the name/label of the specified particle. """ for p in self.physics.particles: if p.ID == node_id: return p.name def getTopology(self): """getTopology() -> list of command tuples that would build the current topology""" topology = [] # first, enumerate the particles for particle in self.physics.particles: topology.append(("ADD", "NODE", particle.ID, particle.name, "random", particle.originaltype)) # now enumerate the linkages for particle in self.physics.particles: for dst in particle.getBondedTo(): topology.append(("ADD", "LINK", particle.ID, dst.ID)) return topology
class TopologyViewer3D(Axon.AdaptiveCommsComponent.AdaptiveCommsComponent): """\ TopologyViewer3D(...) -> new TopologyViewer3D component. A component that takes incoming topology (change) data and displays it live using pygame OpenGL. A simple physics model assists with visual layout. Particle types, appearance and physics interactions can be customised. """ Inboxes = { "inbox" : "Topology (change) data describing an Axon system", "control" : "Shutdown signalling", "alphacontrol" : "Alpha (transparency) of the image (value 0..255)", "callback": "for the response after a displayrequest", "events" : "Place where we recieve events from the outside world", "displaycontrol" : "Replies from Pygame Display service", } Outboxes = { "signal" : "NOT USED", "outbox" : "Notification and topology output", "display_signal" : "Requests to Pygame Display service", } def __init__(self, screensize = (800,600), fullscreen = False, caption = "Topology Viewer", particleTypes = None, initialTopology = None, laws = None, simCyclesPerRedraw = 1, border = 0, extraDrawing = None, showGrid = True, transparency = None, position = None): """x.__init__(...) initializes x; see x.__class__.__doc__ for signature""" super(TopologyViewer3D, self).__init__() glutInit(sys.argv) tracker = _cat.coordinatingassistanttracker.getcat() self.display = OpenGLDisplay(width=screensize[0], height=screensize[1],fullscreen=fullscreen, title=caption) self.display.activate() OpenGLDisplay.setDisplayService(self.display, tracker) self.link((self,"display_signal"), (self.display,"notify")) self.link((self.display,"signal"), (self,"control")) self.border = border if particleTypes == None: self.particleTypes = {"-":CuboidParticle3D, "cuboid":CuboidParticle3D, "sphere":SphereParticle3D, "teapot":TeapotParticle3D} else: self.particleTypes = particleTypes self.hitParticles = [] self.multiSelectMode = False self.selectedParticles = [] self.grabbed = False self.rotationMode = False if laws==None: self.laws = Kamaelia.Support.Particles.SimpleLaws(bondLength=2) else: self.laws = laws self.physics = ParticleSystemX(self.laws, [], 0) self.biggestRadius = 0 # Do interaction self.simCyclesPerRedraw = simCyclesPerRedraw self.lastIdleTime = time.time() # Tell if new node is added; if true, new id needs adding to OpenGLDisplay list self.isNewNode = False # For hierarchy structure self.maxLevel = 0 self.currentLevel = 0 self.currentParentParticleID = '' self.viewerOldPos = Vector() self.levelViewerPos = {} self.currentDisplayedPhysics = ParticleSystemX(self.laws, [], 0) # For double click self.lastClickPos = (0,0) self.lastClickTime = time.time() self.dClickRes = 0.3 def main(self): """\ """ # create display request for itself self.size = Vector(0,0,0) disprequest = { "OGL_DISPLAYREQUEST" : True, "objectid" : id(self), "callback" : (self,"callback"), "events" : (self, "events"), "size": self.size } # send display request self.send(disprequest, "display_signal") # wait for response on displayrequest and get identifier of the viewer while not self.dataReady("callback"): yield 1 self.identifier = self.recv("callback") self.addListenEvents( [pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION, pygame.KEYDOWN, pygame.KEYUP ]) pygame.key.set_repeat(100,100) while True: # process incoming messages if self.dataReady("inbox"): message = self.recv("inbox") self.doCommand(message) #print message # wait for response on displayrequest and get identifier of the particle if self.isNewNode: while not self.dataReady("callback"): yield 1 self.physics.particles[-1].identifier = self.recv("callback") self.isNewNode = False else: self.lastIdleTime = 0 yield 1 if self.lastIdleTime + 1.0 < time.time(): #print [particle.pos for particle in self.physics.particles] avoidedList = [] avoidedList.extend(self.hitParticles) avoidedList.extend(self.selectedParticles) #self.currentDisplayedPhysics.particleDict[ident].breakAllBonds() self.currentDisplayedPhysics.particles = [] if self.physics.particles != []: for particle in self.physics.particles: if self.currentParentParticleID == '': if ':' not in particle.ID: self.currentDisplayedPhysics.add( particle ) particle.oldpos = particle.initialpos elif particle.ID.find(self.currentParentParticleID) == 0 and particle.ID.count(':') == self.currentLevel: self.currentDisplayedPhysics.add( particle ) particle.oldpos = particle.initialpos self.currentDisplayedPhysics.run(self.simCyclesPerRedraw, avoidedList=avoidedList) #print [particle.pos for particle in self.physics.particles] # Draw particles if new or updated for particle in self.currentDisplayedPhysics.particles: if particle.needRedraw: self.drawParticles(particle) #particle.needRedraw = False self.handleEvents() # Perform transformation for particle in self.currentDisplayedPhysics.particles: transform_update = particle.applyTransforms() if transform_update is not None: self.send(transform_update, "display_signal") #print transform_update #print [particle.pos for particle in self.physics.particles] self.lastIdleTime = time.time() else: yield 1 if self.dataReady("control"): msg = self.recv("control") if isinstance(msg, Axon.Ipc.shutdownMicroprocess): self.quit(msg) def quit(self,msg=Axon.Ipc.shutdownMicroprocess()): print 'Shut down...' self.send(msg, "signal") self.scheduler.stop() def draw(self): """\ Invoke draw() and save its commands to a newly generated displaylist. The displaylist name is then sent to the display service via a "DISPLAYLIST_UPDATE" request. """ pass def drawParticles(self, *particles): for particle in particles: # display list id displaylist = glGenLists(1) # draw object to its displaylist glNewList(displaylist, GL_COMPILE) particle.draw() glEndList() #print displaylist dl_update = { "DISPLAYLIST_UPDATE": True, "objectid": id(particle), "displaylist": displaylist } self.send(dl_update, "display_signal") #print particle def addListenEvents(self, events): """\ Sends listening request for pygame events to the display service. The events parameter is expected to be a list of pygame event constants. """ for event in events: self.send({"ADDLISTENEVENT":event, "objectid":id(self)}, "display_signal") def removeListenEvents(self, events): """\ Sends stop listening request for pygame events to the display service. The events parameter is expected to be a list of pygame event constants. """ for event in events: self.send({"REMOVELISTENEVENT":event, "objectid":id(self)}, "display_signal") def handleEvents(self): """ Handle events. """ while self.dataReady("events"): event = self.recv("events") if event.type == pygame.MOUSEBUTTONDOWN or pygame.MOUSEMOTION and self.grabbed: if not self.rotationMode: for particle in self.hitParticles: p1 = Vector(*particle.pos).copy() p1.x += 10 p2 = Vector(*particle.pos).copy() p2.y += 10 #z = Intersect.ray_Plane(Vector(0,0,0), event.direction, [Vector(*particle.pos)-self.display.viewerposition, p1, p2]) z = Intersect.ray_Plane(Vector(0,0,0), event.direction, [Vector(*particle.pos)-Vector(0,0,self.display.viewerposition.z), p1-Vector(0,0,self.display.viewerposition.z), p2-Vector(0,0,self.display.viewerposition.z)]) newpoint = event.direction * z if event.type == pygame.MOUSEBUTTONDOWN: if event.button == 1: # Handle double click clickPos = event.pos currentTime = time.time() elapsedTime = currentTime - self.lastClickTime if clickPos == self.lastClickPos and elapsedTime<self.dClickRes: if self.currentLevel < self.maxLevel and len(self.selectedParticles) == 1: hasChildParticles = False for particle in self.physics.particles: if particle.ID.find(self.selectedParticles[0].ID) == 0 and particle.ID != self.selectedParticles[0].ID: hasChildParticles = True break if hasChildParticles: self.currentParentParticleID = self.selectedParticles[0].ID self.gotoDisplayLevel(1) else: print 'Warning: The particle you double-clicked has no children!' else: if self.currentLevel == self.maxLevel: print "Warning: max hierarchy level has reached!" if len(self.selectedParticles) != 1: print "Tips: To extend a node, please double-click the node you want to extend" else: if not self.rotationMode: for particle in self.currentDisplayedPhysics.particles: if particle.identifier in event.hitobjects: #particle.oldpos = particle.oldpos - self.display.viewerposition self.grabbed = True #particle.scaling = Vector(0.9,0.9,0.9) self.hitParticles.append(particle) self.selectParticle(particle) #print str(id(particle))+'hit' #print self.hitParticles # If click places other than particles in non multiSelectMode, deselect all if not self.hitParticles and not self.multiSelectMode: self.deselectAll() self.lastClickPos = clickPos self.lastClickTime = currentTime if event.button == 3: if self.currentLevel > 0: items = self.currentParentParticleID.split(':') items.pop() self.currentParentParticleID = ':'.join(items) self.gotoDisplayLevel(-1) else: print "Warning: The first hierarchy level has reached!" if event.button == 4: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles for particle in particles: posVector = Vector(*particle.pos) posVector.z -= 1 particle.pos = posVector.toTuple() if event.button == 5: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles for particle in particles: posVector = Vector(*particle.pos) posVector.z += 1 particle.pos = posVector.toTuple() if event.type == pygame.MOUSEBUTTONUP: if event.button == 1: for particle in self.hitParticles: self.grabbed = False particle.oldpoint = None #particle.scaling = Vector(1,1,1) self.hitParticles.pop(self.hitParticles.index(particle)) #print self.hitParticles if event.type == pygame.MOUSEMOTION: if not self.rotationMode and self.grabbed: for particle in self.hitParticles: try: if particle.oldpoint is not None: #print particle.pos diff = newpoint-particle.oldpoint amount = (diff.x, diff.y) particle.pos = (Vector(*particle.pos)+Vector(*amount)).toTuple() except NameError: pass # Redraw the link so that the link can move with the particle for p in particle.bondedFrom: p.needRedraw = True elif self.rotationMode: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAnglex = float(event.rel[1])*math.pi/180 dAngley = -float(event.rel[0])*math.pi/180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = (relativePosVector.z*relativePosVector.z+relativePosVector.y*relativePosVector.y)**0.5 newAnglex = (math.atan2(relativePosVector.z,relativePosVector.y)+dAnglex) particle.pos = (posVector.x, radius*math.cos(newAnglex)+centrePoint.y, radius*math.sin(newAnglex)+centrePoint.z) posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = (relativePosVector.z*relativePosVector.z+relativePosVector.x*relativePosVector.x)**0.5 newAngley = (math.atan2(relativePosVector.z,relativePosVector.x)+dAngley) particle.pos = (radius*math.cos(newAngley)+centrePoint.x, posVector.y, radius*math.sin(newAngley)+centrePoint.z) particle.drotation.y = float(event.rel[0]) particle.drotation.x = float(event.rel[1]) particle.drotation %= 360 try: for particle in self.hitParticles: particle.oldpoint = newpoint except NameError: pass # Keyboard events handling if event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: self.quit() elif event.key == pygame.K_BACKSPACE: if self.currentLevel > 0: items = self.currentParentParticleID.split(':') items.pop() self.currentParentParticleID = ':'.join(items) self.gotoDisplayLevel(-1) else: print "Warning: The first hierarchy level has reached!" elif event.key == pygame.K_RETURN: if self.currentLevel < self.maxLevel and len(self.selectedParticles) == 1: hasChildParticles = False for particle in self.physics.particles: if particle.ID.find(self.selectedParticles[0].ID) == 0 and particle.ID != self.selectedParticles[0].ID: hasChildParticles = True break if hasChildParticles: self.currentParentParticleID = self.selectedParticles[0].ID self.gotoDisplayLevel(1) else: print 'Warning: The particle you double-clicked has no children!' else: if self.currentLevel == self.maxLevel: print "Warning: max hierarchy level has reached!" if len(self.selectedParticles) != 1: print "Tips: To extend a node, please click to select the node (only one) you want to extend first." elif event.key == pygame.K_LSHIFT or event.key == pygame.K_RSHIFT: self.multiSelectMode = True elif event.key == pygame.K_LCTRL or event.key == pygame.K_RCTRL: self.rotationMode = True elif event.key == pygame.K_PAGEUP: self.display.viewerposition.z -= 0.5 elif event.key == pygame.K_PAGEDOWN: self.display.viewerposition.z += 0.5 elif event.key == pygame.K_w: self.display.viewerposition.y += 0.5 elif event.key == pygame.K_s: self.display.viewerposition.y -= 0.5 elif event.key == pygame.K_a: self.display.viewerposition.x -= 0.5 elif event.key == pygame.K_d: self.display.viewerposition.x += 0.5 elif event.key == pygame.K_UP: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = -20*math.pi/180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = (relativePosVector.z*relativePosVector.z+relativePosVector.y*relativePosVector.y)**0.5 newAngle = (math.atan2(relativePosVector.z,relativePosVector.y)+dAngle) particle.pos = (posVector.x, radius*math.cos(newAngle)+centrePoint.y, radius*math.sin(newAngle)+centrePoint.z) particle.drotation = Vector(dAngle*180/math.pi,0,0) elif event.key == pygame.K_DOWN: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = 20*math.pi/180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = (relativePosVector.z*relativePosVector.z+relativePosVector.y*relativePosVector.y)**0.5 newAngle = (math.atan2(relativePosVector.z,relativePosVector.y)+dAngle) particle.pos = (posVector.x, radius*math.cos(newAngle)+centrePoint.y, radius*math.sin(newAngle)+centrePoint.z) particle.drotation = Vector(dAngle*180/math.pi,0,0) elif event.key == pygame.K_LEFT: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = 20*math.pi/180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = (relativePosVector.z*relativePosVector.z+relativePosVector.x*relativePosVector.x)**0.5 newAngle = (math.atan2(relativePosVector.z,relativePosVector.x)+dAngle) particle.pos = (radius*math.cos(newAngle)+centrePoint.x, posVector.y, radius*math.sin(newAngle)+centrePoint.z) particle.drotation = Vector(0,-dAngle*180/math.pi,0) elif event.key == pygame.K_RIGHT: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = -20*math.pi/180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = (relativePosVector.z*relativePosVector.z+relativePosVector.x*relativePosVector.x)**0.5 newAngle = (math.atan2(relativePosVector.z,relativePosVector.x)+dAngle) particle.pos = (radius*math.cos(newAngle)+centrePoint.x, posVector.y, radius*math.sin(newAngle)+centrePoint.z) particle.drotation = Vector(0,-dAngle*180/math.pi,0) elif event.key == pygame.K_COMMA: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = 20*math.pi/180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = (relativePosVector.x*relativePosVector.x+relativePosVector.y*relativePosVector.y)**0.5 newAngle = (math.atan2(relativePosVector.y,relativePosVector.x)+dAngle) particle.pos = (radius*math.cos(newAngle)+centrePoint.x, radius*math.sin(newAngle)+centrePoint.y, posVector.z) particle.drotation = Vector(0,0,dAngle*180/math.pi) elif event.key == pygame.K_PERIOD: if self.selectedParticles: particles = self.selectedParticles else: particles = self.currentDisplayedPhysics.particles centrePoint = Vector() for particle in particles: posVector = Vector(*particle.pos) centrePoint += posVector centrePoint /= len(particles) dAngle = -20*math.pi/180 for particle in particles: posVector = Vector(*particle.pos) relativePosVector = posVector - centrePoint radius = (relativePosVector.x*relativePosVector.x+relativePosVector.y*relativePosVector.y)**0.5 newAngle = (math.atan2(relativePosVector.y,relativePosVector.x)+dAngle) particle.pos = (radius*math.cos(newAngle)+centrePoint.x, radius*math.sin(newAngle)+centrePoint.y, posVector.z) particle.drotation = Vector(0,0,dAngle*180/math.pi) #print self.display.viewerposition # Scroll if self.display.viewerposition changes if self.display.viewerposition.copy() != self.viewerOldPos: self.scroll() self.viewerOldPos = self.display.viewerposition.copy() # for particle in self.currentDisplayedPhysics.particles: # particle.oldpoint = None if event.type == pygame.KEYUP: if event.key == pygame.K_LSHIFT or event.key == pygame.K_RSHIFT: self.multiSelectMode = False elif event.key == pygame.K_LCTRL or event.key == pygame.K_RCTRL: self.rotationMode = False def scroll( self ): # Scroll the surface by resetting gluLookAt glMatrixMode(GL_PROJECTION) glLoadIdentity() self.display.setProjection() def gotoDisplayLevel( self, dlevel): # Save current level's viewer position self.levelViewerPos[self.currentLevel] = self.display.viewerposition.copy() # Display next level self.currentLevel += dlevel # Reset viewer position to previous try: self.display.viewerposition = self.levelViewerPos[self.currentLevel].copy() except KeyError: self.display.viewerposition = self.levelViewerPos[self.currentLevel] = Vector() # Remove current displayed particles for particle in self.currentDisplayedPhysics.particles: self.display.ogl_displaylists.pop(id(particle)) self.display.ogl_transforms.pop(id(particle)) self.currentDisplayedPhysics.removeByID(*self.currentDisplayedPhysics.particleDict.keys()) def doCommand(self, msg): """\ Proceses a topology command tuple: [ "ADD", "NODE", <id>, <name>, <positionSpec>, <particle type> ] [ "DEL", "NODE", <id> ] [ "ADD", "LINK", <id from>, <id to> ] [ "DEL", "LINK", <id from>, <id to> ] [ "DEL", "ALL" ] [ "GET", "ALL" ] """ #print 'doCommand' if len(msg) >= 2: cmd = msg[0].upper(), msg[1].upper() if cmd == ("ADD", "NODE") and len(msg) == 6: if msg[2] in [p.ID for p in self.physics.particles]: print "Node exists, please use a new node ID!" else: if self.particleTypes.has_key(msg[5]): #print 'ADD NODE begin' ptype = self.particleTypes[msg[5]] ident = msg[2] name = msg[3] posSpec = msg[4] pos = self._generatePos(posSpec) #print pos particle = ptype(position = pos, ID=ident, name=name) particle.originaltype = msg[5] #self.particles.append(particle) #print self.particles[0] self.addParticle(particle) self.isNewNode = True #print id(particle) #print 'ADD NODE end' elif cmd == ("DEL", "NODE") and len(msg) == 3: ident = msg[2] self.removeParticle(ident) elif cmd == ("ADD", "LINK") and len(msg) == 4: src = msg[2] dst = msg[3] self.makeBond(src, dst) elif cmd == ("DEL", "LINK") and len(msg) == 4: src = msg[2] dst = msg[3] self.breakBond(src, dst) elif cmd == ("DEL", "ALL") and len(msg) == 2: self.removeParticle(*self.physics.particleDict.keys()) self.currentLevel = 0 self.currentParentParticleID = '' elif cmd == ("GET", "ALL") and len(msg) == 2: topology = [("DEL","ALL")] topology.extend(self.getTopology()) self.send( ("TOPOLOGY", topology), "outbox" ) elif cmd == ("UPDATE_NAME", "NODE") and len(msg) == 4: node_id = msg[2] new_name = msg[3] self.updateParticleLabel(node_id, new_name) self.send( ("UPDATE_NAME", "NODE", node_id, new_name), "outbox" ) elif cmd == ("GET_NAME", "NODE") and len(msg) == 3: node_id = msg[2] name = self.getParticleLabel(node_id) self.send( ("GET_NAME", "NODE", node_id, name), "outbox" ) else: print "Command Error: please check your command format!" else: print "Command Error: not enough parameters!" def _generatePos(self, posSpec): """\ generateXY(posSpec) -> (x,y,z) or raises ValueError posSpec == "randompos" or "auto" -> random (x,y,z) within the surface (specified border distance in from the edege) posSpec == "(XXX,YYY,ZZZ)" -> specified x,y,z (positive or negative integers) """ posSpec = posSpec.lower() if posSpec == "randompos" or posSpec == "auto" : # FIXME: need to consider camera/ viewer setting zLim = self.display.nearPlaneDist, self.display.farPlaneDist z = -1*random.randrange(int((zLim[1]-zLim[0])/20)+self.border,int((zLim[1]-zLim[0])/8)-self.border,1) yLim = z*math.tan(self.display.perspectiveAngle*math.pi/360.0), -z*math.tan(self.display.perspectiveAngle*math.pi/360.0) xLim = yLim[0]*self.display.aspectRatio, yLim[1]*self.display.aspectRatio y = random.randrange(int(yLim[0])+self.border,int(yLim[1])-self.border,1) x = random.randrange(int(xLim[0])+self.border,int(xLim[1])-self.border,1) #print x,y,z return x,y,z else: match = re.match("^([+-]?\d+),([+-]?\d+),([+-]?\d+)$", posSpec) if match: x = int(match.group(1)) y = int(match.group(2)) z = int(match.group(3)) return x,y,z raise ValueError("Unrecognised position specification") def addParticle(self, *particles): """Add particles to the system""" for p in particles: if p.radius > self.biggestRadius: self.biggestRadius = p.radius pLevel = p.ID.count(':') if self.maxLevel < pLevel: self.maxLevel = pLevel # create display request for every particle added disprequest = { "OGL_DISPLAYREQUEST" : True, "objectid" : id(p), "callback" : (self,"callback"), "events" : (self, "events"), "size": p.size } # send display request self.send(disprequest, "display_signal") self.physics.add( *particles ) def removeParticle(self, *ids): """\ Remove particle(s) specified by their ids. Also breaks any bonds to/from that particle. """ for ident in ids: self.physics.particleDict[ident].breakAllBonds() try: self.display.ogl_objects.remove(id(self.physics.particleDict[ident])) self.display.ogl_names.pop(id(self.physics.particleDict[ident])) self.display.ogl_displaylists.pop(id(self.physics.particleDict[ident])) self.display.ogl_transforms.pop(id(self.physics.particleDict[ident])) except KeyError: pass # if self.selected == self.physics.particleDict[id]: # self.selectParticle(None) self.physics.removeByID(*ids) for ident in ids: try: self.currentDisplayedPhysics.removeByID(ident) except KeyError: pass #print self.currentDisplayedPhysics.particles #print self.physics.particles def selectParticle(self, particle): """Select the specified particle.""" if self.multiSelectMode: if particle not in self.selectedParticles: particle.select() self.selectedParticles.append(particle) self.send( "('SELECT', 'NODE', '"+particle.name+"')", "outbox" ) else: particle.deselect() self.selectedParticles.remove(particle) self.send( "('DESELECT', 'NODE', '"+particle.name+"')", "outbox" ) else: self.deselectAll() self.selectedParticles = [] particle.select() self.selectedParticles.append(particle) self.send( "('SELECT', 'NODE', '"+particle.name+"')", "outbox" ) def deselectAll(self): """Deselect all particles.""" for particle in self.selectedParticles: particle.deselect() self.selectedParticles = [] def makeBond(self, source, dest): """Make a bond from source to destination particle, specified by IDs""" self.physics.particleDict[source].makeBond(self.physics.particleDict, dest) self.physics.particleDict[source].needRedraw = True def breakBond(self, source, dest): """Break a bond from source to destination particle, specified by IDs""" self.physics.particleDict[source].breakBond(self.physics.particleDict, dest) self.physics.particleDict[source].needRedraw = True def updateParticleLabel(self, node_id, new_name): """\ updateParticleLabel(node_id, new_name) -> updates the given nodes name & visual label if it exists node_id - an id for an already existing node new_name - a string (may include spaces) defining the new node name """ for p in self.physics.particles: if p.ID == node_id: p.set_label(new_name) p.needRedraw = True return def getParticleLabel(self, node_id): """\ getParticleLabel(node_id) -> particle's name Returns the name/label of the specified particle. """ for p in self.physics.particles: if p.ID == node_id: return p.name def getTopology(self): """getTopology() -> list of command tuples that would build the current topology""" topology = [] # first, enumerate the particles for particle in self.physics.particles: topology.append( ( "ADD","NODE", particle.ID, particle.name, "random", particle.originaltype ) ) # now enumerate the linkages for particle in self.physics.particles: for dst in particle.getBondedTo(): topology.append( ( "ADD","LINK", particle.ID, dst.ID ) ) return topology