刷新QGraphicsScene pyqt - 奇怪的结果

时间:2016-10-12 15:34:40

标签: python qt canvas pyqt graphics2d

我正在尝试在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

1 个答案:

答案 0 :(得分: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)