def __init__(
        self,
        screensize=(800, 600),
        fullscreen=False,
        caption="Topology Viewer",
        particleTypes=None,
        initialTopology=None,
        laws=None,
        simCyclesPerRedraw=None,
        border=100,
        extraDrawing=None,
        showGrid=True,
        transparency=None,
        position=None,
    ):
        """x.__init__(...) initializes x; see x.__class__.__doc__ for signature"""

        tracker = _cat.coordinatingassistanttracker.getcat()
        display = OpenGLDisplay(width=screensize[0], height=screensize[1], fullscreen=fullscreen, title=caption)
        display.activate()
        OpenGLDisplay.setDisplayService(display, tracker)

        super(TopologyViewer3D, self).__init__()

        self.particle = None
        self.particleTypes = {"-": Particle3D}
Example #2
0
    def initialiseComponent(self):
        # listen to shutdown events
        ogl_display = OpenGLDisplay.getDisplayService()[0]
        self.link( (ogl_display, "signal"), (self, "control") )
    
        # create board
        self.boardvis = CheckersBoard(position=(0,0,-15)).activate()
        
        self.interactor_comms = {}

        self.board = {}                
        for i in range(8):
            self.board[i] = {}
            for j in range(8):
                self.board[i][j] = None
        
        # create black pieces
        self.blackPieces = []
        self.blackInteractors = []
        for i in range(8):
            for j in range(3):
                if (i+j) %2 == 0:
                    x = float(i)-3.5
                    y = float(j)-3.5
                    piece = CheckersPiece(position=(x, y, -15), colour=(0.6,0,0)).activate()
                    self.blackPieces.append(piece)

                    interactor = CheckersInteractor(target=piece, colour='B').activate()
                    self.blackInteractors.append(interactor)

                    intcomms = self.addOutbox("interactor_comms")
                    self.interactor_comms[id(interactor)] = intcomms
                    self.link( (self, intcomms), (interactor, "inbox"))
                    self.link( (interactor, "outbox"), (self, "inbox"))
                    
                    self.board[i][j] = 'B'

                    
        # create white pieces
        self.whitePieces = []
        self.whiteInteractors = []
        for i in range(8):
            for j in range(5,8):
                if (i+j) %2 == 0:
                    x = float(i)-3.5
                    y = float(j)-3.5
                    piece = CheckersPiece(position=(x, y, -15), colour=(0,0,0.6)).activate()
                    self.whitePieces.append(piece)

                    interactor = CheckersInteractor(target=piece, colour='B').activate()
                    self.whiteInteractors.append(interactor)

                    intcomms = self.addOutbox("interactor_comms")
                    self.interactor_comms[id(interactor)] = intcomms
                    self.link( (self, intcomms), (interactor, "inbox"))
                    self.link( (interactor, "outbox"), (self, "inbox"))

                    self.board[i][j] = 'W'

        return 1
Example #3
0
    def initUIComponents(self):
        # listen to shutdown events
        ogl_display = OpenGLDisplay().getDisplayService()[0]
        self.link((ogl_display, "signal"), (self, "control"))

        # init mover
        self.mover = WheelMover(radius=15,
                                center=(0, 0, -25),
                                steps=500,
                                slots=40).activate()
        self.link((self, "mover_signal"), (self.mover, "notify"))
        self.link((self, "mover_switch"), (self.mover, "switch"))

        self.background = SkyGrassBackground(size=(5000, 5000, 0),
                                             position=(0, 0, -90)).activate()

        # create & link nav buttons
        self.up_button = ArrowButton(size=(1, 1, 0.3),
                                     position=(7, 5, -15),
                                     msg="UP").activate()
        self.down_button = ArrowButton(size=(1, 1, 0.3),
                                       position=(7, -5, -15),
                                       rotation=(0, 0, 180),
                                       msg="DOWN").activate()
        self.link((self.up_button, "outbox"), (self, "nav"))
        self.link((self.down_button, "outbox"), (self, "nav"))

        # init info display
        self.infoticker = Ticker(text_height=21,
                                 render_right=250,
                                 render_bottom=500,
                                 background_colour=(250, 250, 200),
                                 text_colour=(0, 0, 0),
                                 outline_colour=(255, 255, 255)).activate()
        self.tickerwrapper = PygameWrapper(wrap=self.infoticker,
                                           size=(2.4, 4.0, 0.3)).activate()
        self.hideinfo_button = Button(caption="Hide", fontsize=30).activate()

        infocontents = {
            self.tickerwrapper: {
                "position": (0, 0, 0)
            },
            self.hideinfo_button: {
                "position": (0, -2.4, 0)
            },
        }

        self.infocontainer = Container(contents=infocontents,
                                       position=(-10, 10, -16)).activate()
        infopath = LinearPath([(-10, 10, -16), (-3, 0, -8)], 100)

        self.infomover = PathMover(infopath, False).activate()

        self.link((self.infomover, "outbox"), (self.infocontainer, "position"))
        self.link((self, "infomover_commands"), (self.infomover, "inbox"))
        self.link((self, "torrent_info"), (self.infoticker, "inbox"))
        self.link((self.hideinfo_button, "outbox"), (self, "hide_info"))

        self.send("Stop", "infomover_commands")
    def __init__(self,
                 screensize=(800, 600),
                 fullscreen=False,
                 caption="Topology Viewer",
                 particleTypes=None,
                 initialTopology=None,
                 laws=None,
                 simCyclesPerRedraw=None,
                 border=100,
                 extraDrawing=None,
                 showGrid=True,
                 transparency=None,
                 position=None):
        """x.__init__(...) initializes x; see x.__class__.__doc__ for signature"""

        tracker = _cat.coordinatingassistanttracker.getcat()
        display = OpenGLDisplay(width=screensize[0],
                                height=screensize[1],
                                fullscreen=fullscreen,
                                title=caption)
        display.activate()
        OpenGLDisplay.setDisplayService(display, tracker)

        super(TopologyViewer3D, self).__init__()

        self.particle = None
        self.particleTypes = {"-": Particle3D}
Example #5
0
from Kamaelia.Codec.Dirac import DiracDecoder
from Kamaelia.File.ReadFileAdaptor import ReadFileAdaptor
from Kamaelia.Util.RateFilter import MessageRateLimit
from VideoSurface import VideoSurface
from PixFormatConversion import ToRGB_interleaved

from Kamaelia.UI.PygameDisplay import PygameDisplay
from Kamaelia.UI.OpenGL.OpenGLDisplay import OpenGLDisplay
from Kamaelia.UI.OpenGL.PygameWrapper import PygameWrapper
from Kamaelia.UI.OpenGL.SkyGrassBackground import SkyGrassBackground
from Kamaelia.UI.OpenGL.Movement import SimpleRotator

file = "../../../Code/Python/Kamaelia/Examples/VideoCodecs/Dirac/snowboard-jump-352x288x75.0.5.4.drc"
framerate = 15

# override pygame display service
ogl_display = OpenGLDisplay.getDisplayService()
PygameDisplay.setDisplayService(ogl_display[0])

SkyGrassBackground(size=(5000, 5000, 0), position=(0, 0, -100)).activate()
screen = VideoSurface()
screen__in_scene = PygameWrapper(wrap=screen,
                                 position=(0, 0, -5),
                                 rotation=(-30, 15, 3)).activate()
rotator = SimpleRotator(amount=(0.0, 0.0, 0.5)).activate()
rotator.link((rotator, "outbox"), (screen__in_scene, "rel_rotation"))

Pipeline(ReadFileAdaptor(file, readmode="bitrate", bitrate=300000 * 8 / 5),
         DiracDecoder(), ToRGB_interleaved(),
         MessageRateLimit(framerate, buffer=15), screen).run()
Example #6
0
    def initialiseComponent(self):
        # listen to shutdown events
        ogl_display = OpenGLDisplay.getDisplayService()[0]
        self.link((ogl_display, "signal"), (self, "control"))

        # create board
        self.boardvis = CheckersBoard(position=(0, 0, -15)).activate()

        self.interactor_comms = {}

        self.board = {}
        for i in range(8):
            self.board[i] = {}
            for j in range(8):
                self.board[i][j] = None

        # create black pieces
        self.blackPieces = []
        self.blackInteractors = []
        for i in range(8):
            for j in range(3):
                if (i + j) % 2 == 0:
                    x = float(i) - 3.5
                    y = float(j) - 3.5
                    piece = CheckersPiece(position=(x, y, -15),
                                          colour=(0.6, 0, 0)).activate()
                    self.blackPieces.append(piece)

                    interactor = CheckersInteractor(target=piece,
                                                    colour='B').activate()
                    self.blackInteractors.append(interactor)

                    intcomms = self.addOutbox("interactor_comms")
                    self.interactor_comms[id(interactor)] = intcomms
                    self.link((self, intcomms), (interactor, "inbox"))
                    self.link((interactor, "outbox"), (self, "inbox"))

                    self.board[i][j] = 'B'

        # create white pieces
        self.whitePieces = []
        self.whiteInteractors = []
        for i in range(8):
            for j in range(5, 8):
                if (i + j) % 2 == 0:
                    x = float(i) - 3.5
                    y = float(j) - 3.5
                    piece = CheckersPiece(position=(x, y, -15),
                                          colour=(0, 0, 0.6)).activate()
                    self.whitePieces.append(piece)

                    interactor = CheckersInteractor(target=piece,
                                                    colour='B').activate()
                    self.whiteInteractors.append(interactor)

                    intcomms = self.addOutbox("interactor_comms")
                    self.interactor_comms[id(interactor)] = intcomms
                    self.link((self, intcomms), (interactor, "inbox"))
                    self.link((interactor, "outbox"), (self, "inbox"))

                    self.board[i][j] = 'W'

        return 1
Example #7
0
                if (to[0] < 0 or to[0] > 7 or to[1] < 0 or to[1] > 7
                        or to[0] + to[1]) % 2 != 0 or self.board[to[0]][
                            to[1]] is not None:
                    self.send("INVALID", self.interactor_comms[objectid])
                else:
                    self.board[fr[0]][fr[1]] = None
                    self.board[to[0]][to[1]] = colour
                    self.send("ACK", self.interactor_comms[objectid])

        while self.dataReady("control"):
            cmsg = self.recv("control")
            if isinstance(cmsg, Axon.Ipc.shutdownMicroprocess):
                # dirty way to terminate program
                sys.exit(0)

        return 1


if __name__ == '__main__':
    # initialise display, change point of view
    ogl_display = OpenGLDisplay(viewerposition=(0, -10, 0),
                                lookat=(0, 0, -15),
                                limit_fps=100)
    ogl_display.activate()
    OpenGLDisplay.setDisplayService(ogl_display)

    Checkers().activate()
    Axon.Scheduler.scheduler.run.runThreads()
# Licensed to the BBC under a Contributor Agreement: THF
Example #8
0
 def __init__(self, screensize         = (800,600),
                    fullscreen         = False, 
                    caption            = "3D Topology Viewer", 
                    particleTypes      = None,
                    initialTopology    = None,
                    laws               = None,
                    simCyclesPerRedraw = 1,
                    border             = 0):
     """x.__init__(...) initializes x; see x.__class__.__doc__ for signature"""
     
     super(TopologyViewer3D, self).__init__()
     
     glutInit(sys.argv)
     
     tracker = _cat.coordinatingassistanttracker.getcat()
     try:
         self.display = tracker.retrieveService("ogl_display")[0]
     except KeyError:
         self.display = OpenGLDisplay(width=screensize[0], height=screensize[1],fullscreen=fullscreen,
                                 title=caption)
         self.display.activate()
         OpenGLDisplay.setDisplayService(self.display, tracker)
     self.display = OpenGLDisplay.getDisplayService()[0]                
     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
         
     if initialTopology == None:
         initialTopology = ([],[])
     self.initialNodes   = list(initialTopology[0])
     self.initialBonds   = list(initialTopology[1])
     
     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 = ParticleSystem(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.previousParentParticleID = self.currentParentParticleID = ''
     self.viewerOldPos = Vector()
     self.levelViewerPos = {}
     # The Physics particle system of current display level for display
     self.currentDisplayedPhysics = ParticleSystem(self.laws, [], 0)
     
     # For double click
     self.lastClickPos = (0,0)
     self.lastClickTime = time.time()
     self.dClickRes = 0.3
Example #9
0
    def __init__(self,
                 screensize=(800, 600),
                 fullscreen=False,
                 caption="3D Topology Viewer",
                 particleTypes=None,
                 initialTopology=None,
                 laws=None,
                 simCyclesPerRedraw=1,
                 border=0):
        """x.__init__(...) initializes x; see x.__class__.__doc__ for signature"""

        super(TopologyViewer3D, self).__init__()

        glutInit(sys.argv)

        tracker = _cat.coordinatingassistanttracker.getcat()
        try:
            self.display = tracker.retrieveService("ogl_display")[0]
        except KeyError:
            self.display = OpenGLDisplay(width=screensize[0],
                                         height=screensize[1],
                                         fullscreen=fullscreen,
                                         title=caption)
            self.display.activate()
            OpenGLDisplay.setDisplayService(self.display, tracker)
        self.display = OpenGLDisplay.getDisplayService()[0]
        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

        if initialTopology == None:
            initialTopology = ([], [])
        self.initialNodes = list(initialTopology[0])
        self.initialBonds = list(initialTopology[1])

        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 = ParticleSystem(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.previousParentParticleID = self.currentParentParticleID = ''
        self.viewerOldPos = Vector()
        self.levelViewerPos = {}
        # The Physics particle system of current display level for display
        self.currentDisplayedPhysics = ParticleSystem(self.laws, [], 0)

        # For double click
        self.lastClickPos = (0, 0)
        self.lastClickTime = time.time()
        self.dClickRes = 0.3
Example #10
0
from Kamaelia.Apps.Whiteboard.SmartBoard import SmartBoard
from Kamaelia.Apps.Whiteboard.Webcam import Webcam

#from Webcam import Webcam
from Webcam import VideoCaptureSource

#from BlankCanvas import BlankCanvas


if __name__=="__main__":
    width = 1024
    height = 768
    top = 0
    left = 0
    colours_order = [ "black", "red", "orange", "yellow", "green", "turquoise", "blue", "purple", "darkgrey", "lightgrey" ]
    ogl_display = OpenGLDisplay(title="Kamaelia Whiteboard",width=width,height=height,background_colour=(255,255,255))
    ogl_display.activate()
    OpenGLDisplay.setDisplayService(ogl_display)
    
    ogl_display = OpenGLDisplay.getDisplayService()
    PygameDisplay.setDisplayService(ogl_display[0])
    
    if (0):
        #PygameDisplay.setDisplayService(ogl_display)
        CANVAS = Canvas( position=(left,top+32),size=(1200,(900-(32+15))),notepad="Test" ).activate() #(replace width with 'width' and height with 'height-(32+15)'
        PAINTER = Painter().activate()
        CANVAS_WRAPPER = PygameWrapper(wrap=CANVAS, position=(0,0,-10), rotation=(0,0,0)).activate() 
        ERASER  = Eraser(left,top).activate()
        PALETTE = buildPalette( cols=colours, order=colours_order, topleft=(left+64,top), size=32 ).activate()
        CLEAR = ClearPage(left+(64*5)+32*len(colours),top).activate()
        #PALETTE_WRAPPER = PygameWrapper(wrap=PALETTE, position=(4,1,-10), rotation=(-20,15,3)).activate()
Example #11
0
from Kamaelia.Chassis.Pipeline import Pipeline
from Kamaelia.UI.OpenGL.OpenGLDisplay import OpenGLDisplay
from Kamaelia.UI.OpenGL.PygameWrapper import PygameWrapper
from Kamaelia.UI.OpenGL.MatchedTranslationInteractor import MatchedTranslationInteractor
from Kamaelia.UI.PygameDisplay import PygameDisplay
from Kamaelia.UI.Pygame.Button import Button
from Kamaelia.UI.Pygame.Text import Textbox, TextDisplayer

from Kamaelia.UI.Pygame.VideoSurface import VideoSurface
from Kamaelia.Codec.Dirac import DiracDecoder
from Kamaelia.Util.RateFilter import MessageRateLimit
from Kamaelia.File.ReadFileAdaptor import ReadFileAdaptor
from Kamaelia.Video.PixFormatConversion import ToRGB_interleaved

# override pygame display service
ogl_display = OpenGLDisplay.getDisplayService(fullscreen=True)
PygameDisplay.setDisplayService(ogl_display[0])

READER = Textbox(size=(400, 300), text_height=30).activate()
WRITER = TextDisplayer(size=(400, 300), text_height=30).activate()

SCREEN = VideoSurface().activate()

Pipeline(
    ReadFileAdaptor("TestMaterial/TrainWindow.drc",
                    readmode="bitrate",
                    bitrate=1000000),
    DiracDecoder(),
    MessageRateLimit(10),
    ToRGB_interleaved(),
    SCREEN,
Example #12
0
from Kamaelia.Chassis.Pipeline import Pipeline
from Kamaelia.UI.OpenGL.OpenGLDisplay import OpenGLDisplay
from Kamaelia.UI.OpenGL.PygameWrapper import PygameWrapper
from Kamaelia.UI.OpenGL.MatchedTranslationInteractor import MatchedTranslationInteractor
from Kamaelia.UI.PygameDisplay import PygameDisplay
from Kamaelia.UI.Pygame.Button import Button
from Kamaelia.UI.Pygame.Text import Textbox, TextDisplayer

from Kamaelia.UI.Pygame.VideoSurface import VideoSurface
from Kamaelia.Codec.Dirac import DiracDecoder
from Kamaelia.Util.RateFilter import MessageRateLimit
from Kamaelia.File.ReadFileAdaptor import ReadFileAdaptor
from Kamaelia.Video.PixFormatConversion import ToRGB_interleaved

# override pygame display service
ogl_display = OpenGLDisplay.getDisplayService(fullscreen=True)
PygameDisplay.setDisplayService(ogl_display[0])

READER = Textbox(size = (400, 300),text_height=30).activate()
WRITER = TextDisplayer(size = (400, 300),text_height=30).activate()

SCREEN = VideoSurface().activate()


Pipeline(
         ReadFileAdaptor("TestMaterial/TrainWindow.drc", readmode="bitrate",
                         bitrate = 1000000),
         DiracDecoder(),
         MessageRateLimit(10),
         ToRGB_interleaved(),
         SCREEN,
Example #13
0
  def main(self):
    while 1:
      self.capture_one()
      self.send(self.snapshot, "outbox")
      time.sleep(self.delay)


from Kamaelia.UI.OpenGL.OpenGLDisplay import OpenGLDisplay
from Kamaelia.UI.PygameDisplay import PygameDisplay
from Kamaelia.UI.OpenGL.SkyGrassBackground import SkyGrassBackground

from Kamaelia.UI.OpenGL.PygameWrapper import PygameWrapper
from Kamaelia.UI.OpenGL.MatchedTranslationInteractor import MatchedTranslationInteractor

ogl_display = OpenGLDisplay.getDisplayService()
PygameDisplay.setDisplayService(ogl_display[0])
# SkyGrassBackground(size=(5000,5000,0), position=(0,0,-100)).activate()

screen = VideoSurface().activate()
screen_in_scene = PygameWrapper(wrap=screen, position=(0, 0,-8), rotation=(-30,15,3)).activate()

i1 = MatchedTranslationInteractor(target=screen_in_scene).activate()

Pipeline(
   VideoCaptureSource(),
   PureTransformer(lambda F : \
             {"rgb" : pygame.image.tostring(F, "RGB"),
                      "size" : (352, 288),
                      "pixformat" : "RGB_interleaved",
             }),
Example #14
0
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
Example #15
0
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
Example #16
0
def rotate(angle, vector):
    cos = math.cos(angle)
    sin = math.sin(angle)
    return (cos * vector[0] + sin * vector[1],
            -sin * vector[0] + cos * vector[1])


def dist(vector):
    return (vector[0] * vector[0] + vector[1] * vector[1])**0.5


if __name__ == '__main__':
    import Axon

    from Kamaelia.UI.OpenGL.OpenGLDisplay import OpenGLDisplay
    from Kamaelia.UI.OpenGL.SimpleRotationInteractor import SimpleRotationInteractor

    display = OpenGLDisplay(background_colour=(0.75, 0.75, 1.0)).activate()
    OpenGLDisplay.setDisplayService(display)

    FOLD = Simple3dFold(position=(0, 0, -22),
                        size=(15, 15, 2),
                        rotation=(-30, 0, 0),
                        radius=1.0,
                        segments=15).activate()
    #    SimpleRotationInteractor(target=FOLD).activate()

    print "Grab close to a corner and drag!"

    Axon.Scheduler.scheduler.run.runThreads()
Example #17
0
def left90(vector):
    return (-vector[1],vector[0])

def right90(vector):
    return (vector[1],-vector[0])

def rotate(angle, vector):
    cos = math.cos(angle)
    sin = math.sin(angle)
    return ( cos*vector[0]+sin*vector[1],
            -sin*vector[0]+cos*vector[1] )

def dist(vector):
    return (vector[0]*vector[0] + vector[1]*vector[1])**0.5

if __name__ == '__main__':
    import Axon

    from Kamaelia.UI.OpenGL.OpenGLDisplay import OpenGLDisplay
    from Kamaelia.UI.OpenGL.SimpleRotationInteractor import SimpleRotationInteractor

    display = OpenGLDisplay(background_colour=(0.75, 0.75, 1.0)).activate()
    OpenGLDisplay.setDisplayService(display)

    FOLD = Simple3dFold(position=(0,0,-22), size=(15,15,2), rotation=(-30,0,0),radius=1.0,segments=15).activate()
#    SimpleRotationInteractor(target=FOLD).activate()

    print "Grab close to a corner and drag!"

    Axon.Scheduler.scheduler.run.runThreads()
Example #18
0
class TopologyViewer3D(Axon.Component.component):
    """\
    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.
    
    Keyword arguments (in order):
    
    - screensize          -- (width,height) of the display area (default = (800,600))
    - fullscreen          -- True to start up in fullscreen mode (default = False)
    - caption             -- Caption for the pygame window (default = "3D Topology Viewer")
    - particleTypes       -- dict("type" -> klass) mapping types of particle to classes used to render them (default = {"-":CuboidParticle3D})
    - initialTopology     -- (nodes,bonds) where bonds=list((src,dst)) starting state for the topology  (default=([],[]))
    - laws                -- Physics laws to apply between particles (default = SimpleLaws(bondlength=2))
    - simCyclesPerRedraw  -- number of physics sim cycles to run between each redraw (default=1)
    - border              -- Minimum distance from edge of display area that new particles appear (default=0)
    """

    Inboxes = {
        "inbox": "Topology (change) data describing an Axon system",
        "control": "Shutdown signalling",
        "callback": "for the response after a displayrequest",
        "events": "Place where we recieve events from the outside world",
    }

    Outboxes = {
        "signal": "Control signalling",
        "outbox": "Notification and topology output",
        "display_signal": "Requests to Pygame Display service",
    }

    def __init__(self,
                 screensize=(800, 600),
                 fullscreen=False,
                 caption="3D Topology Viewer",
                 particleTypes=None,
                 initialTopology=None,
                 laws=None,
                 simCyclesPerRedraw=1,
                 border=0):
        """x.__init__(...) initializes x; see x.__class__.__doc__ for signature"""

        super(TopologyViewer3D, self).__init__()

        glutInit(sys.argv)

        tracker = _cat.coordinatingassistanttracker.getcat()
        try:
            self.display = tracker.retrieveService("ogl_display")[0]
        except KeyError:
            self.display = OpenGLDisplay(width=screensize[0],
                                         height=screensize[1],
                                         fullscreen=fullscreen,
                                         title=caption)
            self.display.activate()
            OpenGLDisplay.setDisplayService(self.display, tracker)
        self.display = OpenGLDisplay.getDisplayService()[0]
        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

        if initialTopology == None:
            initialTopology = ([], [])
        self.initialNodes = list(initialTopology[0])
        self.initialBonds = list(initialTopology[1])

        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 = ParticleSystem(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.previousParentParticleID = self.currentParentParticleID = ''
        self.viewerOldPos = Vector()
        self.levelViewerPos = {}
        # The Physics particle system of current display level for display
        self.currentDisplayedPhysics = ParticleSystem(self.laws, [], 0)

        # For double click
        self.lastClickPos = (0, 0)
        self.lastClickTime = time.time()
        self.dClickRes = 0.3

    def initialiseComponent(self):
        """Initialises."""
        self.addListenEvents([
            pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION,
            pygame.KEYDOWN, pygame.KEYUP
        ])
        # For key holding handling
        pygame.key.set_repeat(100, 100)

        for node in self.initialNodes:
            self.addParticle(*node)

        for source, dest in self.initialBonds:
            self.makeBond(source, dest)

    def main(self):
        """Main loop."""
        # Make display request for event listening purpose
        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.initialiseComponent()

        while True:
            # Process incoming messages
            if self.dataReady("inbox"):
                message = self.recv("inbox")
                self.doCommand(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():
                #Freeze selected particles so that they are not subject to the physics law
                for particle in self.selectedParticles:
                    particle.freeze()
                # Do interaction between particles
                self.currentDisplayedPhysics.run(self.simCyclesPerRedraw)
                # Unfreeze selected particles
                for particle in self.selectedParticles:
                    particle.unFreeze()

                # Draw particles if new or updated
                for particle in self.currentDisplayedPhysics.particles:
                    if particle.needRedraw:
                        self.drawParticles(particle)

                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")

                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()):
        """Cause termination."""
        print('Shut down...')
        self.send(msg, "signal")
        self.scheduler.stop()

    def draw(self):
        """\
        Dummy method reserved for future use
        
        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):
        """\
            Sends particles drawing opengl command to the display service.
        """
        for particle in particles:
            # Display list id
            displaylist = glGenLists(1)
            # Draw object to its displaylist
            glNewList(displaylist, GL_COMPILE)
            particle.draw()
            glEndList()

            # Send displaylist
            dl_update = {
                "DISPLAYLIST_UPDATE": True,
                "objectid": id(particle),
                "displaylist": displaylist
            }
            self.send(dl_update, "display_signal")

    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 event.type == pygame.MOUSEMOTION or event.type == pygame.MOUSEBUTTONUP:
                self.handleMouseEvents(event)
            elif event.type == pygame.KEYDOWN or event.type == pygame.KEYUP:
                self.handleKeyEvents(event)

            # Scroll if self.display.viewerposition changes
            if self.display.viewerposition.copy() != self.viewerOldPos:
                self.scroll()
                self.viewerOldPos = self.display.viewerposition.copy()

    def handleMouseEvents(self, event):
        """Handle mouse 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
                    # Get the position of mouse
                    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 it's a double-click
                if clickPos == self.lastClickPos and elapsedTime < self.dClickRes:
                    self.gotoDisplayLevel(1)
                else:  # Single click
                    if not self.rotationMode:  # Select particle
                        for particle in self.currentDisplayedPhysics.particles:
                            if particle.identifier in event.hitobjects:
                                self.grabbed = True
                                self.hitParticles.append(particle)
                                self.selectParticle(particle)
                        # 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
            elif event.button == 3:  # Right-clicked
                self.gotoDisplayLevel(-1)
            elif event.button == 4:  # Scrolled-up: zoom out
                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()
            elif event.button == 5:  # Scrolled-down: zoom in
                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
                    self.hitParticles.pop(self.hitParticles.index(particle))
        if event.type == pygame.MOUSEMOTION:
            if not self.rotationMode and self.grabbed:  # Drag particles
                for particle in self.hitParticles:
                    try:
                        if particle.oldpoint is not None:
                            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:  # Rotate particles
                dAnglex = float(event.rel[1])
                dAngley = -float(event.rel[0])
                self.rotateParticles(self.selectedParticles,
                                     (dAnglex, dAngley, 0))

        try:
            for particle in self.hitParticles:
                particle.oldpoint = newpoint
        except NameError:
            pass

    def handleKeyEvents(self, event):
        """Handle keyboard events."""
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                self.quit()
            elif event.key == pygame.K_BACKSPACE:
                self.gotoDisplayLevel(-1)
            elif event.key == pygame.K_RETURN:
                self.gotoDisplayLevel(1)
            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
            # Change viewer position
            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
            # Rotate particles
            elif event.key == pygame.K_UP:
                self.rotateParticles(self.selectedParticles, (-20, 0, 0))
            elif event.key == pygame.K_DOWN:
                self.rotateParticles(self.selectedParticles, (20, 0, 0))
            elif event.key == pygame.K_LEFT:
                self.rotateParticles(self.selectedParticles, (0, 20, 0))
            elif event.key == pygame.K_RIGHT:
                self.rotateParticles(self.selectedParticles, (0, -20, 0))
            elif event.key == pygame.K_COMMA:
                self.rotateParticles(self.selectedParticles, (0, 0, 20))
            elif event.key == pygame.K_PERIOD:
                self.rotateParticles(self.selectedParticles, (0, 0, -20))
        # Key exit (release) handling
        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_LSHIFT or event.key == pygame.K_RSHIFT:
                # Return to normal mode from multiSelectMode
                self.multiSelectMode = False
            elif event.key == pygame.K_LCTRL or event.key == pygame.K_RCTRL:
                # Return to normal mode from rotationMode
                self.rotationMode = False

    def scroll(self):
        """Scroll the surface by resetting gluLookAt."""
        glMatrixMode(GL_PROJECTION)
        glLoadIdentity()
        self.display.setProjection()

    def rotateParticles(self, particles, dAngle):
        """\
        Rotate the particles around their common centre dAngle degree.
        Particles is a list; dAngle is a triple tuple of degree.
        If particles are given an empty list, rotate all particles instead.
        """
        if particles == []:
            particles = self.currentDisplayedPhysics.particles
        centrePoint = Vector()
        for particle in particles:
            posVector = Vector(*particle.pos)
            centrePoint += posVector
        centrePoint /= len(particles)
        if dAngle[0] != 0:  # Rotate around x axis
            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[0] * math.pi / 180)
                particle.pos = (posVector.x,
                                radius * math.cos(newAngle) + centrePoint.y,
                                radius * math.sin(newAngle) + centrePoint.z)
                particle.drotation += Vector(dAngle[0], 0, 0)
        if dAngle[1] != 0:  # Rotate around y axis
            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[1] * math.pi / 180)
                particle.pos = (radius * math.cos(newAngle) + centrePoint.x,
                                posVector.y,
                                radius * math.sin(newAngle) + centrePoint.z)
                particle.drotation += Vector(0, -dAngle[1], 0)
        if dAngle[2] != 0:  # Rotate around z axis
            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[2] * math.pi / 180)
                particle.pos = (radius * math.cos(newAngle) + centrePoint.x,
                                radius * math.sin(newAngle) + centrePoint.y,
                                posVector.z)
                particle.drotation += Vector(0, 0, dAngle[2])
        # An angle keeps the same with when it minus muptiple 360
        particle.drotation %= 360

    def gotoDisplayLevel(self, dlevel):
        """Switch to another display level."""
        isValid = False
        if self.currentLevel + dlevel > self.maxLevel:
            print("Warning: max hierarchy level has reached!")
        elif self.currentLevel + dlevel < 0:
            print("Warning: The first hierarchy level has reached!")
        else:
            if dlevel < 0:  # Go to the last dlevel level
                self.previousParentParticleID = self.currentParentParticleID
                items = self.currentParentParticleID.split(':')
                for _ in xrange(-dlevel):
                    items.pop()
                self.currentParentParticleID = ':'.join(items)
                isValid = True
            if dlevel == 1:  # It only makes sense if dlevel == 1 when go to next dlevel level
                if 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.previousParentParticleID = self.currentParentParticleID
                        self.currentParentParticleID = self.selectedParticles[
                            0].ID
                        isValid = True
                    else:
                        print(
                            'Warning: The particle you double-clicked has no children!'
                        )
                else:
                    print(
                        "Tips: To extend a node, please double-click the node you want to extend"
                    )
        # Show the specified display level if valid
        if isValid:
            # Save current level's viewer position
            self.levelViewerPos[
                self.currentLevel, self.
                previousParentParticleID] = self.display.viewerposition.copy()
            # Deselect all
            self.deselectAll()
            # Display next level
            self.currentLevel += dlevel
            # Reset viewer position to previous
            try:
                self.display.viewerposition = self.levelViewerPos[
                    self.currentLevel, self.currentParentParticleID].copy()
            except KeyError:
                self.display.viewerposition = self.levelViewerPos[
                    self.currentLevel,
                    self.currentParentParticleID] = 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())

            # Add current level's particles to self.currentDisplayedPhysics.particles for display
            self.currentDisplayedPhysics.particles = []
            if self.physics.particles != []:
                for particle in self.physics.particles:
                    if self.currentParentParticleID == '':  # If no parent, it's the top level
                        if ':' not in particle.ID:
                            self.currentDisplayedPhysics.add(particle)
                            particle.oldpos = particle.initialpos
                    # The child particles of self.currentParentParticleID
                    elif particle.ID.find(
                            self.currentParentParticleID
                    ) == 0 and particle.ID.count(':') == self.currentLevel:
                        self.currentDisplayedPhysics.add(particle)
                        particle.oldpos = particle.initialpos

    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" ]
        """
        if len(msg) >= 2:
            cmd = msg[0].upper(), msg[1].upper()

            # Add default arguments when they are not provided
            if cmd == ("ADD", "NODE"):
                if len(msg) == 4:
                    msg += ['randompos', '-']
                elif len(msg) == 5:
                    msg += ['-']

            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 (msg[5] in self.particleTypes):
                        ptype = self.particleTypes[msg[5]]
                        ident = msg[2]
                        name = msg[3]

                        posSpec = msg[4]
                        pos = self._generatePos(posSpec)

                        particle = ptype(position=pos, ID=ident, name=name)
                        particle.originaltype = msg[5]

                        self.addParticle(particle)
                        self.isNewNode = True

            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)
        spaces are allowed within the tuple, but quotation is needed in this case.
        E.g., " ( 0 , 0 , -10 ) "
        """
        posSpec = posSpec.lower()
        if posSpec == "randompos" or posSpec == "auto":
            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)
            # Apply camera/ viewer transformation
            x += self.display.viewerposition.x
            y += self.display.viewerposition.y
            z += self.display.viewerposition.z
            return x, y, z
        else:  # given specified position
            posSpec = posSpec.strip()
            # Use triple tuple format for position
            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
            # Make 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)

        # Add new particles to self.currentDisplayedPhysics
        for particle in particles:
            if self.currentParentParticleID == '':  # If no parent, it's the top level
                if ':' not in particle.ID:
                    self.currentDisplayedPhysics.add(particle)
                    particle.oldpos = particle.initialpos
            # The child particles of self.currentParentParticleID
            elif particle.ID.find(
                    self.currentParentParticleID) == 0 and particle.ID.count(
                        ':') == self.currentLevel:
                self.currentDisplayedPhysics.add(particle)
                particle.oldpos = particle.initialpos

    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
        self.physics.removeByID(*ids)
        for ident in ids:
            try:
                self.currentDisplayedPhysics.removeByID(ident)
            except KeyError:
                pass

    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
Example #19
0
class TopologyViewer3D(Axon.Component.component):
    """\
    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.
    
    Keyword arguments (in order):
    
    - screensize          -- (width,height) of the display area (default = (800,600))
    - fullscreen          -- True to start up in fullscreen mode (default = False)
    - caption             -- Caption for the pygame window (default = "3D Topology Viewer")
    - particleTypes       -- dict("type" -> klass) mapping types of particle to classes used to render them (default = {"-":CuboidParticle3D})
    - initialTopology     -- (nodes,bonds) where bonds=list((src,dst)) starting state for the topology  (default=([],[]))
    - laws                -- Physics laws to apply between particles (default = SimpleLaws(bondlength=2))
    - simCyclesPerRedraw  -- number of physics sim cycles to run between each redraw (default=1)
    - border              -- Minimum distance from edge of display area that new particles appear (default=0)
    """
    
    Inboxes = { "inbox"          : "Topology (change) data describing an Axon system",
                "control"        : "Shutdown signalling",
                "callback"       : "for the response after a displayrequest",
                "events"         : "Place where we recieve events from the outside world",
              }
              
    Outboxes = { "signal"         : "Control signalling",
                 "outbox"         : "Notification and topology output",
                 "display_signal" : "Requests to Pygame Display service",
               }
                                                     
    
    def __init__(self, screensize         = (800,600),
                       fullscreen         = False, 
                       caption            = "3D Topology Viewer", 
                       particleTypes      = None,
                       initialTopology    = None,
                       laws               = None,
                       simCyclesPerRedraw = 1,
                       border             = 0):
        """x.__init__(...) initializes x; see x.__class__.__doc__ for signature"""
        
        super(TopologyViewer3D, self).__init__()
        
        glutInit(sys.argv)
        
        tracker = _cat.coordinatingassistanttracker.getcat()
        try:
            self.display = tracker.retrieveService("ogl_display")[0]
        except KeyError:
            self.display = OpenGLDisplay(width=screensize[0], height=screensize[1],fullscreen=fullscreen,
                                    title=caption)
            self.display.activate()
            OpenGLDisplay.setDisplayService(self.display, tracker)
        self.display = OpenGLDisplay.getDisplayService()[0]                
        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
            
        if initialTopology == None:
            initialTopology = ([],[])
        self.initialNodes   = list(initialTopology[0])
        self.initialBonds   = list(initialTopology[1])
        
        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 = ParticleSystem(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.previousParentParticleID = self.currentParentParticleID = ''
        self.viewerOldPos = Vector()
        self.levelViewerPos = {}
        # The Physics particle system of current display level for display
        self.currentDisplayedPhysics = ParticleSystem(self.laws, [], 0)
        
        # For double click
        self.lastClickPos = (0,0)
        self.lastClickTime = time.time()
        self.dClickRes = 0.3
    
    
    def initialiseComponent(self):
        """Initialises."""
        self.addListenEvents( [pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP, pygame.MOUSEMOTION, pygame.KEYDOWN, pygame.KEYUP ])
        # For key holding handling
        pygame.key.set_repeat(100,100)
        
        for node in self.initialNodes:
            self.addParticle(*node)

        for source,dest in self.initialBonds:
            self.makeBond(source, dest)
    
    
    def main(self):
        """Main loop."""
        # Make display request for event listening purpose
        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.initialiseComponent()
        
        while True:
            # Process incoming messages
            if self.dataReady("inbox"):
                message = self.recv("inbox")
                self.doCommand(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():
                #Freeze selected particles so that they are not subject to the physics law
                for particle in self.selectedParticles:
                    particle.freeze()
                # Do interaction between particles
                self.currentDisplayedPhysics.run(self.simCyclesPerRedraw)
                # Unfreeze selected particles
                for particle in self.selectedParticles:
                    particle.unFreeze()
                
                # Draw particles if new or updated
                for particle in self.currentDisplayedPhysics.particles:
                    if particle.needRedraw:
                        self.drawParticles(particle)
                
                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")
                
                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()):
        """Cause termination."""
        print ('Shut down...')
        self.send(msg, "signal")
        self.scheduler.stop()
    
    
    def draw(self):
        """\
        Dummy method reserved for future use
        
        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):
        """\
            Sends particles drawing opengl command to the display service.
        """
        for particle in particles:
            # Display list id
            displaylist = glGenLists(1)
            # Draw object to its displaylist
            glNewList(displaylist, GL_COMPILE)
            particle.draw()
            glEndList()
    
            # Send displaylist
            dl_update = { "DISPLAYLIST_UPDATE": True,
                          "objectid": id(particle),
                          "displaylist": displaylist
                        }
            self.send(dl_update, "display_signal")
    
    
    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 event.type == pygame.MOUSEMOTION or event.type == pygame.MOUSEBUTTONUP:
                self.handleMouseEvents(event)
            elif event.type == pygame.KEYDOWN or event.type == pygame.KEYUP:
                self.handleKeyEvents(event)
                
            # Scroll if self.display.viewerposition changes
            if self.display.viewerposition.copy() != self.viewerOldPos:
                self.scroll()
                self.viewerOldPos = self.display.viewerposition.copy()

    
    def handleMouseEvents(self, event):
        """Handle mouse 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
                    # Get the position of mouse
                    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 it's a double-click
                if clickPos == self.lastClickPos and elapsedTime<self.dClickRes:
                    self.gotoDisplayLevel(1)
                else: # Single click
                    if not self.rotationMode: # Select particle
                        for particle in self.currentDisplayedPhysics.particles:
                            if particle.identifier in event.hitobjects:
                                self.grabbed = True
                                self.hitParticles.append(particle)
                                self.selectParticle(particle)
                        # 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
            elif event.button == 3: # Right-clicked
                self.gotoDisplayLevel(-1)
            elif event.button == 4: # Scrolled-up: zoom out
                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()
            elif event.button == 5: # Scrolled-down: zoom in
                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
                    self.hitParticles.pop(self.hitParticles.index(particle))
        if event.type == pygame.MOUSEMOTION: 
            if not self.rotationMode and self.grabbed: # Drag particles
                for particle in self.hitParticles:
                    try:
                        if particle.oldpoint is not None:
                            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: # Rotate particles
                dAnglex = float(event.rel[1])
                dAngley = -float(event.rel[0])
                self.rotateParticles(self.selectedParticles, (dAnglex,dAngley,0))
        
        try:
            for particle in self.hitParticles:
                particle.oldpoint = newpoint                    
        except NameError: pass    
    
    
    def handleKeyEvents(self, event):
        """Handle keyboard events."""            
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                self.quit()
            elif event.key == pygame.K_BACKSPACE:
                self.gotoDisplayLevel(-1)
            elif event.key == pygame.K_RETURN:
                self.gotoDisplayLevel(1)
            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
            # Change viewer position
            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
            # Rotate particles
            elif event.key == pygame.K_UP:
                self.rotateParticles(self.selectedParticles, (-20,0,0))      
            elif event.key == pygame.K_DOWN:
                self.rotateParticles(self.selectedParticles, (20,0,0))      
            elif event.key == pygame.K_LEFT:
                self.rotateParticles(self.selectedParticles, (0,20,0))    
            elif event.key == pygame.K_RIGHT:
                self.rotateParticles(self.selectedParticles, (0,-20,0))
            elif event.key == pygame.K_COMMA:
                self.rotateParticles(self.selectedParticles, (0,0,20))    
            elif event.key == pygame.K_PERIOD:
                self.rotateParticles(self.selectedParticles, (0,0,-20))
        # Key exit (release) handling
        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_LSHIFT or event.key == pygame.K_RSHIFT:
                # Return to normal mode from multiSelectMode
                self.multiSelectMode = False
            elif event.key == pygame.K_LCTRL or event.key == pygame.K_RCTRL:
                # Return to normal mode from rotationMode
                self.rotationMode = False                 
    
    
    def scroll( self ):
        """Scroll the surface by resetting gluLookAt."""
        glMatrixMode(GL_PROJECTION)                 
        glLoadIdentity()
        self.display.setProjection()
        
    
    def rotateParticles( self, particles, dAngle ):
        """\
        Rotate the particles around their common centre dAngle degree.
        Particles is a list; dAngle is a triple tuple of degree.
        If particles are given an empty list, rotate all particles instead.
        """
        if particles == []:
            particles = self.currentDisplayedPhysics.particles
        centrePoint = Vector() 
        for particle in particles:
            posVector = Vector(*particle.pos)
            centrePoint += posVector
        centrePoint /= len(particles)
        if dAngle[0] != 0: # Rotate around x axis
            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[0]*math.pi/180)
                particle.pos = (posVector.x, radius*math.cos(newAngle)+centrePoint.y, radius*math.sin(newAngle)+centrePoint.z)
                particle.drotation += Vector(dAngle[0],0,0)
        if dAngle[1] != 0: # Rotate around y axis
            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[1]*math.pi/180)
                    particle.pos = (radius*math.cos(newAngle)+centrePoint.x, posVector.y, radius*math.sin(newAngle)+centrePoint.z)
                    particle.drotation += Vector(0,-dAngle[1],0)
        if dAngle[2] != 0: # Rotate around z axis
            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[2]*math.pi/180)
                particle.pos = (radius*math.cos(newAngle)+centrePoint.x, radius*math.sin(newAngle)+centrePoint.y, posVector.z)
                particle.drotation += Vector(0,0,dAngle[2])
        # An angle keeps the same with when it minus muptiple 360 
        particle.drotation %= 360
        
    
    def gotoDisplayLevel( self, dlevel):
        """Switch to another display level."""
        isValid = False
        if self.currentLevel + dlevel > self.maxLevel:
            print ("Warning: max hierarchy level has reached!")
        elif self.currentLevel + dlevel < 0:
            print ("Warning: The first hierarchy level has reached!")
        else:
            if dlevel < 0: # Go to the last dlevel level
                self.previousParentParticleID = self.currentParentParticleID
                items = self.currentParentParticleID.split(':')
                for _ in xrange(-dlevel):
                    items.pop()
                self.currentParentParticleID = ':'.join(items)
                isValid = True
            if dlevel == 1: # It only makes sense if dlevel == 1 when go to next dlevel level
                if 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.previousParentParticleID = self.currentParentParticleID
                        self.currentParentParticleID = self.selectedParticles[0].ID
                        isValid = True
                    else:
                        print ('Warning: The particle you double-clicked has no children!')
                else:
                    print ("Tips: To extend a node, please double-click the node you want to extend")
        # Show the specified display level if valid
        if isValid:                    
            # Save current level's viewer position
            self.levelViewerPos[self.currentLevel, self.previousParentParticleID] = self.display.viewerposition.copy()
            # Deselect all
            self.deselectAll()
            # Display next level
            self.currentLevel += dlevel
            # Reset viewer position to previous
            try:
                self.display.viewerposition = self.levelViewerPos[self.currentLevel, self.currentParentParticleID].copy()
            except KeyError:
                self.display.viewerposition = self.levelViewerPos[self.currentLevel, self.currentParentParticleID] = 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())
            
            # Add current level's particles to self.currentDisplayedPhysics.particles for display
            self.currentDisplayedPhysics.particles = []
            if self.physics.particles != []:
                for particle in self.physics.particles:
                    if self.currentParentParticleID == '': # If no parent, it's the top level 
                        if ':' not in particle.ID:
                            self.currentDisplayedPhysics.add( particle )
                            particle.oldpos = particle.initialpos
                    # The child particles of self.currentParentParticleID
                    elif particle.ID.find(self.currentParentParticleID) == 0 and particle.ID.count(':') == self.currentLevel:
                        self.currentDisplayedPhysics.add( particle )
                        particle.oldpos = particle.initialpos
                            
    
    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" ]
        """  
        if len(msg) >= 2:
            cmd = msg[0].upper(), msg[1].upper()
            
            # Add default arguments when they are not provided
            if cmd == ("ADD", "NODE"):
                if len(msg) == 4:
                    msg += ['randompos', '-']
                elif len(msg) == 5:
                    msg += ['-']

            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 ( msg[5] in self.particleTypes ):
                        ptype = self.particleTypes[msg[5]]
                        ident    = msg[2]
                        name  = msg[3]
                        
                        posSpec = msg[4]
                        pos     = self._generatePos(posSpec)
    
                        particle = ptype(position = pos, ID=ident, name=name)
                        particle.originaltype = msg[5]
                      
                        self.addParticle(particle)
                        self.isNewNode = True

            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)
        spaces are allowed within the tuple, but quotation is needed in this case.
        E.g., " ( 0 , 0 , -10 ) "
        """
        posSpec = posSpec.lower()
        if posSpec == "randompos" or posSpec == "auto" :
            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)
            # Apply camera/ viewer transformation
            x += self.display.viewerposition.x
            y += self.display.viewerposition.y
            z += self.display.viewerposition.z
            return x,y,z            
        else: # given specified position
            posSpec = posSpec.strip()
            # Use triple tuple format for position
            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
            # Make 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 )
        
        # Add new particles to self.currentDisplayedPhysics
        for particle in particles:
            if self.currentParentParticleID == '': # If no parent, it's the top level 
                if ':' not in particle.ID:
                    self.currentDisplayedPhysics.add( particle )
                    particle.oldpos = particle.initialpos
            # The child particles of self.currentParentParticleID
            elif particle.ID.find(self.currentParentParticleID) == 0 and particle.ID.count(':') == self.currentLevel:
                self.currentDisplayedPhysics.add( particle )
                particle.oldpos = particle.initialpos
    
        
    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
        self.physics.removeByID(*ids)
        for ident in ids:
            try:
                self.currentDisplayedPhysics.removeByID(ident)
            except KeyError: pass
    
        
    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
Example #20
0
        
    def hideInfo(self):
        self.send("Backward", "infomover_commands")
        self.send("Play", "infomover_commands")
        
        
if __name__ == "__main__":
    from Kamaelia.Chassis.Graphline import Graphline
    from Kamaelia.Util.Console import ConsoleReader
    from Kamaelia.UI.PygameDisplay import PygameDisplay
    from Kamaelia.UI.OpenGL.OpenGLDisplay import OpenGLDisplay
    from Kamaelia.Protocol.HTTP.HTTPClient import SimpleHTTPClient
    from Kamaelia.Protocol.Torrent.TorrentPatron import TorrentPatron
    
    ogl_display = OpenGLDisplay(limit_fps=100).activate()  
    OpenGLDisplay.setDisplayService(ogl_display)
    # override pygame display service
    PygameDisplay.setDisplayService(ogl_display)
    
    Graphline(
        reader = ConsoleReader(prompt="Enter torrent location:", eol=""),
        httpclient = SimpleHTTPClient(),
        gui = TorrentOpenGLGUI(),
        backend = TorrentPatron(),
        linkages = {
            ("gui", "outbox") : ("backend", "inbox"),
            ("reader", "outbox") : ("gui", "torrent_url"),
            ("gui", "fetcher") : ("httpclient", "inbox"),
            ("httpclient", "outbox") : ("gui", "torrent_file"),
            ("backend", "outbox"): ("gui", "inbox")
        }
Example #21
0
        self.send(infostring, "torrent_info")

    def hideInfo(self):
        self.send("Backward", "infomover_commands")
        self.send("Play", "infomover_commands")


if __name__ == "__main__":
    from Kamaelia.Chassis.Graphline import Graphline
    from Kamaelia.Util.Console import ConsoleReader
    from Kamaelia.UI.PygameDisplay import PygameDisplay
    from Kamaelia.UI.OpenGL.OpenGLDisplay import OpenGLDisplay
    from Kamaelia.Protocol.HTTP.HTTPClient import SimpleHTTPClient
    from Kamaelia.Protocol.Torrent.TorrentPatron import TorrentPatron

    ogl_display = OpenGLDisplay(limit_fps=100).activate()
    OpenGLDisplay.setDisplayService(ogl_display)
    # override pygame display service
    PygameDisplay.setDisplayService(ogl_display)

    Graphline(reader=ConsoleReader(prompt="Enter torrent location:", eol=""),
              httpclient=SimpleHTTPClient(),
              gui=TorrentOpenGLGUI(),
              backend=TorrentPatron(),
              linkages={
                  ("gui", "outbox"): ("backend", "inbox"),
                  ("reader", "outbox"): ("gui", "torrent_url"),
                  ("gui", "fetcher"): ("httpclient", "inbox"),
                  ("httpclient", "outbox"): ("gui", "torrent_file"),
                  ("backend", "outbox"): ("gui", "inbox")
              }).run()