0
私はpyqtで単純なノードグラフを作成しようとしています。アーティファクトを残して、マウスで動かすと正しく描画されないカスタムウィジェットにいくつか問題があります。以下の画像を参照してください。QGraphicsSceneをリフレッシュするpyqt - 奇妙な結果
でモードを移動した後、私は多分それは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