Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions NodeGraphQt/base/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ def __repr__(self):
def _wire_signals(self):
# internal signals.
self._viewer.search_triggered.connect(self._on_search_triggered)
self._viewer.connection_sliced.connect(self._on_connection_sliced)
self._viewer.connection_changed.connect(self._on_connection_changed)
self._viewer.moved_nodes.connect(self._on_nodes_moved)
self._viewer.node_double_clicked.connect(self._on_node_double_clicked)
Expand Down Expand Up @@ -188,6 +189,26 @@ def _on_connection_changed(self, disconnected, connected):
port1.connect_to(port2)
self._undo_stack.endMacro()

def _on_connection_sliced(self, ports):
"""
slot when connection pipes have been sliced.

Args:
ports (list[list[widgets.port.PortItem]]):
pair list of port connections (in port, out port)
"""
if not ports:
return
ptypes = {'in': 'inputs', 'out': 'outputs'}
self._undo_stack.beginMacro('slice connections')
for p1_view, p2_view in ports:
node1 = self._model.nodes[p1_view.node.id]
node2 = self._model.nodes[p2_view.node.id]
port1 = getattr(node1, ptypes[p1_view.port_type])()[p1_view.name]
port2 = getattr(node2, ptypes[p2_view.port_type])()[p2_view.name]
port1.disconnect_from(port2)
self._undo_stack.endMacro()

@property
def model(self):
"""
Expand Down
1 change: 1 addition & 0 deletions NodeGraphQt/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
PIPE_DISABLED_COLOR = (190, 20, 20, 255)
PIPE_ACTIVE_COLOR = (70, 255, 220, 255)
PIPE_HIGHLIGHT_COLOR = (232, 184, 13, 255)
PIPE_SLICER_COLOR = (255, 50, 75)
#: The draw the connection pipes as straight lines.
PIPE_LAYOUT_STRAIGHT = 0
#: The draw the connection pipes as curved lines.
Expand Down
63 changes: 63 additions & 0 deletions NodeGraphQt/qgraphics/slicer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/python
from NodeGraphQt import QtCore, QtGui, QtWidgets
from NodeGraphQt.constants import Z_VAL_NODE_WIDGET, PIPE_SLICER_COLOR


class SlicerPipe(QtWidgets.QGraphicsPathItem):
"""
Base item used for drawing the pipe connection slicer.
"""

def __init__(self):
super(SlicerPipe, self).__init__()
self.setZValue(Z_VAL_NODE_WIDGET + 2)

def paint(self, painter, option, widget):
"""
Draws the slicer pipe.

Args:
painter (QtGui.QPainter): painter used for drawing the item.
option (QtGui.QStyleOptionGraphicsItem):
used to describe the parameters needed to draw.
widget (QtWidgets.QWidget): not used.
"""
color = QtGui.QColor(*PIPE_SLICER_COLOR)
p1 = self.path().pointAtPercent(0)
p2 = self.path().pointAtPercent(1)
size = 6.0
offset = size / 2

painter.save()
painter.setRenderHint(painter.Antialiasing, True)

font = painter.font()
font.setPointSize(12)
painter.setFont(font)
text = 'slice'
text_x = painter.fontMetrics().width(text) / 2
text_y = painter.fontMetrics().height() / 1.5
text_pos = QtCore.QPointF(p1.x() - text_x, p1.y() - text_y)
text_color = QtGui.QColor(*PIPE_SLICER_COLOR)
text_color.setAlpha(80)
painter.setPen(QtGui.QPen(text_color, 1.5, QtCore.Qt.SolidLine))
painter.drawText(text_pos, text)

painter.setPen(QtGui.QPen(color, 1.5, QtCore.Qt.DashLine))
painter.drawPath(self.path())

painter.setPen(QtGui.QPen(color, 1.5, QtCore.Qt.SolidLine))
painter.setBrush(color)

rect = QtCore.QRectF(p1.x() - offset, p1.y() - offset, size, size)
painter.drawEllipse(rect)

rect = QtCore.QRectF(p2.x() - offset, p2.y() - offset, size, size)
painter.drawEllipse(rect)
painter.restore()

def draw_path(self, p1, p2):
path = QtGui.QPainterPath()
path.moveTo(p1)
path.lineTo(p2)
self.setPath(path)
44 changes: 43 additions & 1 deletion NodeGraphQt/widgets/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from NodeGraphQt.qgraphics.node_backdrop import BackdropNodeItem
from NodeGraphQt.qgraphics.pipe import Pipe
from NodeGraphQt.qgraphics.port import PortItem
from NodeGraphQt.qgraphics.slicer import SlicerPipe
from NodeGraphQt.widgets.scene import NodeScene
from NodeGraphQt.widgets.stylesheet import STYLE_QMENU
from NodeGraphQt.widgets.tab_search import TabSearchWidget
Expand All @@ -30,6 +31,7 @@ class NodeViewer(QtWidgets.QGraphicsView):

moved_nodes = QtCore.Signal(dict)
search_triggered = QtCore.Signal(str, tuple)
connection_sliced = QtCore.Signal(list)
connection_changed = QtCore.Signal(list, list)

# pass through signals
Expand Down Expand Up @@ -63,6 +65,10 @@ def __init__(self, parent=None):
self._rubber_band = QtWidgets.QRubberBand(
QtWidgets.QRubberBand.Rectangle, self
)
self._pipe_slicer = SlicerPipe()
self._pipe_slicer.setVisible(False)
self.scene().addItem(self._pipe_slicer)

self._undo_stack = QtWidgets.QUndoStack(self)
self._context_menu = QtWidgets.QMenu('main', self)
self._context_menu.setStyleSheet(STYLE_QMENU)
Expand Down Expand Up @@ -126,6 +132,12 @@ def _on_search_submitted(self, node_type):
pos = self.mapToScene(self._previous_pos)
self.search_triggered.emit(node_type, (pos.x(), pos.y()))

def _on_pipes_sliced(self, path):
self.connection_sliced.emit([
[i.input_port, i.output_port]
for i in self.scene().items(path) if isinstance(i, Pipe)
])

# --- reimplemented events ---

def resizeEvent(self, event):
Expand All @@ -138,6 +150,7 @@ def contextMenuEvent(self, event):
def mousePressEvent(self, event):
alt_modifier = event.modifiers() == QtCore.Qt.AltModifier
shift_modifier = event.modifiers() == QtCore.Qt.ShiftModifier

if event.button() == QtCore.Qt.LeftButton:
self.LMB_state = True
elif event.button() == QtCore.Qt.RightButton:
Expand All @@ -152,10 +165,19 @@ def mousePressEvent(self, event):
if self._search_widget.isVisible():
self.tab_search_toggle()

# cursor pos.
map_pos = self.mapToScene(event.pos())

# pipe slicer enabled.
if event.modifiers() == (QtCore.Qt.AltModifier | QtCore.Qt.ShiftModifier):
self._pipe_slicer.draw_path(map_pos, map_pos)
self._pipe_slicer.setVisible(True)
return

if alt_modifier:
return

items = self._items_near(self.mapToScene(event.pos()), None, 20, 20)
items = self._items_near(map_pos, None, 20, 20)
nodes = [i for i in items if isinstance(i, AbstractNodeItem)]

# toggle extend node selection.
Expand Down Expand Up @@ -188,6 +210,13 @@ def mouseReleaseEvent(self, event):
elif event.button() == QtCore.Qt.MiddleButton:
self.MMB_state = False

# hide pipe slicer.
if self._pipe_slicer.isVisible():
self._on_pipes_sliced(self._pipe_slicer.path())
p = QtCore.QPointF(0.0, 0.0)
self._pipe_slicer.draw_path(p, p)
self._pipe_slicer.setVisible(False)

# hide selection marquee
if self._rubber_band.isVisible():
rect = self._rubber_band.rect()
Expand All @@ -211,6 +240,15 @@ def mouseReleaseEvent(self, event):
def mouseMoveEvent(self, event):
alt_modifier = event.modifiers() == QtCore.Qt.AltModifier
shift_modifier = event.modifiers() == QtCore.Qt.ShiftModifier
if event.modifiers() == (QtCore.Qt.AltModifier | QtCore.Qt.ShiftModifier):
if self.LMB_state:
p1 = self._pipe_slicer.path().pointAtPercent(0)
p2 = self.mapToScene(self._previous_pos)
self._pipe_slicer.draw_path(p1, p2)
self._previous_pos = event.pos()
super(NodeViewer, self).mouseMoveEvent(event)
return

if self.MMB_state and alt_modifier:
pos_x = (event.x() - self._previous_pos.x())
zoom = 0.1 if pos_x > 0 else -0.1
Expand Down Expand Up @@ -296,6 +334,10 @@ def sceneMousePressEvent(self, event):
event (QtWidgets.QGraphicsScenePressEvent):
The event handler from the QtWidgets.QGraphicsScene
"""
# pipe slicer enabled.
if event.modifiers() == (QtCore.Qt.AltModifier | QtCore.Qt.ShiftModifier):
return
# viewer pan mode.
if event.modifiers() == QtCore.Qt.AltModifier:
return

Expand Down
Binary file added docs/_images/slicer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 6 additions & 1 deletion docs/_static/ngqt.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ code span.pre {
}

/* tables */
table.docutils td, table.docutils th {
table.docutils td,
table.docutils th {
padding: 4px 8px;
border-top: 0;
border-left: 0;
Expand All @@ -52,6 +53,10 @@ table.docutils td, table.docutils th {
background: #24272b;
}

table.align-center {
margin-left: unset;
}

/*-----------------------------------------*/

/* modules index */
Expand Down
15 changes: 15 additions & 0 deletions docs/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ Navigation
| Pan | *Alt + LMB + Drag* or *MMB + Drag* |
+---------------+----------------------------------------------+

Port Connections
================

.. image:: _images/slicer.png
:width: 600px

Connection pipes can be disconnected easily with the built in slice tool.

+---------------------+----------------------------+
| action | controls |
+=====================+============================+
| Slice connections | *Alt + Shift + LMB + Drag* |
+---------------------+----------------------------+


Node Search
===========

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
PySide2>=5.12
Qt.py>=1.2.0.b2
python>=3.6
python>=3.6
10 changes: 7 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
# -*- coding: utf-8 -*-
import setuptools

from NodeGraphQt import __version__ as version
import NodeGraphQt

with open('README.md', 'r') as fh:
long_description = fh.read()

with open('requirements.txt') as f:
requirements = f.read().splitlines()

description = (
'Node graph framework that can be re-implemented into applications that '
'supports PySide & PySide2'
Expand All @@ -19,7 +22,8 @@

setuptools.setup(
name='NodeGraphQt',
version=version,
version=NodeGraphQt.__version__,
install_requires=requirements,
author='Johnny Chan',
author_email='johnny@chantasticvfx.com',
description=description,
Expand All @@ -28,7 +32,7 @@
url='https://github.com/jchanvfx/NodeGraphQt',
packages=setuptools.find_packages(exclude=["example_nodes"]),
classifiers=classifiers,
include_package_data=True,
include_package_data=True
)


Expand Down