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