示例#1
0
    def __init__(self, graph=None, camera=None, orthocamera=None, netcli=False):
        print "Calling graph context init"
        #
        # Cacheable: Keep track of observers
        #
        self.__dict__['observers'] = []
        if not config.current['global:highlight']:
            self.highLight = False
        else:
            self.highLight = True
        self.highLightNow = True
        self.ready = False

        if not graph:
            print "Making a graph object"
            self.graph = Graph(parent=self)
        else:
            self.graph = graph

        if netcli:
            self.UndoStack = undo.DummyStack(self)
            self.RedoStack = undo.DummyStack(self)
        else:
            self.UndoStack = undo.UndoStack(self)
            self.RedoStack = undo.RedoStack(self)

        self.graphgl = graphgl.GraphGL(self.graph)
        self.decor = interfacegl.InterfaceGL()

        if not camera:
            self.camera = Camera()
            self.camera.lookAt = (0.0, 0.0, 0.0)
        else:
            self.camera = camera

        if not orthocamera:
            self.orthocamera = OrthoCamera()
        else:
            self.orthocamera = orthocamera

        self.cursor = Cursor()

        self.path = None

        # This should default to False and turn on automatically
        # when you have a 3D input device.
        # XXX#
        self.showCursor = False

        # The drawList should be a dictionary of Drawables,
        # used by the InputManager to enable transient objects,
        # menus, etc to be drawn.
        self.drawList = {}

        self.orthoMode = None
        self.showAxes = config.current['default:show-axes']
        self.showLocalAxes = config.current['default:show-local-axes']
        self.showHUD = config.current['default:show-hud']
        self.showGrid = config.current['default:show-grid']
        self.showGridLines = config.current['default:show-grid-lines']
        self.showPlane = config.current['default:show-plane']
        self.planeColor = config.current['default:plane-color']
        self.bgColor = config.current['default:background-color']
        self.showVertexLabels = config.current['default:show-vertex-labels']
        self.showDummyLabels = config.current['default:show-dummy-labels']
        self.vertexLabelName = config.current['default:vertex-label-name']
        self.vertexLabelColor = config.current['default:vertex-label-color']
        self.vertexShadowColor = config.current['default:vertex-shadow-color']
        self.vertexLabelStroke = config.current['default:vertex-label-stroke']

        self.showGridPlane = "XZ"
        self.pos = (0.0, 0.0, 0.0)
        self.sizeX = 5
        self.sizeY = 5
        self.sizeZ = 5
        self.spacing = 2.0

        # The HUD status text line.
        self.HUDstatus = ''


        #If no highlight object exists, create one.
        if self.highLight:
            print "Creating highlight for active context"
            self.highLight = True
            from interfacegl import DrawObjectHighLight

            self.setDraw('wandaHighLight',
                         DrawObjectHighLight(
                             ctx=self,
                             tolerance=5.0,
                             restrict=None,
                         )
            )
示例#2
0
class GraphContext(pb.Cacheable):
    """
    GraphContext binds together the elements required to present a Graph
    in an OpenGL environment. It includes:
	- The graph
	- The primary camera
	- The orthogonal camera
	- Display settings:
	    - ortho vs. standard camera
	    - whether to show local axes
	    - whether to show ground plane
	    - whether to show HUD
	    - whether to show, and color of, ground plane
	    - color of background
	    - whether to show vertex labels (IDs or names) and whether to show labels for bendpoints
	    - colors for vertex labels and their shadows, and the stroke width for drawing labels
    """

    def __setattr__(self, name, value):
        # Don't update the remote 'ready' state; it's a local flag.
        if name != 'observers' and name != 'ready':
            for o in self.observers:
                o.callRemote('setattr', {name: value}).addErrback(connectionError, 'context setattr')
        self.__dict__[name] = value

    def __init__(self, graph=None, camera=None, orthocamera=None, netcli=False):
        print "Calling graph context init"
        #
        # Cacheable: Keep track of observers
        #
        self.__dict__['observers'] = []
        if not config.current['global:highlight']:
            self.highLight = False
        else:
            self.highLight = True
        self.highLightNow = True
        self.ready = False

        if not graph:
            print "Making a graph object"
            self.graph = Graph(parent=self)
        else:
            self.graph = graph

        if netcli:
            self.UndoStack = undo.DummyStack(self)
            self.RedoStack = undo.DummyStack(self)
        else:
            self.UndoStack = undo.UndoStack(self)
            self.RedoStack = undo.RedoStack(self)

        self.graphgl = graphgl.GraphGL(self.graph)
        self.decor = interfacegl.InterfaceGL()

        if not camera:
            self.camera = Camera()
            self.camera.lookAt = (0.0, 0.0, 0.0)
        else:
            self.camera = camera

        if not orthocamera:
            self.orthocamera = OrthoCamera()
        else:
            self.orthocamera = orthocamera

        self.cursor = Cursor()

        self.path = None

        # This should default to False and turn on automatically
        # when you have a 3D input device.
        # XXX#
        self.showCursor = False

        # The drawList should be a dictionary of Drawables,
        # used by the InputManager to enable transient objects,
        # menus, etc to be drawn.
        self.drawList = {}

        self.orthoMode = None
        self.showAxes = config.current['default:show-axes']
        self.showLocalAxes = config.current['default:show-local-axes']
        self.showHUD = config.current['default:show-hud']
        self.showGrid = config.current['default:show-grid']
        self.showGridLines = config.current['default:show-grid-lines']
        self.showPlane = config.current['default:show-plane']
        self.planeColor = config.current['default:plane-color']
        self.bgColor = config.current['default:background-color']
        self.showVertexLabels = config.current['default:show-vertex-labels']
        self.showDummyLabels = config.current['default:show-dummy-labels']
        self.vertexLabelName = config.current['default:vertex-label-name']
        self.vertexLabelColor = config.current['default:vertex-label-color']
        self.vertexShadowColor = config.current['default:vertex-shadow-color']
        self.vertexLabelStroke = config.current['default:vertex-label-stroke']

        self.showGridPlane = "XZ"
        self.pos = (0.0, 0.0, 0.0)
        self.sizeX = 5
        self.sizeY = 5
        self.sizeZ = 5
        self.spacing = 2.0

        # The HUD status text line.
        self.HUDstatus = ''


        #If no highlight object exists, create one.
        if self.highLight:
            print "Creating highlight for active context"
            self.highLight = True
            from interfacegl import DrawObjectHighLight

            self.setDraw('wandaHighLight',
                         DrawObjectHighLight(
                             ctx=self,
                             tolerance=5.0,
                             restrict=None,
                         )
            )

    def getStateToCacheAndObserveFor(self, perspective, observer):
        """
        Add an observer to the list, and return our current
        state with the observer list stripped out.
        """
        print "adding observer to GraphContext:", perspective, observer
        self.observers.append(observer)

        d = self.__dict__.copy()
        # XXX# What other Cacheable-specific data do we have to remove?
        if 'parent' in d:
            d['parent'] = None
        del d['observers']
        del d['graphgl']
        del d['decor']
        del d['UndoStack']
        del d['RedoStack']
        return d

    def stoppedObserving(self, perspective, observer):
        """
        Remove an observer from the list.
        """
        self.observers.remove(observer)
        print "stopped observing:", perspective, observer

    def init(self):
        """
        Should be called the first time this context is drawn, after OpenGL
        has been set up. It ensures that the initial viewpoint reflects
        the initial camera.
        """
        glEnable(GL_COLOR_MATERIAL)
        glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE)
        glEnable(GL_LIGHT0)

        if self.orthoMode:
            self.orthocamera.use()
        else:
            self.camera.use()

            # glLightfv(GL_LIGHT0, GL_POSITION, (-0.1, 1.0, 0.1, 0.0))

        self.graphgl.init()
        self.decor.init()
        self.ready = True

    def loadFromFile(self, filename, format, queryCallback=None, warningCallback=None):
        """Loads a graph into this context. Sets up defaults for ground plane
           and camera."""

        filepath = path.realpath(filename)
        (dir, basename) = path.split(filepath)
        (prefix, ext) = path.splitext(basename)

        fobj = file(filepath, 'rU')
        format.load(self.graph, fobj, queryCallback=queryCallback, warningCallback=warningCallback)
        fobj.close()

        self.graph.new = False
        self.graph.change(False)
        # Set this to force a rendering update of the graph - maybe there's
        # a better way to do it? It hasn't been changed (for saving purposes)
        # but the graph object itself has changed (for display purposes).
        self.graph.dirty = True

        # Store the path
        self.path = filename

        # These should be user preferences.Cursor()
        self.showAxes = config.current['default:show-axes']
        self.showLocalAxes = config.current['default:show-local-axes']
        self.showPlane = config.current['default:show-plane']
        self.showHUD = config.current['default:show-hud']
        self.showGrid = config.current['default:show-grid']
        self.showGridLines = config.current['default:show-grid-lines']
        self.planeColor = config.current['default:plane-color']
        self.bgColor = config.current['default:background-color']
        self.showVertexLabels = config.current['default:show-vertex-labels']
        self.showDummyLabels = config.current['default:show-dummy-labels']
        self.vertexLabelName = config.current['default:vertex-label-name']
        self.vertexLabelColor = config.current['default:vertex-label-color']
        self.vertexShadowColor = config.current['default:vertex-shadow-color']
        self.vertexLabelStroke = config.current['default:vertex-label-stroke']
        self.orthoMode = None

        # Cacheable: Send all these parameters across the wire
        if self.observers:
            d = self.__dict__.copy()
            del d['observers']
            del d['graphgl']
            del d['decor']
            for o in self.observers: o.callRemote('setattr', d).addErrback(connectionError, 'context loadfromfile')
            # Update the graph edge and vertex lists.
            for o in self.graph.observers:
                o.callRemote('setattr', {
                'vertices': self.graph.vertices,
                'vertices_selected': self.graph.vertices_selected,
                'edges': self.graph.edges,
                'edges_selected': self.graph.edges_selected,
                })
                o.callRemote('change', True)

        self.camera.lookAtGraph(self.graph, self.graph.centerOfMass(), offset=self.graph.viewpoint())

    def writeToFile(self, format=None, filename=None, options={}):
        """GraphContext.writeToFile(filename = None) -> void
           Writes the graph in this context out to a file, autodetecting
           the filetype from the name of the file. If filename == None,
           will attempt to use self.path. If self.path == None and
           filename == None, raises ExportError."""
        # Detect what kind of file we're loading and
        # call a specialized loader
        if filename == None or format == None:
            if not self.path == None:
                filename = self.path
                filepath = path.realpath(filename)
                (dir, basename) = path.split(filepath)
                (prefix, ext) = path.splitext(basename)
                if not ext == '.mg' and not ext == '.mg2':
                    raise ExportError, "Couldn't save into exported type."
                format = formatmanager.formats[ext[1:]]
            else:
                raise ExportError, "Couldn't save new file without filename."

        filepath = path.realpath(filename)
        format.write(self.graph, filepath, options=options)
        self.path = filename

        self.graph.change(False)

    def exportToFile(self, filename, format, options={}):
        """GraphContext.exportToFile(filename) -> void
           Writes the graph in this context out to a file, autodetecting
           the filetype from the name of the file. Will not change the path
           or reset the changed flag."""
        filepath = path.realpath(filename)
        format.write(self.graph, filepath, options=options)

    def writeToPOVRay(self, filename, options={}):
        """Draw the graph to a .pov POVRay file

        Arguments:
        filename -- name of the file with path and extension
        options -- a dictionary of POVRay options

        """
        filepath = path.realpath(filename)
        if 'skyColor' not in options:
            options['skyColor'] = self.bgColor
        if 'planeColor' not in options:
            options['planeColor'] = self.planeColor
        if self.orthoMode:
            formatmanager.formats['pov'].write(self.graph, filepath,
                                               self.orthocamera,
                                               options=options)
        else:
            formatmanager.formats['pov'].write(self.graph, filepath,
                                               self.camera,
                                               options=options)

    def revert(self):
        """GraphContext.revert() -> void
           Reloads a context from the last saved state.
           Raises ImportError if there is no last saved state."""
        if self.path == None:
            raise ImportError, "Can't revert an unsaved context."
        filepath = path.realpath(self.path)
        (dir, basename) = path.split(filepath)
        (prefix, ext) = path.splitext(basename)

        self.graph.clear()
        self.loadFromFile(self.path, formatmanager.formats[ext[1:]])

    def setDraw(self, slot, drawable):
        self.drawList[slot] = drawable
        for o in self.observers:
            o.callRemote('setattr', {'drawList': self.drawList}).addErrback(connectionError, 'context setDraw')

    def delDraw(self, slot):
        if slot in self.drawList:
            del self.drawList[slot]
            for o in self.observers:
                o.callRemote('setattr', {'drawList': self.drawList}).addErrback(connectionError, 'context removeDraw')

    def getDraw(self, slot):
        return self.drawList[slot]

    def draw(self, edgeNameBase=None, vertNameBase=None):
        """Draw the elements of this context on the current GL canvas."""

        if not self.ready:
            self.init()

        # Set up the camera
        if self.orthoMode:
            activecam = self.orthocamera
        else:
            activecam = self.camera

        activecam.use()
        # Apply the tracker offsets.
        activecam.useTracker()

        if self.graph.vertices_selected:
            orbitCentre = self.graph.vertices_selected[0]
        else:
            orbitCentre = self.graph.centerOfMass()
        # Multiply points if snapping to grid
        if self.showGrid:
            ocp = utilities.mult3D(orbitCentre.pos, self.spacing)
        else:
            ocp = orbitCentre.pos

        if config.current['global:camera-orbit'] == "cw":
            self.camera.absorbit(ocp, Y_AXIS, -1)
        elif config.current['global:camera-orbit'] == "ccw":
            self.camera.absorbit(ocp, Y_AXIS, 1)


        # Only draw decorations if we're in render mode - not in
        # selection mode.
        if glGetInteger(GL_RENDER_MODE) == GL_RENDER:
            # Draw the ground plane if it's enabled, but not when we're
            # using ortho mode
            glEnable(GL_CULL_FACE)
            if self.showPlane and not self.orthoMode:
                self.decor.drawGroundPlane(height=self.graph.findGroundPlane(), color=self.planeColor,
                                           reference=self.camera.pos)
        #
        # Draw the graph
        #
        glEnable(GL_TEXTURE_2D)
        self.graphgl.draw(edgeNameBase=edgeNameBase, vertNameBase=vertNameBase)

        #
        # Draw A grid if the user wants one
        #
        if self.showGridLines:
            self.decor.drawGrid(parent=self, pos=self.pos, sizeX=self.sizeX, sizeY=self.sizeY, sizeZ=self.sizeZ,
                                spacing=self.spacing)

        # Only draw decorations if we're in render mode - not in
        # selection mode.
        if glGetInteger(GL_RENDER_MODE) == GL_RENDER:
            glEnable(GL_CULL_FACE)
            # Draw the coordinate axes if they're enabled
            if self.showAxes:
                self.decor.drawAxes()

                # Draw local axes if required
                # print "window:",wx.Window.FindFocus(), "\n canvas:", self.parent.canvas
            showA = False
            if not self.parent == None:
                if wx.Window.FindFocus() == self.parent.canvas:
                    showA = True

            if self.showLocalAxes and showA:
                localAxes = (X_AXIS, Y_AXIS, Z_AXIS)
                labels = ('Q', 'A', 'W', 'S', 'E', 'D')
                if self.orthoMode:
                    if self.orthoMode[0] == X_AXIS:
                        localAxes = (Y_AXIS, Z_AXIS)
                        if self.orthoMode[1] > 0:
                            labels = (None, None, 'W', 'S', 'D', 'A')
                        else:
                            labels = (None, None, 'W', 'S', 'A', 'D')
                    elif self.orthoMode[0] == Y_AXIS:
                        localAxes = (X_AXIS, Z_AXIS)
                        if self.orthoMode[1] > 0:
                            labels = ('A', 'D', None, None, 'S', 'W')
                        else:
                            labels = ('D', 'A', None, None, 'S', 'W')
                    elif self.orthoMode[0] == Z_AXIS:
                        localAxes = (X_AXIS, Y_AXIS)
                        if self.orthoMode[1] > 0:
                            labels = ('A', 'D', 'W', 'S', None, None)
                        else:
                            labels = ('D', 'A', 'W', 'S', None, None)

                for v in self.graph.vertices_selected:
                    if v.hidden:
                        continue
                    self.decor.draw_localaxes(self, v, axes=localAxes, labels=labels)

            # Draw vertex labels if selected.
            if self.showVertexLabels:
                if self.showDummyLabels:
                    vxlist = self.graph.vertices
                else:
                    vxlist = [x for x in self.graph.vertices if not isinstance(x, DummyVertex)]
                if len(vxlist) > 0:
                    if self.vertexLabelName:
                        labelFunc = lambda v: str(v.name)
                    else:
                        labelFunc = lambda v: str(v.id)
                    self.decor.drawVertexLabels(parent=self, vxlist=vxlist, labelFunc=labelFunc,
                                                labelStrokeWidth=self.vertexLabelStroke,
                                                labelColor=self.vertexLabelColor, shadowColor=self.vertexShadowColor)

            if self.showHUD:
                if self.showCursor:
                    self.decor.drawHUD(activecam, self.graph.vertices_selected, pos=self.cursor.realPosition(activecam),
                                       status=self.HUDstatus)
                else:
                    self.decor.drawHUD(activecam, self.graph.vertices_selected, status=self.HUDstatus)

            if self.showCursor:
                # First, draw the local grid around the cursor position.
                #		self.decor.drawLocalGrid(pos = self.cursor.realPosition(activecam), size=1, spacing=5.0, snap=5.0)
                glEnable(GL_CULL_FACE)
                glDisable(GL_TEXTURE_2D)
                glDisable(GL_POLYGON_SMOOTH)

                self.decor.drawCursor(camera=activecam, cursor=self.cursor)

            for d in self.drawList.values():
                d.draw()

    def goPersp(self):
        """
        Restore and unlock the perspective camera in this context.
        """
        self.orthoMode = None
        for o in self.observers: o.callRemote('setOrtho', None).addErrback(connectionError, 'abc')

    def goOrtho(self, axis, look):
        """
        Align and lock in this context for orthographic movement.
        axis and side specify the alignment of the camera.

        eg. goOrtho(Y_AXIS, -1) will place the camera on the Y axis
        looking in the negative direction with movement restricted
        to the XZ plane.
        """
        self.orthoMode = (axis, look)
        for o in self.observers: o.callRemote('setOrtho', self.orthoMode).addErrback(connectionError, 'abc')
        self.orthocamera.setOnAxis(axis, look * -20.0)

    def vertexWarp(self, vertex, refresh=None):
        """
        Warp to vertex, using the Animator.
        """
        from utilities import add3D, diff3D, mult3D

        offset = add3D(mult3D(self.camera.vpn, -10.0), (0.0, vertex.radius * 3.0, 0.0))

        if self.showGrid:
            npos = utilities.mult3D(vertex.pos, self.spacing)
        else:
            npos = vertex.pos

        targetpos = add3D(npos, offset)

        self.parent.animator.perspCameraMove(self.camera, newPos=targetpos, newLook=npos, duration=1.0)

    def lookAt(self, target, refresh=None):
        """
        Re-point the current camera to look at some object in the graph.
        Target can be:
        - a position in 3-space expressed as a 3-tuple of floats
        - a Vertex object
        - an Edge object (will look at the midpoint)
        """

        if isinstance(target, Vertex):
            if self.showGrid:
                tgt = target
                tgt.pos = utilities.mult3D(target.pos, self.spacing)
            else:
                tgt = target
        elif isinstance(target, Edge):
            if self.showGrid:
                tgt = target
                tgt.source.pos = utilities.mult3D(target.source.pos, self.spacing)
                tgt.target.pos = utilities.mult3D(target.target.pos, self.spacing)
            else:
                tgt = target
        elif self.showGrid:
            tgt = utilities.mult3D(target, self.spacing)
        else:
            tgt = target

        if self.orthoMode:
            # XXX# Do something
            pass
        else:
            self.parent.animator.perspCameraMove(
                camera=self.camera,
                newLook=tgt,
                duration=1.2,
            )