diff --git a/NodeGraphQt/base/graph.py b/NodeGraphQt/base/graph.py index 932b8247..913a109e 100644 --- a/NodeGraphQt/base/graph.py +++ b/NodeGraphQt/base/graph.py @@ -502,7 +502,7 @@ def close(self): def viewer(self): """ - Returns the view interface used by the node graph. + Returns the internal view interface used by the node graph. Warnings: Methods in the ``NodeViewer`` are used internally @@ -572,8 +572,17 @@ def set_grid_mode(self, mode=VIEWER_GRID_LINES): """ Set node graph grid mode. + Note: + By default grid mode is set to "VIEWER_GRID_LINES". + + Node graph background types: + + * :attr:`NodeGraphQt.constants.VIEWER_GRID_NONE` + * :attr:`NodeGraphQt.constants.VIEWER_GRID_DOTS` + * :attr:`NodeGraphQt.constants.VIEWER_GRID_LINES` + Args: - mode: VIEWER_GRID_LINES/VIEWER_GRID_DOTS/VIEWER_GRID_NONE. + mode (int): background styles. """ self.scene().grid_mode = mode self._viewer.force_update() @@ -734,8 +743,11 @@ def set_pipe_style(self, style=PIPE_LAYOUT_CURVED): """ Set node graph pipes to be drawn as straight, curved or angled. + .. image:: _images/pipe_layout_types.gif + :width: 80% + Note: - By default all pipes are set curved. + By default pipe layout is set to "PIPE_LAYOUT_CURVED". Pipe Layout Styles: @@ -1239,6 +1251,7 @@ def _deserialize(self, data, relative_pos=False, pos=None, set_parent=True): Args: data (dict): node data. relative_pos (bool): position node relative to the cursor. + pos (tuple or list): custom x, y position. set_parent (bool): set node parent to current node space. Returns: @@ -1280,7 +1293,10 @@ def _deserialize(self, data, relative_pos=False, pos=None, set_parent=True): self.add_node(node, n_data.get('pos'), unique_name=set_parent) if n_data.get('dynamic_port', None): - node.set_ports({'input_ports': n_data['input_ports'], 'output_ports': n_data['output_ports']}) + node.set_ports({ + 'input_ports': n_data['input_ports'], + 'output_ports': n_data['output_ports'] + }) # build the connections. for connection in data.get('connections', []): diff --git a/NodeGraphQt/base/port.py b/NodeGraphQt/base/port.py index 29aaeee3..05122efc 100644 --- a/NodeGraphQt/base/port.py +++ b/NodeGraphQt/base/port.py @@ -145,6 +145,9 @@ def connect_to(self, port=None): if not port: return + if self in port.connected_ports(): + return + graph = self.node().graph viewer = graph.viewer() diff --git a/NodeGraphQt/constants.py b/NodeGraphQt/constants.py index b2ebc962..317ea4a5 100644 --- a/NodeGraphQt/constants.py +++ b/NodeGraphQt/constants.py @@ -88,8 +88,11 @@ # === NODE VIEWER === +#: Style to render the node graph background with nothing. VIEWER_GRID_NONE = 0 +#: Style to render the node graph background with dots. VIEWER_GRID_DOTS = 1 +#: Style to render the node graph background with grid lines. VIEWER_GRID_LINES = 2 VIEWER_BG_COLOR = (35, 35, 35) diff --git a/NodeGraphQt/widgets/node_publish_widget.py b/NodeGraphQt/widgets/node_publish_widget.py index c46a9cb0..4c5ebb9f 100644 --- a/NodeGraphQt/widgets/node_publish_widget.py +++ b/NodeGraphQt/widgets/node_publish_widget.py @@ -1,6 +1,7 @@ +import os + from .properties import PropFileSavePath from .. import QtWidgets -import os class _element_widget(QtWidgets.QWidget): diff --git a/NodeGraphQt/widgets/viewer.py b/NodeGraphQt/widgets/viewer.py index 6fe3708d..e9801ecf 100644 --- a/NodeGraphQt/widgets/viewer.py +++ b/NodeGraphQt/widgets/viewer.py @@ -23,7 +23,7 @@ class NodeViewer(QtWidgets.QGraphicsView): """ The widget interface used for displaying the scene and nodes. - functions in this class are called by the + functions in this class should mainly be called by the class:`NodeGraphQt.NodeGraph` class. """ @@ -665,6 +665,13 @@ def apply_live_connection(self, event): self.end_live_connection() return + # end connection if starting port is already connected. + if self._start_port.multi_connection and \ + self._start_port in end_port.connected_ports: + self._detached_port = None + self.end_live_connection() + return + # register as disconnected if not acyclic. if self.acyclic and not self.acyclic_check(self._start_port, end_port): if self._detached_port: @@ -814,6 +821,16 @@ def message_dialog(text, title='Node Graph'): BaseDialog.message_dialog(text, title) def load_dialog(self, current_dir=None, ext=None): + """ + Prompt node viewer file load dialog widget. + + Args: + current_dir (str): directory path starting point. (optional) + ext (str): custom file extension filter type. (optional) + + Returns: + str: selected file path. + """ ext = '*{} '.format(ext) if ext else '' ext_filter = ';;'.join([ 'Node Graph ({}*json)'.format(ext), 'All Files (*)' @@ -824,6 +841,16 @@ def load_dialog(self, current_dir=None, ext=None): return file def save_dialog(self, current_dir=None, ext=None): + """ + Prompt node viewer file save dialog widget. + + Args: + current_dir (str): directory path starting point. (optional) + ext (str): custom file extension filter type. (optional) + + Returns: + str: selected file path. + """ ext_label = '*{} '.format(ext) if ext else '' ext_type = '.{}'.format(ext) if ext else '.json' ext_map = {'Node Graph ({}*json)'.format(ext_label): ext_type, @@ -924,6 +951,14 @@ def remove_node(node): node.delete() def move_nodes(self, nodes, pos=None, offset=None): + """ + Globally move specified nodes. + + Args: + nodes (list[AbstractNodeItem]): node items. + pos (tuple or list): custom x, y position. + offset (tuple or list): x, y position offset. + """ group = self.scene().createItemGroup(nodes) group_rect = group.boundingRect() if pos: diff --git a/README.md b/README.md index 6a5e8baf..624d515e 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,11 @@ applications that supports PySide2. #### Vertical Layout - + + +#### Pipe Layout + + #### Example diff --git a/docs/_images/menu_hotkeys.png b/docs/_images/menu_hotkeys.png index 00824e1f..cf3b312a 100644 Binary files a/docs/_images/menu_hotkeys.png and b/docs/_images/menu_hotkeys.png differ diff --git a/docs/_images/pipe_layout_menu.png b/docs/_images/pipe_layout_menu.png new file mode 100644 index 00000000..90cc32c6 Binary files /dev/null and b/docs/_images/pipe_layout_menu.png differ diff --git a/docs/_images/pipe_layout_types.gif b/docs/_images/pipe_layout_types.gif new file mode 100644 index 00000000..4bd6b54e Binary files /dev/null and b/docs/_images/pipe_layout_types.gif differ diff --git a/docs/conf.py b/docs/conf.py index 434c88ba..b9a4ae05 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -162,7 +162,7 @@ # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'NodeGraphQT.tex', 'NodeGraphQt Documentation', - 'Johnny Chan', 'manual'), + author, 'manual'), ] diff --git a/docs/examples/ex_node.rst b/docs/examples/ex_node.rst index 710afe8c..0c6e74a9 100644 --- a/docs/examples/ex_node.rst +++ b/docs/examples/ex_node.rst @@ -75,6 +75,8 @@ To you update the widget you can call the :meth:`NodeGraphQt.NodeObject.set_prop - ``QCheckBox``: :meth:`NodeGraphQt.BaseNode.add_checkbox` - ``QLineEdit``: :meth:`NodeGraphQt.BaseNode.add_text_input` +See: :ref:`Node Widgets` for more node widget types. + Connecting Nodes **************** diff --git a/docs/examples/ex_pipe.rst b/docs/examples/ex_pipe.rst new file mode 100644 index 00000000..152bd477 --- /dev/null +++ b/docs/examples/ex_pipe.rst @@ -0,0 +1,35 @@ +Pipe Examples +############# + +Example Pipe Layouts +******************** + +.. image:: ../_images/pipe_layout_types.gif + :width: 650px + +The :class:`NodeGraphQt.NodeGraph` class has 3 different pipe layout styles as +shown above this can be set easily with the :meth:`NodeGraphQt.NodeGraph.set_pipe_style` +function. + +Here's a super simple example snippet for setting the pipe layout style to be angled. + +.. code-block:: python + :linenos: + + from NodeGraphQt import NodeGraph + from NodeGraphQt.constants import PIPE_LAYOUT_ANGLE + + graph = NodeGraph() + graph.set_pipe_style(PIPE_LAYOUT_ANGLE) + +Constants variables for the 3 different pipe layout styles: + + - ``Curved``: :attr:`NodeGraphQt.constants.PIPE_LAYOUT_CURVED` + - ``Straight``: :attr:`NodeGraphQt.constants.PIPE_LAYOUT_STRAIGHT` + - ``Angle``: :attr:`NodeGraphQt.constants.PIPE_LAYOUT_ANGLE` + +Note: if you've set up your node graph with the ``NodeGraphQt.setup_context_menu`` +is a convenience function then you'll already have the actions to set the pipe +layout under "Edit>Pipe". + +.. image:: ../_images/pipe_layout_menu.png \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 409a3abd..4d1e9bbc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -30,4 +30,5 @@ GitHub Project: https://github.com/jchanvfx/NodeGraphQt examples/ex_overview examples/ex_node examples/ex_port + examples/ex_pipe examples/ex_menu diff --git a/docs/node_widgets.rst b/docs/node_widgets.rst index 5e90e77f..a0d957dd 100644 --- a/docs/node_widgets.rst +++ b/docs/node_widgets.rst @@ -11,6 +11,13 @@ NodeBaseWidget :members: :exclude-members: node, setToolTip, type_, value, widget +NodeCheckBox +************ + +.. autoclass:: NodeGraphQt.widgets.node_widgets.NodeCheckBox + :members: + :exclude-members: widget, type_ + NodeComboBox ************ @@ -25,9 +32,23 @@ NodeLineEdit :members: :exclude-members: widget, type_ -NodeCheckBox +NodeFilePath ************ -.. autoclass:: NodeGraphQt.widgets.node_widgets.NodeCheckBox +.. autoclass:: NodeGraphQt.widgets.node_widgets.NodeFilePath + :members: + :exclude-members: widget, type_ + +NodeFloatEdit +************* + +.. autoclass:: NodeGraphQt.widgets.node_widgets.NodeFloatEdit + :members: + :exclude-members: widget, type_ + +NodeIntEdit +*********** + +.. autoclass:: NodeGraphQt.widgets.node_widgets.NodeIntEdit :members: :exclude-members: widget, type_