2016-10-12 45 views
0

我想在pyqt中建立一個簡單的節點圖。我在使用自定義小部件時遇到了一些問題,並且在移動鼠標時不能正確繪製圖形。見下圖:刷新QGraphicsScene pyqt - 奇怪的結果

Before I move the node with the mouse 在我用鼠標移動節點之前。

After I move the mode with the mouse 後,我移動模式用鼠標

我想,也許這是對我的自定義插件的邊界方法稱爲nodeGFX:

def boundingRect(self): 
     """Bounding.""" 
     return QtCore.QRectF(self.pos().x(), 
          self.pos().y(), 
          self.width, 
          self.height) 

任何想法的傢伙?完整的py文件在下面。

"""Node graph and related classes.""" 

from PyQt4 import QtGui 
from PyQt4 import QtCore 
# import canvas 

''' 
TODO 

Function 
- Delete Connection 
- Delete nodes 

Look 
- Use look information from settings 
    - nodes 
    - connections 
    - canvas 

''' 
# ----------------------------- NodeGFX Class --------------------------------# 
# Provides a visual repersentation of a node in the node interface. Requeres 
# canvas interface. Added to main scene 
# 


class NodeGFX(QtGui.QGraphicsItem): 
    """Display a node.""" 

    # --------------------------- INIT ---------------------------------------# 
    # 
    # Initlize the node 
    # n_x - Where in the graphics scene to position the node. x cord 
    # n_y - Where in the graphics scene to position the node. y cord 
    # n_node - Node object from Canvas. Used in construction of node 
    # n_scene - What is the parent scene of this object 
    # 
    def __init__(self, n_x, n_y, n_node, n_scene): 
     """INIT.""" 
     super(NodeGFX, self).__init__() 
     # Colection of input and output AttributeGFX Objects 
     self.gscene = n_scene 
     self.inputs = {} 
     self.outputs = {} 

     # An identifier for selections 
     self.io = "node" 

     # The width of a node - TODO implement in settings! 
     self.width = 350 

     # Use information from the passed in node to build 
     # this object. 
     self.name = n_node.name 
     node_inputs = n_node.in_attributes 
     node_outputs = n_node.out_attributes 

     # How far down to go between each attribute TODO implement in settings! 
     attr_offset = 25 
     org_offset = attr_offset 

     if len(node_inputs) > len(node_outputs): 
      self.height = attr_offset * len(node_inputs) + (attr_offset * 2) 
     else: 
      self.height = attr_offset * len(node_outputs) + (attr_offset * 2) 

     # Create the node! 
     ''' 
     QtGui.QGraphicsRectItem.__init__(self, 
             n_x, 
             n_y, 
             self.width, 
             self.height, 
             scene=n_scene) 
     ''' 
     self.setFlag(QtGui.QGraphicsItem.ItemIsMovable, True) 

     self.lable = QtGui.QGraphicsTextItem(self.name, 
              self) 

     # Set up inputs 
     for key, value in node_inputs.iteritems(): 
      new_attr_gfx = AttributeGFX(0, 
             0, 
             self.scene(), 
             self, 
             key, 
             value.type, 
             "input") 
      new_attr_gfx.setPos(0, attr_offset) 
      self.inputs[key] = new_attr_gfx 
      attr_offset = attr_offset + 25 

     # set up Outputs 
     attr_offset = org_offset 
     for key, value in node_outputs.iteritems(): 
      new_attr_gfx = AttributeGFX(0, 
             0, 
             self.scene(), 
             self, 
             key, 
             value.type, 
             "output") 
      new_attr_gfx.setPos(self.width, attr_offset) 
      self.outputs[key] = new_attr_gfx 
      attr_offset = attr_offset + 25 

    # ---------------- Utility Functions -------------------------------------# 
    def canv(self): 
     """Link to the canvas object.""" 
     return self.scene().parent().parent().canvasobj 

    def __del__(self): 
     """Destory a node and all child objects.""" 
     # Remove self from GFX scene 
     print "Node del func called" 
     self.scene().removeItem(self) 

    def boundingRect(self): 
     """Bounding.""" 
     return QtCore.QRectF(self.pos().x(), 
          self.pos().y(), 
          self.width, 
          self.height) 

    def mousePressEvent(self, event): 
     self.update() 
     super(NodeGFX, self).mousePressEvent(event) 

    def mouseReleaseEvent(self, event): 
     self.update() 
     super(NodeGFX, self).mouseReleaseEvent(event) 

    # ------------- Event Functions ------------------------------------------# 
    def mouseMoveEvent(self, event): 
     """Update connections when nodes are moved.""" 
     self.scene().updateconnections() 
     QtGui.QGraphicsItem.mouseMoveEvent(self, event) 
     self.gscene.update() 

    def mousePressEvent(self, event): 
     """Select a node.""" 
     print "Node Selected" 
     self.scene().selection(self) 
     QtGui.QGraphicsEllipseItem.mousePressEvent(self, event) 

    # ----------- Paint Functions -------------------------------------------# 
    def paint(self, painter, option, widget): 
     painter.setPen(QtCore.Qt.NoPen) 
     painter.setBrush(QtCore.Qt.darkGray) 
     self.width = 400 
     self.height = 400 
     painter.drawEllipse(-7, -7, 20, 20) 

     rectangle = QtCore.QRectF(0, 
            0, 
            self.width, 
            self.height) 
     painter.drawRoundedRect(rectangle, 15.0, 15.0) 



# ----------------------------- NodeGFX Class --------------------------------# 
# Provides a visual repersentation of a Connection in the node interface. 
# Requeres canvas interface and two nodes. Added to main scene 
# Using two attributes draw a line between them. When 
# Set up, a connection is also made on the canvas. unlike the canvas which 
# stores connections on attributes, connectionGFX objects are stored in a 
# list on the scene object 
# 


class ConnectionGFX (QtGui.QGraphicsLineItem): 
    """A connection between two nodes.""" 

    # ---------------------- Init Function -----------------------------------# 
    # 
    # Inits the Connection. 
    # n_scene - The scene to add these connections to 
    # n_upsteam - a ref to an upstream attributeGFX object. 
    # n_downstream - a ref to a downstream attributeGFX object. 
    # 

    def __init__(self, n_scene, n_upstream, n_downstream): 
     """INIT.""" 
     # Links to the AttributeGFX objs 
     self.upstreamconnect = n_upstream 
     self.downstreamconnect = n_downstream 
     self.io = 'connection' 

     super(ConnectionGFX, self).__init__(scene=n_scene) 
     self.setFlag(QtGui.QGraphicsItem.ItemIsSelectable, True) 

     self.scene().addItem(self) 
     self.update() 

    # ----------------- Utility functions ------------------------------- 

    # When nodes are moved update is called. This will change the line 
    def update(self): 
     """Called when new Draw.""" 
     super(ConnectionGFX, self).update() 
     x1, y1, x2, y2 = self.updatepos() 
     self.setLine(x1, y1, x2, y2) 

    # Called by update calculate the new line points 
    def updatepos(self): 
     """Get new position Data to draw line.""" 
     up_pos = QtGui.QGraphicsItem.scenePos(self.upstreamconnect) 
     dn_pos = QtGui.QGraphicsItem.scenePos(self.downstreamconnect) 

     x1 = up_pos.x() 
     y1 = up_pos.y() 

     x2 = dn_pos.x() 
     y2 = dn_pos.y() 

     return x1, y1, x2, y2 

    # -------------------------- Event Overides ------------------------------# 
    def mousePressEvent(self, event): 
     """Select a connection.""" 
     print "Connection Selected" 
     self.scene().selection(self) 
     QtGui.QGraphicsEllipseItem.mousePressEvent(self, event) 

# ------------------------ AttributeGFX Class --------------------------------# 
# Provides a visual repersentation of an attribute. Used for both input and 
# output connections. Stored on nodes themselves. They do not hold any of 
# the attribute values. This info is stored and modded in the canvas. 
# 


class AttributeGFX (QtGui.QGraphicsEllipseItem): 
    """An attribute on a node.""" 

    # ---------------- Init -------------------------------------------------# 
    # 
    # Init the attributeGFX obj. This object is created by the nodeGFX obj 
    # n_x - Position x 
    # n_y - Position y 
    # n_scene - The scene to add this object to 
    # n_parent - The patent node of this attribute. Used to link 
    # n_name - The name of the attribute, must match whats in canvas 
    # n_type - The data type of the attribute 
    # n_io - Identifier for selection 

    def __init__(self, 
       n_x, 
       n_y, 
       n_scene, 
       n_parent, 
       n_name, 
       n_type, 
       n_io): 
     """INIT.""" 
     self.width = 15 
     self.height = 15 
     self.io = n_io 
     self.name = n_name 
     # Use same object for inputs and outputs 
     self.is_input = True 
     if "output" in n_io: 
      self.is_input = False 

     QtGui.QGraphicsEllipseItem.__init__(self, 
              n_x, 
              n_y, 
              self.width, 
              self.height, 
              n_parent, 
              n_scene) 
     self.lable = QtGui.QGraphicsTextItem(n_name, self, n_scene) 
     # self.lable.setY(n_y) 
     # TODO - Need a more procedual way to place the outputs... 
     if self.is_input is False: 
      n_x = n_x - 100 
     # self.lable.setX(self.width + n_x) 
     self.lable.setPos(self.width + n_x, n_y) 

    # ----------------------------- Event Overides -------------------------- # 
    def mousePressEvent(self, event): 
     """Select and attribute.""" 
     print "Attr Selected" 
     self.scene().selection(self) 
     QtGui.QGraphicsEllipseItem.mousePressEvent(self, event) 

# ------------------------ SceneGFX Class --------------------------------# 
# Provides tracking of all the elements in the scene and provides all the 
# functionality. Is a child of the NodeGraph object. Commands for editing the 
# node network byond how they look in the node graph are passed up to the 
# canvas. If the functions in the canvas return true then the operation is 
# permitted and the data in the canvas has been changed. 
# 

class SceneGFX(QtGui.QGraphicsScene): 
    """Stores grapahic elems.""" 

    # -------------------------- init -------------------------------------- # 
    # 
    # n_x - position withing the node graph widget x cord 
    # n_y - position withing the node graph widget y cord 
    def __init__(self, n_x, n_y, n_width, n_height, n_parent): 
     """INIT.""" 
     # Dict of nodes. Must match canvas 
     self.nodes = {} 

     # list of connections between nodes 
     self.connections = [] 

     # The currently selected object 
     self.cur_sel = None 

     # how far to off set newly created nodes. Prevents nodes from 
     # being created ontop of each other 
     self.node_creation_offset = 100 

     super(SceneGFX, self).__init__(n_parent) 
     self.width = n_width 
     self.height = n_height 

    def addconnection(self, n1_node, n1_attr, n2_node, n2_attr): 
     """Add a new connection.""" 
     new_connection = ConnectionGFX(self, 
             self.nodes[n1_node].outputs[n1_attr], 
             self.nodes[n2_node].inputs[n2_attr]) 
     self.connections.append(new_connection) 
     self.parent().update_attr_panel() 

    def helloworld(self): 
     """test.""" 
     print "Scene - hello world" 

    def updateconnections(self): 
     """Update connections.""" 
     for con in self.connections: 
      con.update() 

    def canv(self): 
     """Link to the canvas object.""" 
     return self.parent().canvasobj 

    def mainwidget(self): 
     """Link to the main widget obj.""" 
     return self.parent() 

    def delselection(self): 
     """Delete the selected obj.""" 
     if "connection" in self.cur_sel.io: 
      print "Deleteing Connection" 
      if self.mainwidget().delete_connection(self.cur_sel): 
       self.removeItem(self.cur_sel) 
       for x in range(0, len(self.connections) - 1): 
        if self.cur_sel == self.connections[x]: 
         del self.connections[x] 
         break 
       self.cur_sel = None 

     elif "node" in self.cur_sel.io: 
      if self.mainwidget().delete_node(self.cur_sel): 
       print "Deleteing Node" 
       node_name = self.cur_sel.name 

       # First search for all connections assosiated with this node 
       # and delete 

       # Create Dic from list 
       connection_dict = {} 
       for x in range(0, len(self.connections)): 
        connection_dict[str(x)] = self.connections[x] 

       new_connection_list = [] 

       for key, con in connection_dict.iteritems(): 
        up_node = con.upstreamconnect.parentItem().name 
        down_node = con.downstreamconnect.parentItem().name 

        if up_node == node_name or down_node == node_name: 
         self.removeItem(connection_dict[key]) 
        else: 
         new_connection_list.append(con) 

       self.connections = new_connection_list 
       del connection_dict 

       self.removeItem(self.nodes[node_name]) 
       del self.nodes[node_name] 
     self.parent().update_attr_panel() 

    def keyPressEvent(self, event): 
     """Listen for key presses on scene obj.""" 
     if event.key() == QtCore.Qt.Key_Delete: 
      self.delselection() 

     super(SceneGFX, self).keyPressEvent(event) 

    def selection(self, sel): 
     """Function to handel selections and connections.""" 
     last_sel = self.cur_sel 
     self.cur_sel = sel 

     print "Last Sel:", last_sel 
     print "Current Sel:", self.cur_sel 

     if "node" in sel.io: 
      self.mainwidget().selected_node = sel 
      self.mainwidget().attr_panel.update_layout() 

     # Need to compaire the current and last selections to see 
     # if a connection has been made 
     if last_sel != None: 
      if "input" in last_sel.io and "output" in self.cur_sel.io: 
       lspn = last_sel.parentItem().name 
       cspn = self.cur_sel.parentItem().name 
       if lspn is not cspn: 
        print "Connecting Attrs 1" 
        self.mainwidget().connect(last_sel.parentItem().name, 
               last_sel.name, 
               self.cur_sel.parentItem().name, 
               self.cur_sel.name) 
       last_sel = None 
       self.cur_sel = None 

      elif "output" in last_sel.io and "input" in self.cur_sel.io: 
       lspn = last_sel.parentItem().name 
       cspn = self.cur_sel.parentItem().name 
       if lspn is not cspn: 
        print "Connecting Attrs 2" 
        self.mainwidget().connect(last_sel.parentItem().name, 
               last_sel.name, 
               self.cur_sel.parentItem().name, 
               self.cur_sel.name) 
       last_sel = None 
       self.cur_sel = None 


class NodeGraph (QtGui.QGraphicsView): 
    """Main Wrapper for node network.""" 

    def __init__(self, p): 
     """INIT.""" 
     QtGui.QGraphicsView.__init__(self, p) 
     self.mainwin = p 
     self.initui() 
     self.nodes = {} 

    def initui(self): 
     """Set up the UI.""" 
     self.setFixedSize(1000, 720) 
     self.scene = SceneGFX(0, 0, 25, 1000, self.mainwin) 
     self.setScene(self.scene) 

    def addnode(self, node_name, node_type): 
     """Forward node creation calls to scene.""" 
     br = self.mapToScene(self.viewport().geometry()).boundingRect() 
     x = br.x() + (br.width()/2) 
     y = br.y() + (br.height()/2) 
     new_node = NodeGFX(x, 
          y, 
          self.canv().nodes[node_name], 
          self) 
     self.scene.addItem(new_node) 
     self.nodes[node_name] = new_node 

    def addconnection(self, n1_node, n1_attr, n2_node, n2_attr): 
     """Add a connection between 2 nodes.""" 
     self.scene.addconnection(n1_node, n1_attr, n2_node, n2_attr) 

    def helloworld(self): 
     """test.""" 
     print "Node graph - hello world" 

    def canv(self): 
     """Link to the canvas object.""" 
     return self.mainwin.canvasobj 

    def change_name_accepted(self, old_name, new_name): 
     """Update the node graph to accept new names""" 
     pass 

回答

0

所以我的問題是,我沒有縮放「對象空間」中的邊界框...以下更改修復我的問題。

def boundingRect(self): 
     """Bounding.""" 
     # Added .5 for padding 
     return QtCore.QRectF(-.5, 
          -.5, 
          self.width + .5, 
          self.height + .5) 

def paint(self, painter, option, widget): 
    painter.setPen(QtCore.Qt.NoPen) 
    painter.setBrush(QtCore.Qt.darkGray) 
    self.width = 400 
    self.height = 400 

    rectangle = QtCore.QRectF(0, 
           0, 
           self.width, 
           self.height) 
    painter.drawRoundedRect(rectangle, 15.0, 15.0)