diff --git a/pykat/components.py b/pykat/components.py index a9bd94f737d820d93a88bf62f7e771ceb2d1b8e1..c900875c2fdb729fe4e9dc42cad1d112f63cd5b9 100644 --- a/pykat/components.py +++ b/pykat/components.py @@ -8,7 +8,7 @@ import exceptions import pykat.gui.resources import pykat import inspect - +import pykat.gui.graphics from pykat.gui.graphics import * from pykat.node_network import * from PyQt4.QtGui import * @@ -40,9 +40,10 @@ class Component(object) : n = self._kat.nodes.createNode(name) if n == None: - raise exceptions.RuntimeError("getNode did not return a node for '{0}'".format(name)) + raise exceptions.RuntimeError("createNode did not return a node for '{0}'".format(name)) else: - n.connect(self) + n.connect(self) + self.__nodes.append(n) return n @@ -143,12 +144,11 @@ class mirror(Component): def getQGraphicsItem(self): if self._svgItem == None: - self._svgItem = ComponentQGraphicsItem(":/resources/mirror_flat.svg",self + self._svgItem = pykat.gui.graphics.ComponentQGraphicsItem(":/resources/mirror_flat.svg",self ,[(-4,15,self.node1),(14,15,self.node2)]) return self._svgItem - class space(Component): def __init__(self,kat , name, node1, node2, L=0, n=1): Component.__init__(self,name,kat) @@ -177,11 +177,25 @@ class space(Component): def getQGraphicsItem(self): if self._QItem == None: - self._QItem = SpaceQGraphicsItem(self) + self._QItem = pykat.gui.graphics.SpaceQGraphicsItem(self) return self._QItem - + def changeNode(self, node_old, node_new): + ''' + Called when a space's node has been connected + to another components node + ''' + node_new.connect(self) + node_old.disconnect(self) + + if self.node1 == node_old: + self.node1 = node_new + + if self.node2 == node_old: + self.node2 = node_new + + class laser(Component): def __init__(self,kat,name,node,P=1,f_offset=0,phase=0): Component.__init__(self,name,kat) @@ -215,7 +229,7 @@ class laser(Component): def getQGraphicsItem(self): if self._svgItem == None: - self._svgItem = ComponentQGraphicsItem(":/resources/laser.svg", + self._svgItem = pykat.gui.graphics.ComponentQGraphicsItem(":/resources/laser.svg", self,[(65,25,self.node)]) return self._svgItem diff --git a/pykat/detectors.py b/pykat/detectors.py index 85c9ec79392ac25ee4e88f3d2f8707c9a1aef08c..04747fcca963dee7b836cc10a27422db1879ab43 100644 --- a/pykat/detectors.py +++ b/pykat/detectors.py @@ -11,8 +11,6 @@ from pykat.utils import * from pykat.gui.graphics import * from pykat.node_network import * -from PyQt4.QtGui import * -from PyQt4.Qt import * class Detector(object) : def __init__(self, name,node,kat): diff --git a/pykat/finesse.py b/pykat/finesse.py index 3e6cddbc13ab8ba54ed5b6e84d0f693faaa650d1..d81dbcb1c22e8a34b2a414ad8e4333d2a6fa107a 100644 --- a/pykat/finesse.py +++ b/pykat/finesse.py @@ -10,7 +10,6 @@ import subprocess import tempfile import numpy as np import datetime -#from colorama import Fore import pickle from pykat.node_network import NodeNetwork @@ -85,7 +84,7 @@ class kat(object): def noxaxis(self): return self.__noxaxis @noxaxis.setter def noxaxis(self,value): self.__noxaxis = bool(value) - + def run(self, printout=1, printerr=1, save_output=False, save_kat=False,kat_name=None) : """ Runs the current simulation setup that has been built thus far. @@ -287,6 +286,44 @@ class kat(object): def getComponents(self): return self.__components.values() + def hasComponent(self, name): + return (name in self.__components) + + def getNewComponentName(self,prefix): + ''' + Returns a name for a component which hasn't already been added. + Returns [prefix] + number, where number is greater than 1. e.g. + if m1 exists getNewName('m') will return 'm2' + ''' + n = 1 + name = "{0}{1}".format(prefix, n) + + while name in self.__components: + n += 1 + name = "{0}{1}".format(prefix,n) + + return name + + def getNewNodeNames(self,prefix,N=1): + ''' + Returns a list of names for N number of nodes which haven't already been added. + Returns [prefix] + number, where number is greater than 1. e.g. + if m1 exists getNewName('m') will return 'm2' + ''' + rtn = [] + n = 1 + + for M in range(1,N+1): + name = "{0}{1}".format(prefix, n) + + while name in self.nodes.getNodes() or (name in rtn): + n += 1 + name = "{0}{1}".format(prefix,n) + + rtn.append(name) + + return rtn + def __add_detector(self, det): diff --git a/pykat/gui/graphics.py b/pykat/gui/graphics.py index fef8789df576c02f2df26c402fb54de8eb080862..6e95ae3f75f40e3cc885a4a9830a2df8b2eb2231 100644 --- a/pykat/gui/graphics.py +++ b/pykat/gui/graphics.py @@ -8,21 +8,37 @@ Created on Fri Feb 01 09:13:03 2013 from PyQt4.QtGui import * from PyQt4.Qt import * import pykat.components +import exceptions nsize = 8 - + class NodeQGraphicItem(QGraphicsRectItem): def __init__(self, node, x,y, *args, **kwargs): QGraphicsRectItem.__init__(self, *args, **kwargs) self.__node = node - self.setPos(x,y) item = QGraphicsTextItem(node.name, self) rect = item.boundingRect() item.setPos(-0.5*rect.width(), 0) + self.setAcceptHoverEvents(True) + + self.marked = False + + @property + def node(self): return self.__node + + def refresh(self): + if not self.marked: + if self.__node.isConnected(): + self.setBrush(QBrush(Qt.red)) + else: + self.setBrush(QBrush(Qt.green)) + else: + self.setBrush(QBrush(Qt.yellow)) + class SpaceQGraphicsItem(QGraphicsLineItem): def __init__(self, spaceComponent): QGraphicsLineItem.__init__(self) @@ -36,18 +52,21 @@ class SpaceQGraphicsItem(QGraphicsLineItem): self.refresh() + @property + def space(self): return self.__space + def refresh(self): nodes = self.__space.getNodes() - - if self.__n1 == None: - self.__n1 = NodeQGraphicItem(nodes[0],0,0,-nsize/2,-nsize/2,nsize,nsize,self) - self.__n1.setPen(QPen(Qt.black)) conn = nodes[0].amIConnected(self.__space) if conn[0]: if conn[1] != None: - self.__n1.setVisible(False) + if self.__n1 is not None: + # i.e. we have a node graphic item but now it is connected to something + self.__n1.scene().removeItem(self.__n2) + self.__n1 = None + # now check if a connected component was returned too if conn[1] != None: # so this node should be attached to something @@ -57,20 +76,26 @@ class SpaceQGraphicsItem(QGraphicsLineItem): x1 = itm.x() + itm.nodedx[conn[2]][0] y1 = itm.y() + itm.nodedx[conn[2]][1] else: + if self.__n1 == None: + self.__n1 = NodeQGraphicItem(nodes[0],0,0,-nsize/2,-nsize/2,nsize,nsize,self) + self.__n1.setPen(QPen(Qt.black,1)) + self.__n1.setVisible(True) - self.__n1.setBrush(QBrush(Qt.red)) - x1 = 0 - y1 = 0 - - if self.__n2 == None: - self.__n2 = NodeQGraphicItem(nodes[1],0,0,-nsize/2,-nsize/2,nsize,nsize,self) - self.__n2.setPen(QPen(Qt.black)) + self.__n1.setBrush(QBrush(Qt.green)) + p = self.__n1.pos() + x1 = self.x()+p.x() + y1 = self.y()+p.y() conn = nodes[1].amIConnected(self.__space) if conn[0]: if conn[1] != None: - self.__n2.setVisible(False) + + if self.__n2 is not None: + # i.e. we have a node graphic item but now it is connected to something + self.__n2.scene().removeItem(self.__n2) + self.__n2 = None + # now check if a connected component was returned too if conn[1] != None: # so this node should be attached to something @@ -80,32 +105,40 @@ class SpaceQGraphicsItem(QGraphicsLineItem): x2 = itm.x() + itm.nodedx[conn[2]][0] y2 = itm.y() + itm.nodedx[conn[2]][1] else: + if self.__n2 == None: + self.__n2 = NodeQGraphicItem(nodes[1],0,0,-nsize/2,-nsize/2,nsize,nsize,self) + self.__n2.setPen(QPen(Qt.black,1)) + self.__n2.setVisible(True) - self.__n2.setBrush(QBrush(Qt.red)) + self.__n2.setBrush(QBrush(Qt.green)) p = self.__n2.pos() - x2 = -p.x() - y2 = -p.y() - + x2 = self.x()+p.x() + y2 = self.y()+p.y() + + # convert x1,y1,x2 and y2 into the local coordinates of the + # space object p = QPointF((x1-x2)*0.5,(y1-y2)*0.5) self.setPos(x1 - p.x(), y1 - p.y()) # if the nodes are visible then reposition them in the # component reference frame - if self.__n1.isVisible(): + if self.__n1 is not None and self.__n1.isVisible(): self.__n1.setPos(QPointF(p.x(),p.y())) + self.__n1.refresh() - if self.__n2.isVisible(): + if self.__n2 is not None and self.__n2.isVisible(): self.__n2.setPos(QPointF(p.x()+x2-x1, p.y()+y2-y1)) + self.__n2.refresh() self.setLine(p.x(), p.y(), p.x()+x2-x1, p.y()+y2-y1) self.setPen(QPen(Qt.red, 3)) - + class ComponentQGraphicsItem(QGraphicsSvgItem): def __init__(self, svgfile, component, nodes): QGraphicsSvgItem.__init__(self,svgfile) - + self.__nodeGraphics = [] self.__component = component # this signals the itemChange() method when this item is moved # used for refreshing the spaces between components @@ -116,11 +149,25 @@ class ComponentQGraphicsItem(QGraphicsSvgItem): rect = item.boundingRect() item.setPos(-0.5*rect.width(),40-0.5*rect.height()) + self.setAcceptsHoverEvents(True) + for n in nodes: self.nodedx.append([n[0],n[1]]) - node = NodeQGraphicItem(n[2],n[0],n[1],-nsize/2,-nsize/2,nsize,nsize,self) - node.setBrush(QBrush(Qt.red)) + node = n[2].getQGraphicsItem(n[0],n[1],nsize,self) node.setPen(QPen(Qt.black)) + node.refresh() + self.__nodeGraphics.append(node) + + self.refresh() + self.installEventFilter(self) + self.setHandlesChildEvents(True) + + @property + def component(self): return self.__component + + def refresh(self): + for n in self.__nodeGraphics: + n.refresh() def itemChange(self, change, value): # if the item move then update any spaces diff --git a/pykat/gui/gui.py b/pykat/gui/gui.py index 7e64e331b435733570c21ad099fa8c399b234422..eecad83bcd4147ff3c9027e8dcaa416e73777899 100644 --- a/pykat/gui/gui.py +++ b/pykat/gui/gui.py @@ -13,13 +13,15 @@ from PyQt4.Qt import * from PyQt4.QtGui import QCursor, QGraphicsItem from pykat.gui.graphics import * import qt_gui - +import functools + def openGUI(kat): app = QtGui.QApplication([""]) pykatgui = pyKatGUI(kat) pykatgui.main() app.exec_() - + + class pyKatGUI(QtGui.QMainWindow, qt_gui.Ui_MainWindow): def __init__(self, kat,parent=None): super(pyKatGUI, self).__init__(parent) @@ -28,6 +30,9 @@ class pyKatGUI(QtGui.QMainWindow, qt_gui.Ui_MainWindow): self.graphicsView = pyKatGraphicsView(self.centralwidget) self.graphicsView.setObjectName("graphicsView") self.graphicsView.setViewportUpdateMode(QGraphicsView.FullViewportUpdate) + self.graphicsView.viewport().setMouseTracking(True) + self.setMouseTracking(True) + self.gridLayout.addWidget(self.graphicsView, 0, 0, 1, 1) # create a new scene @@ -43,7 +48,7 @@ class pyKatGUI(QtGui.QMainWindow, qt_gui.Ui_MainWindow): self.actionExport_to_SVG.triggered.connect(lambda: self.exportToSVG()) self.actionClose.triggered.connect(lambda: self.close) - self._kat = kat + self.kat = kat def main(self): self.show() @@ -55,23 +60,32 @@ class pyKatGUI(QtGui.QMainWindow, qt_gui.Ui_MainWindow): def addComponentsToScene(self): - for c in self._kat.getComponents(): - itm = c.getQGraphicsItem() - - if itm != None: - itm.setPos(0,0) - # uncomment this line to stop background bitmap caching of - # svg rendering. Important to make sure when rendering to - # svg file that it is in a vector format. Gradients however - # don't work... - itm.setCacheMode(QGraphicsItem.NoCache) - self.__scene.addItem(itm) + for c in self.kat.getComponents(): + self.addComponentToScene(c) + def addComponentToScene(self,c,x=0,y=0): + itm = c.getQGraphicsItem() + + if itm != None: + itm.setPos(x,y) + # uncomment this line to stop background bitmap caching of + # svg rendering. Important to make sure when rendering to + # svg file that it is in a vector format. Gradients however + # don't work... + itm.refresh() + itm.setCacheMode(QGraphicsItem.NoCache) + self.__scene.addItem(itm) + def exportToSVG(self): self.statusbar.showMessage("Saving to 'output.svg'...") svg = QSvgGenerator() - svg.setFileName("./output.svg") + filename = QtGui.QFileDialog.getSaveFileNameAndFilter(self,'Save SVG File',filter=".svg") + + if filename == None: + return None + + svg.setFileName(filename) svg.setSize(QSize(self.__scene.width(), self.__scene.height())) svg.setViewBox(QRect(0,0,self.__scene.width(), self.__scene.height())) svg.setTitle("pyKat output of example.kat") @@ -81,6 +95,13 @@ class pyKatGUI(QtGui.QMainWindow, qt_gui.Ui_MainWindow): pntr.end() self.statusbar.showMessage("Complete: Saved to 'output.svg'") + + def addMirror(self, x,y): + name = self.kat.getNewComponentName('m') + n = self.kat.getNewNodeNames('n',2) + m = pykat.components.mirror(self.kat,name,n[0],n[1]) + + self.addComponentToScene(m,x,y) class pyKatGraphicsScene(QGraphicsScene): def drawBackground(self, painter, rect): @@ -115,21 +136,31 @@ class pyKatGraphicsView(QGraphicsView): self.__selected_item = None self.__prev_pt = None - + self.setMouseTracking(True) + self.__itemHover = None + self.__marked = None + def contextMenuEvent(self, ev): pt = self.mapToScene(ev.pos()) + gui = self.parentWidget().parent() # get the main gui window + menu = QMenu(self) addmenu = menu.addMenu("Add...") - addmenu.addAction("Mirror") + + action = addmenu.addAction("Mirror") + action.triggered.connect(functools.partial(gui.addMirror,pt.x(),pt.y())) + addmenu.addAction("Laser") addmenu.addAction("Beamsplitter") addmenu.addAction("Photodiode") - item = self.itemAt(pt.x(),pt.y()) + item = self.scene().itemAt(pt.x(),pt.y()) - if item != None : - if isinstance(item, Component): + print pt.x(),pt.y(),item + + if item is not None : + if isinstance(item, ComponentQGraphicsItem): menu.addSeparator() menu.addAction("Edit") menu.addAction("Delete") @@ -157,22 +188,52 @@ class pyKatGraphicsView(QGraphicsView): self.__selected_item = item self.__prev_pt = pt elif isinstance(item, NodeQGraphicItem): - if item == None: - self.__selected_item = None - self.__prev_pt = None - else: - if isinstance(item.parentItem(),SpaceQGraphicsItem): - self.__selected_item = item - self.__prev_pt = pt + + + if isinstance(item.parentItem(),SpaceQGraphicsItem): + self.__selected_item = item + self.__prev_pt = pt + elif isinstance(item.parentItem(),ComponentQGraphicsItem): + self.__selected_item = item.parentItem() + self.__prev_pt = pt + + if self.__selected_item is not None: + self.setCursor(QCursor(Qt.ClosedHandCursor)) + def mouseReleaseEvent(self, ev): + # if we have dragged a node and marked another to connect it too + if self.__selected_item is not None and isinstance(self.__selected_item, NodeQGraphicItem) and self.__marked is not None: + # node attached to space which needs to be removed + node_s = self.__selected_item.node + + # get the selected node which must be attached to a space, which is the nodes parent + # and then get the space component from it + qspace = self.__selected_item.parentItem() + space = qspace.space + + # marked node, then get the parent object and component + node_c = self.__marked.node + qcomp = self.__marked.parentItem() + + # connect space of node dragged to the component node + space.changeNode(node_s,node_c) + node_s.remove() + + # then refresh the graphical items + qspace.refresh() + qcomp.refresh() + + if self.__marked is not None: + self.__marked.marked = False + self.__marked.refresh() + self.__marked = None + self.__selected_item = None self.setCursor(QCursor(Qt.ArrowCursor)) - pass - + def mouseMoveEvent(self, ev): if self.__selected_item != None: - self.setCursor(QCursor(Qt.ClosedHandCursor)) item = self.__selected_item #pt_ = self.__prev_pt @@ -183,9 +244,60 @@ class pyKatGraphicsView(QGraphicsView): # then snap to some integer value snap = 10.0 - - item.setPos(int(round(pt.x()/snap)*snap),int(round(pt.y()/snap)*snap)) + # if we are moving a node, it must be attached to a space + # component otherwise we shouldn't be here + if isinstance(item, NodeQGraphicItem) and isinstance(item.parentItem(), SpaceQGraphicsItem): + space = item.parentItem() + + item.setPos(pt.x()-space.x(),pt.y()-space.y()) + space.refresh() + + # now check to see if any other connectable nodes are within reach + # and if so hightlight them + select_size = 20 + rect = QRectF(pt.x()-select_size/2,pt.y()-select_size/2,select_size,select_size) + itms = item.scene().items(rect) + + # remove the node we are dragging + if item in itms: + itms.remove(item) + + if self.__marked is not None: + self.__marked.marked = False + self.__marked.refresh() + self.__marked = None + + if len(itms) > 0: + for i in itms: + if isinstance(i,NodeQGraphicItem) and i != item: + i.marked = True + i.refresh() + self.__marked = i + break + else: + item.setPos(int(round(pt.x()/snap)*snap),int(round(pt.y()/snap)*snap)) + self.__prev_pt = pt + return + + else: + item = self.itemAt(ev.pos()) + + if isinstance(item, (NodeQGraphicItem, ComponentQGraphicsItem)) or item is None: + #if item is not None or self.__itemHover is not None: + + if isinstance(item, ComponentQGraphicsItem): + self.__itemHover = item + elif isinstance(item,NodeQGraphicItem) and (not item.node.isConnected()): + self.__itemHover = item + else: + self.__itemHover = None + + if self.__itemHover is not None: + self.setCursor(QCursor(Qt.OpenHandCursor)) + else: + self.setCursor(QCursor(Qt.ArrowCursor)) + \ No newline at end of file diff --git a/pykat/node_network.py b/pykat/node_network.py index 53194fda34a01a8d6efa9d248b37cd427a890c1a..64acdf17e233bbeae2c040156e9ab65fb47dd177 100644 --- a/pykat/node_network.py +++ b/pykat/node_network.py @@ -5,6 +5,7 @@ Created on Sun Jan 27 10:02:41 2013 @author: Daniel """ import exceptions +import pykat.gui.graphics from pykat.components import Component from pykat.detectors import Detector @@ -18,17 +19,36 @@ class NodeNetwork(object): return DumpNode() if node_name in self._nodes: - n = self._nodes[node_name] - - return n + # then this node already exists + return self._nodes[node_name] else: - n = Node(node_name) + n = Node(node_name,self) self._nodes[node_name] = n return n + + def removeNode(self, node): + if not isinstance(node,Node): + raise exceptions.ValueError("node argument is not of type Node") + + if node not in self._nodes: + raise exceptions.RuntimeError("Trying to remove node {0} when it has not been added".format(node.name)) + + C = node.getComponents() + + if C[0][0] is not None or C[0][1] is not None: + raise exceptions.RuntimeError("Cannot remove a node which is attached to components still") + if len(C[1]) > 0: + raise exceptions.RuntimeError("Cannot remove a node which is attached to detectors still") + + del self._nodes[node.name] + def hasNode(self, name): return (name in self._nodes) - + + def getNodes(self): + return self._nodes.copy() + def dumpInfo(self): for name in self._nodes: @@ -61,15 +81,30 @@ class NodeNetwork(object): class Node(object): - def __init__(self, name): + def __init__(self, name, network): self._comp1 = None self._comp2 = None self._detectors = [] self.__name = name + self._item = None + self._network = None + + @property + def network(self): return self._network def isConnected(self): - return (self._comp1!=None) and (self._comp2!=None) - + if (self._comp1 is not None) and (self._comp2 is not None): + return True + else: + return False + + def remove(self): + self._network.removeNode(self) + self._item.scene().removeItem(self._item) + + #def disconnect(self, obj): + + def connect(self, obj): if not (isinstance(obj,Component) or isinstance(obj,Detector)): @@ -83,11 +118,26 @@ class Node(object): self._comp2 = obj else: raise exceptions.RuntimeError("Cannot attach {0} to node {1} as it is already connected: {2} and {3}".format( - obj.name, self.__name,self._comp1.name,self._comp2.name)) + obj.name, self.__name,self._comp1.name,self._comp2.name)) + + if self._comp1 == self._comp2: + raise exceptions.RuntimeError("Cannot connect {obj} to both sides of node".format(obj.name)) else: # we must have a detector as we check above self._detectors.append(obj) + if self._item is not None: + self._item.refresh() + + def getQGraphicsItem(self,dx=0,dy=0,nsize=8,parent=None): + if self._item == None: + self._item = pykat.gui.graphics.NodeQGraphicItem(self, + dx,dy, + -nsize/2,-nsize/2, + nsize,nsize,parent) + + return self._item + def getComponents(self): ''' Returns a tuple with the first being the 2 components that connect to the node. The second item is a list of the detectors at the diff --git a/scripts/test_kat.py b/scripts/test_kat.py deleted file mode 100644 index 4418d7165f63a1a7d32e2ddc6de11b731cf1e1b0..0000000000000000000000000000000000000000 --- a/scripts/test_kat.py +++ /dev/null @@ -1,58 +0,0 @@ -from pykat import finesse -from pykat.detectors import * -from pykat.components import * -from pykat.commands import * -from pykat.structs import * -from pykat.plotting import * -import numpy as np -import pylab as pl - -kat = finesse.kat() - -laser(kat,'l1','n1',1) -space(kat,'s1','n1','n2',1) - -mirror(kat,'m1','n2','n3',R=0.8,T=0.2) -space(kat,'s2','n3','n4',L=1) -mirror(kat,'m2','n4','n5',R=0.7,T=0.3) -cavity(kat, 'cav1','m1','n3','m2','n4') -space(kat,'s3','n5','n6',L=1) - -photodiode(kat,'pd_cav','n4') -photodiode(kat,'pd_ref','n2') -photodiode(kat,'pd_trs','n5') - -kat.m1.Rcx = -1000.0 -kat.m1.Rcy = -1000.0 -kat.m2.Rcx = 1000.0 -kat.m2.Rcy = 1000.0 - -xaxis(kat, Scale.linear, [0,360], kat.m2, kat.m2.phi, 1000) - -kat.maxtem = 0 - -run = kat.run(printout=0,printerr=0) - -pl.figure() -pl.plot(run.x,run.y) -pl.xlabel(run.xlabel) -pl.ylabel("Intensity [W]") -pl.legend(run.ylabels) -#pl.show() - -kat.m1.R = 0.5 -kat.m1.T = 0.5 -kat.pd_cav.enabled = False - -run = kat.run(printout=0,printerr=0) - -pl.figure() -pl.plot(run.x,run.y) -pl.xlabel(run.xlabel) -pl.ylabel("Intensity [W]") -pl.legend(run.ylabels) -#pl.show() - -kat.openGUI() - -