diff --git a/NodeGraphQt/base/commands.py b/NodeGraphQt/base/commands.py index 966db5cc..a8a62975 100644 --- a/NodeGraphQt/base/commands.py +++ b/NodeGraphQt/base/commands.py @@ -163,6 +163,60 @@ def redo(self): self.node.view.delete() +class NodeInputConnectedCmd(QtWidgets.QUndoCommand): + """ + "BaseNode.on_input_connected()" command. + + Args: + src_port (NodeGraphQt.Port): source port. + trg_port (NodeGraphQt.Port): target port. + """ + + def __init__(self, src_port, trg_port): + QtWidgets.QUndoCommand.__init__(self) + if src_port.type_() == IN_PORT: + self.source = src_port + self.target = trg_port + else: + self.source = trg_port + self.target = src_port + + def undo(self): + node = self.source.node() + node.on_input_disconnected(self.source, self.target) + + def redo(self): + node = self.source.node() + node.on_input_connected(self.source, self.target) + + +class NodeInputDisconnectedCmd(QtWidgets.QUndoCommand): + """ + Node "on_input_disconnected()" command. + + Args: + src_port (NodeGraphQt.Port): source port. + trg_port (NodeGraphQt.Port): target port. + """ + + def __init__(self, src_port, trg_port): + QtWidgets.QUndoCommand.__init__(self) + if src_port.type_() == IN_PORT: + self.source = src_port + self.target = trg_port + else: + self.source = trg_port + self.target = src_port + + def undo(self): + node = self.source.node() + node.on_input_connected(self.source, self.target) + + def redo(self): + node = self.source.node() + node.on_input_disconnected(self.source, self.target) + + class PortConnectedCmd(QtWidgets.QUndoCommand): """ Port connected command. diff --git a/NodeGraphQt/base/graph.py b/NodeGraphQt/base/graph.py index 62dab907..37935f4e 100644 --- a/NodeGraphQt/base/graph.py +++ b/NodeGraphQt/base/graph.py @@ -16,7 +16,9 @@ from NodeGraphQt.base.port import Port from NodeGraphQt.constants import (DRAG_DROP_ID, PIPE_LAYOUT_CURVED, - PIPE_LAYOUT_STRAIGHT, PIPE_LAYOUT_ANGLE) + PIPE_LAYOUT_STRAIGHT, + PIPE_LAYOUT_ANGLE, + IN_PORT, OUT_PORT) from NodeGraphQt.widgets.viewer import NodeViewer @@ -25,21 +27,21 @@ class NodeGraph(QtCore.QObject): base node graph controller. """ - #: (signal) emits the node object when a node is created in the node graph. + #:QtCore.Signal: emits the node object when a node is created in the node graph. node_created = QtCore.Signal(NodeObject) - #: (signal) emits a list of node ids from the deleted nodes. + #:QtCore.Signal: emits a ``list[str]`` of node ids from the deleted nodes. nodes_deleted = QtCore.Signal(list) - #: (signal) emits the node object when selected in the node graph. + #:QtCore.Signal: emits the node object when selected in the node graph. node_selected = QtCore.Signal(NodeObject) - #: (signal) triggered when a node is double clicked and emits the node. + #:QtCore.Signal: triggered when a node is double clicked and emits the node. node_double_clicked = QtCore.Signal(NodeObject) - #: (signal) for when a node has been connected emits (source port, target port). + #:QtCore.Signal: for when a node has been connected emits (``input port``, ``output port``). port_connected = QtCore.Signal(Port, Port) - #: (signal) for when a node has been disconnected emits (source port, target port). + #:QtCore.Signal: for when a node has been disconnected emits (``input port``, ``output port``). port_disconnected = QtCore.Signal(Port, Port) - #: (signal) for when a node property has changed emits (node, property name, property value). + #:QtCore.Signal: for when a node property has changed emits (``node``, ``property name``, ``property value``). property_changed = QtCore.Signal(NodeObject, str, object) - #: (signal) for when drop data has been added to the graph. + #:QtCore.Signal: for when drop data has been added to the graph. data_dropped = QtCore.Signal(QtCore.QMimeData, QtCore.QPoint) def __init__(self, parent=None): @@ -177,7 +179,7 @@ def _on_connection_changed(self, disconnected, connected): return label = 'connect node(s)' if connected else 'disconnect node(s)' - ptypes = {'in': 'inputs', 'out': 'outputs'} + ptypes = {IN_PORT: 'inputs', OUT_PORT: 'outputs'} self._undo_stack.beginMacro(label) for p1_view, p2_view in disconnected: @@ -204,7 +206,7 @@ def _on_connection_sliced(self, ports): """ if not ports: return - ptypes = {'in': 'inputs', 'out': 'outputs'} + ptypes = {IN_PORT: 'inputs', OUT_PORT: 'outputs'} self._undo_stack.beginMacro('slice connections') for p1_view, p2_view in ports: node1 = self._model.nodes[p1_view.node.id] @@ -242,14 +244,14 @@ def widget(self): def show(self): """ Show node graph widget this is just a convenience - function to :meth:`NodeGraphQt.NodeGraph.widget.show()`. + function to :meth:`NodeGraph.widget().show()`. """ self._widget.show() def close(self): """ Close node graph NodeViewer widget this is just a convenience - function to :meth:`NodeGraphQt.NodeGraph.widget.close()`. + function to :meth:`NodeGraph.widget().close()`. """ self._widget.close() @@ -356,7 +358,7 @@ def clear_undo_stack(self): Note: Convenience function to - :meth:`NodeGraphQt.NodeGraph.undo_stack().clear` + :meth:`NodeGraph.undo_stack().clear()` See Also: :meth:`NodeGraph.begin_undo()`, @@ -772,14 +774,16 @@ def _serialize(self, nodes): for pname, conn_data in inputs.items(): for conn_id, prt_names in conn_data.items(): for conn_prt in prt_names: - pipe = {'in': [n_id, pname], 'out': [conn_id, conn_prt]} + pipe = {IN_PORT: [n_id, pname], + OUT_PORT: [conn_id, conn_prt]} if pipe not in serial_data['connections']: serial_data['connections'].append(pipe) for pname, conn_data in outputs.items(): for conn_id, prt_names in conn_data.items(): for conn_prt in prt_names: - pipe = {'out': [n_id, pname], 'in': [conn_id, conn_prt]} + pipe = {OUT_PORT: [n_id, pname], + IN_PORT: [conn_id, conn_prt]} if pipe not in serial_data['connections']: serial_data['connections'].append(pipe) diff --git a/NodeGraphQt/base/node.py b/NodeGraphQt/base/node.py index 1c7abd14..4c500826 100644 --- a/NodeGraphQt/base/node.py +++ b/NodeGraphQt/base/node.py @@ -33,14 +33,14 @@ class NodeObject(object): qgraphics_item (AbstractNodeItem): graphic item used for drawing. """ - #: (str) unique node identifier domain. + #:str: unique node identifier domain. __identifier__ = 'nodeGraphQt.nodes' - #: (str) base node name. + #:str: base node name. NODE_NAME = None def __init__(self, qgraphics_item=None): - assert qgraphics_item, 'qgraphics item cannot be None.' + assert qgraphics_item, 'qgraphics_item item cannot be None.' self._graph = None self._model = NodeModel() self._model.type_ = self.type_ @@ -261,7 +261,7 @@ def get_property(self, name): object: property data. """ if self.graph and name == 'selected': - self.model.set_property(self.view.selected) + self.model.set_property(name, self.view.selected) return self.model.get_property(name) @@ -602,6 +602,65 @@ def set_output(self, index, port): src_port = self.output(index) src_port.connect_to(port) + def connected_input_nodes(self): + """ + Returns all nodes connected from the input ports. + + Returns: + dict: {: } + """ + nodes = {} + for p in self.input_ports(): + nodes[p] = [cp.node() for cp in p.connected_ports()] + return nodes + + def connected_output_nodes(self): + """ + Returns all nodes connected from the output ports. + + Returns: + dict: {: } + """ + nodes = {} + for p in self.output_ports(): + nodes[p] = [cp.node() for cp in p.connected_ports()] + return nodes + + def on_input_connected(self, in_port, out_port): + """ + Callback triggered when a new pipe connection is made. + + *The default of this function does nothing re-implement if you require + logic to run for this event.* + + Note: + to work with undo & redo for this method re-implement + :meth:`BaseNode.on_input_disconnected` with the reverse logic. + + Args: + in_port (NodeGraphQt.Port): source input port from this node. + out_port (NodeGraphQt.Port): output port that connected to this node. + """ + return + + def on_input_disconnected(self, in_port, out_port): + """ + Callback triggered when a pipe connection has been disconnected + from a INPUT port. + + *The default of this function does nothing re-implement if you require + logic to run for this event.* + + Note: + to work with undo & redo for this method re-implement + :meth:`BaseNode.on_input_connected` with the reverse logic. + + Args: + in_port (NodeGraphQt.Port): source input port from this node. + out_port (NodeGraphQt.Port): output port that was disconnected. + """ + return + class BackdropNode(NodeObject): """ diff --git a/NodeGraphQt/base/port.py b/NodeGraphQt/base/port.py index cf7bc989..15469ccd 100644 --- a/NodeGraphQt/base/port.py +++ b/NodeGraphQt/base/port.py @@ -1,7 +1,9 @@ #!/usr/bin/python from NodeGraphQt.base.commands import (PortConnectedCmd, PortDisconnectedCmd, - PortVisibleCmd) + PortVisibleCmd, + NodeInputConnectedCmd, + NodeInputDisconnectedCmd) from NodeGraphQt.base.model import PortModel from NodeGraphQt.constants import IN_PORT, OUT_PORT @@ -66,7 +68,7 @@ def node(self): Return the parent node. Returns: - NodeGraphQt.NodeObject: parent node object. + NodeGraphQt.BaseNode: parent node object. """ return self.model.node @@ -138,8 +140,8 @@ def connect_to(self, port=None): graph = self.node().graph viewer = graph.viewer() - undo_stack = graph.undo_stack() + undo_stack = graph.undo_stack() undo_stack.beginMacro('connect port') pre_conn_port = None @@ -149,26 +151,33 @@ def connect_to(self, port=None): if not port: if pre_conn_port: + undo_stack.push(NodeInputDisconnectedCmd(self, port)) undo_stack.push(PortDisconnectedCmd(self, port)) return if graph.acyclic() and viewer.acyclic_check(self.view, port.view): if pre_conn_port: + undo_stack.push(NodeInputDisconnectedCmd(self, pre_conn_port)) undo_stack.push(PortDisconnectedCmd(self, pre_conn_port)) return trg_conn_ports = port.connected_ports() if not port.multi_connection() and trg_conn_ports: dettached_port = trg_conn_ports[0] + undo_stack.push(NodeInputDisconnectedCmd(port, dettached_port)) undo_stack.push(PortDisconnectedCmd(port, dettached_port)) if pre_conn_port: + undo_stack.push(NodeInputDisconnectedCmd(self, pre_conn_port)) undo_stack.push(PortDisconnectedCmd(self, pre_conn_port)) + undo_stack.push(NodeInputConnectedCmd(self, port)) undo_stack.push(PortConnectedCmd(self, port)) + undo_stack.endMacro() # emit "port_connected" signal from the parent graph. - graph.port_connected.emit(self, port) + ports = {p.type_(): p for p in [self, port]} + graph.port_connected.emit(ports[IN_PORT], ports[OUT_PORT]) def disconnect_from(self, port=None): """ @@ -181,7 +190,11 @@ def disconnect_from(self, port=None): if not port: return graph = self.node().graph + graph.undo_stack().beginMacro('disconnect port') + graph.undo_stack().push(NodeInputDisconnectedCmd(self, port)) graph.undo_stack().push(PortDisconnectedCmd(self, port)) + graph.undo_stack().endMacro() # emit "port_disconnected" signal from the parent graph. - graph.port_disconnected.emit(self, port) + ports = {p.type_(): p for p in [self, port]} + graph.port_disconnected.emit(ports[IN_PORT], ports[OUT_PORT]) diff --git a/NodeGraphQt/constants.py b/NodeGraphQt/constants.py index 6bb7657b..15104ac2 100644 --- a/NodeGraphQt/constants.py +++ b/NodeGraphQt/constants.py @@ -9,9 +9,9 @@ # === PIPE === PIPE_WIDTH = 1.2 -PIPE_STYLE_DEFAULT = 'line' -PIPE_STYLE_DASHED = 'dashed' -PIPE_STYLE_DOTTED = 'dotted' +PIPE_STYLE_DEFAULT = 0 +PIPE_STYLE_DASHED = 1 +PIPE_STYLE_DOTTED = 2 PIPE_DEFAULT_COLOR = (175, 95, 30, 255) PIPE_DISABLED_COLOR = (190, 20, 20, 255) PIPE_ACTIVE_COLOR = (70, 255, 220, 255) diff --git a/docs/_static/ngqt.css b/docs/_static/ngqt.css index 2e6ad2dc..3a1a6882 100644 --- a/docs/_static/ngqt.css +++ b/docs/_static/ngqt.css @@ -42,6 +42,11 @@ code span.pre { color: #5cafb9; } +/* properties */ +em.property { + color: #5d86c3; +} + /* tables */ table.docutils td, table.docutils th {