From 6dd7114e47a7520e62c1175f9b5e5094d18887cd Mon Sep 17 00:00:00 2001 From: jchanvfx Date: Wed, 8 Jan 2020 20:36:30 +1300 Subject: [PATCH 1/6] updated example docs. --- NodeGraphQt/__init__.py | 54 +++++----- NodeGraphQt/base/graph.py | 65 +++++++++--- NodeGraphQt/base/menu.py | 36 +------ NodeGraphQt/base/node.py | 6 +- NodeGraphQt/base/utils.py | 9 +- docs/_images/selection.png | Bin 0 -> 55263 bytes docs/constants.rst | 1 - docs/examples/ex_index.rst | 16 +++ docs/examples/ex_menu.rst | 100 ++++++++++++++++++ docs/examples/ex_node.rst | 2 + .../ex_overview.rst} | 60 ++++++----- docs/graph.rst | 6 +- docs/index.rst | 8 +- docs/menu.rst | 48 ++------- docs/nodes/NodeObject.rst | 37 +++++-- 15 files changed, 289 insertions(+), 159 deletions(-) create mode 100644 docs/_images/selection.png create mode 100644 docs/examples/ex_index.rst create mode 100644 docs/examples/ex_menu.rst create mode 100644 docs/examples/ex_node.rst rename docs/{overview.rst => examples/ex_overview.rst} (79%) diff --git a/NodeGraphQt/__init__.py b/NodeGraphQt/__init__.py index aa004d71..ba7ba753 100644 --- a/NodeGraphQt/__init__.py +++ b/NodeGraphQt/__init__.py @@ -30,45 +30,47 @@ # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """ -NodeGraphQt is a node graph framework that can be implemented and re purposed -into applications that supports PySide2. +**NodeGraphQt** is a node graph framework that can be implemented and re purposed +into applications that supports **PySide2**. -url: https://github.com/jchanvfx/NodeGraphQt -docs: http://chantasticvfx.com/nodeGraphQt/html/index.html +project: https://github.com/jchanvfx/NodeGraphQt -Basic Example: +example code: -import sys -from NodeGraphQt import QtWidgets, NodeGraph, BaseNode +.. code-block:: python + :linenos: + import sys + from NodeGraphQt import QtWidgets, NodeGraph, BaseNode -class MyNode(BaseNode): - __identifier__ = 'com.chantasticvfx' - NODE_NAME = 'My Node' + class MyNode(BaseNode): - def __init__(self): - super(MyNode, self).__init__() - self.add_input('foo', color=(180, 80, 0)) - self.add_output('bar') + __identifier__ = 'com.chantasticvfx' + NODE_NAME = 'My Node' -if __name__ == '__main__': - app = QtWidgets.QApplication(sys.argv) - graph = NodeGraph() + def __init__(self): + super(MyNode, self).__init__() + self.add_input('foo', color=(180, 80, 0)) + self.add_output('bar') - graph.register_node(BaseNode) - graph.register_node(BackdropNode) + if __name__ == '__main__': + app = QtWidgets.QApplication(sys.argv) + graph = NodeGraph() - backdrop = graph.create_node('nodeGraphQt.nodes.Backdrop', name='Backdrop') - node_a = graph.create_node('com.chantasticvfx.MyNode', name='Node A') - node_b = graph.create_node('com.chantasticvfx.MyNode', name='Node B', color='#5b162f') + graph.register_node(BaseNode) + graph.register_node(BackdropNode) - node_a.set_input(0, node_b.output(0)) + backdrop = graph.create_node('nodeGraphQt.nodes.Backdrop', name='Backdrop') + node_a = graph.create_node('com.chantasticvfx.MyNode', name='Node A') + node_b = graph.create_node('com.chantasticvfx.MyNode', name='Node B', color='#5b162f') - viewer = graph.viewer() - viewer.show() + node_a.set_input(0, node_b.output(0)) - app.exec_() + viewer = graph.viewer() + viewer.show() + + app.exec_() """ try: diff --git a/NodeGraphQt/base/graph.py b/NodeGraphQt/base/graph.py index ba406d98..da091170 100644 --- a/NodeGraphQt/base/graph.py +++ b/NodeGraphQt/base/graph.py @@ -32,22 +32,61 @@ class NodeGraph(QtCore.QObject): :width: 60% """ - #:QtCore.Signal: emits the node object when a node is created in the node graph. node_created = QtCore.Signal(NodeObject) - #:QtCore.Signal: emits a ``list[str]`` of node ids from the deleted nodes. + """ + Signal triggered when a node is created in the node graph. + + :parameters: :class:`NodeGraphQt.NodeObject` + :emits: created node + """ nodes_deleted = QtCore.Signal(list) - #:QtCore.Signal: emits the node object when selected in the node graph. + """ + Signal triggered when nodes have been deleted from the node graph. + + :parameters: list[str] + :emits: list of deleted node ids. + """ node_selected = QtCore.Signal(NodeObject) - #:QtCore.Signal: triggered when a node is double clicked and emits the node. + """ + Signal triggered when a node is clicked with the LMB. + + :parameters: :class:`NodeGraphQt.NodeObject` + :emits: selected node + """ node_double_clicked = QtCore.Signal(NodeObject) - #:QtCore.Signal: for when a node has been connected emits (``input port``, ``output port``). + """ + Signal triggered when a node is double clicked and emits the node. + + :parameters: :class:`NodeGraphQt.NodeObject` + :emits: selected node + """ port_connected = QtCore.Signal(Port, Port) - #:QtCore.Signal: for when a node has been disconnected emits (``input port``, ``output port``). + """ + Signal triggered when a node port has been connected. + + :parameters: :class:`NodeGraphQt.Port`, :class:`NodeGraphQt.Port` + :emits: input port, output port + """ port_disconnected = QtCore.Signal(Port, Port) - #:QtCore.Signal: for when a node property has changed emits (``node``, ``property name``, ``property value``). + """ + Signal triggered when a node port has been disconnected. + + :parameters: :class:`NodeGraphQt.Port`, :class:`NodeGraphQt.Port` + :emits: input port, output port + """ property_changed = QtCore.Signal(NodeObject, str, object) - #:QtCore.Signal: for when drop data has been added to the graph. + """ + Signal is triggered when a property has changed on a node. + + :parameters: :class:`NodeGraphQt.BaseNode`, str, object + :emits: triggered node, property name, property value + """ data_dropped = QtCore.Signal(QtCore.QMimeData, QtCore.QPoint) + """ + Signal is triggered when data has been dropped to the graph. + + :parameters: :class:`PySide2.QtCore.QMimeData`, :class:`PySide2.QtCore.QPoint` + """ def __init__(self, parent=None): super(NodeGraph, self).__init__(parent) @@ -237,7 +276,7 @@ def widget(self): The node graph widget for adding into a layout. Returns: - QtWidgets.QWidget: node graph widget. + PySide2.QtWidgets.QWidget: node graph widget. """ if self._widget is None: self._widget = QtWidgets.QWidget() @@ -249,16 +288,16 @@ def widget(self): def show(self): """ Show node graph widget this is just a convenience - function to :meth:`NodeGraph.widget().show()`. + function to :meth:`NodeGraph.widget.show()`. """ - self._widget.show() + self.widget.show() def close(self): """ Close node graph NodeViewer widget this is just a convenience - function to :meth:`NodeGraph.widget().close()`. + function to :meth:`NodeGraph.widget.close()`. """ - self._widget.close() + self.widget.close() def viewer(self): """ diff --git a/NodeGraphQt/base/menu.py b/NodeGraphQt/base/menu.py index 58ee0b0b..66c57b73 100644 --- a/NodeGraphQt/base/menu.py +++ b/NodeGraphQt/base/menu.py @@ -8,7 +8,7 @@ class NodeGraphMenu(object): """ - The ``NodeGraphMenu`` is the context menu triggered from the node graph. + The ``NodeGraphMenu`` is the main context menu triggered from the node graph. example for accessing the node graph context menu. @@ -22,8 +22,6 @@ class NodeGraphMenu(object): # get the context menu for the node graph. context_menu = node_graph.get_context_menu('graph') - print(context_menu) - # >> """ def __init__(self, graph, qmenu): @@ -146,45 +144,21 @@ def add_separator(self): class NodesMenu(NodeGraphMenu): """ - The ``NodesMenu`` is the context menu triggered from the nodes. + The ``NodesMenu`` is the context menu triggered from a node. **Inherited from:** :class:`NodeGraphQt.NodeGraphMenu` - example for adding a command to the nodes context menu. + example for accessing the nodes context menu. .. code-block:: python :linenos: - from NodeGraphQt import BaseNode, NodeGraph - - # example node. - class MyNode(BaseNode): - - __identifier__ = 'com.chantasticvfx' - NODE_NAME = 'my node' - - def __init__(self): - super(MyNode, self).__init__() - self.add_input('in') - self.add_output('out') + from NodeGraphQt import NodeGraph - # create node graph. node_graph = NodeGraph() - # register example node. - node_graph.register_node(MyNode) - - # get the context menu for the nodes. + # get the nodes context menu. nodes_menu = node_graph.get_context_menu('nodes') - - # create a command - def test_func(graph, node): - print('Clicked on node: {}'.format(node.name())) - - nodes_menu.add_command('test', - func=test_func, - node_type='com.chantasticvfx.MyNode') - """ def add_command(self, name, func=None, node_type=None): diff --git a/NodeGraphQt/base/node.py b/NodeGraphQt/base/node.py index 22509444..b8a8114c 100644 --- a/NodeGraphQt/base/node.py +++ b/NodeGraphQt/base/node.py @@ -36,10 +36,10 @@ class NodeObject(object): qgraphics_item (AbstractNodeItem): graphic item used for drawing. """ - #:str: unique node identifier domain. + # Unique node identifier domain. `eg.` ``"com.chantacticvfx"`` __identifier__ = 'nodeGraphQt.nodes' - #:str: base node name. + # Base node name. NODE_NAME = None def __init__(self, qgraphics_item=None): @@ -61,7 +61,7 @@ def __repr__(self): def type_(cls): """ Node type identifier followed by the class name. - eg. nodeGraphQt.nodes.MyNode + `eg.` ``"com.chantacticvfx.NodeObject"`` Returns: str: node type. diff --git a/NodeGraphQt/base/utils.py b/NodeGraphQt/base/utils.py index 2d384f64..bc677b0f 100644 --- a/NodeGraphQt/base/utils.py +++ b/NodeGraphQt/base/utils.py @@ -6,7 +6,9 @@ def setup_context_menu(graph): """ - Sets up the node graphs context menu with some basic menus and commands. + populate the specified graph's context menu with essential menus commands. + + example code: .. code-block:: python :linenos: @@ -16,6 +18,11 @@ def setup_context_menu(graph): graph = NodeGraph() setup_context_menu(graph) + result: + + .. image:: _images/menu_hotkeys.png + :width: 300px + Args: graph (NodeGraphQt.NodeGraph): node graph. """ diff --git a/docs/_images/selection.png b/docs/_images/selection.png new file mode 100644 index 0000000000000000000000000000000000000000..de08904b28b626fb282cbecf81839a6ef1ba541a GIT binary patch literal 55263 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 Date: Wed, 8 Jan 2020 23:34:16 +1300 Subject: [PATCH 2/6] menu doc updates. --- NodeGraphQt/base/menu.py | 6 +-- docs/examples/ex_node.rst | 81 +++++++++++++++++++++++++++++++++++++++ docs/menu.rst | 2 + docs/widgets.rst | 8 ++-- 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/NodeGraphQt/base/menu.py b/NodeGraphQt/base/menu.py index 66c57b73..ac437aab 100644 --- a/NodeGraphQt/base/menu.py +++ b/NodeGraphQt/base/menu.py @@ -122,7 +122,7 @@ def add_command(self, name, func=None, shortcut=None): shortcut (str): shotcut key. Returns: - NodeGraphQt.MenuCommand: the appended command. + NodeGraphQt.NodeGraphCommand: the appended command. """ action = GraphAction(name, self._graph.viewer()) action.graph = self._graph @@ -171,7 +171,7 @@ def add_command(self, name, func=None, node_type=None): node_type (str): specified node type for the command. Returns: - NodeGraphQt.MenuCommand: the appended command. + NodeGraphQt.NodeGraphCommand: the appended command. """ if not node_type: raise NodeMenuError('Node type not specified!') @@ -213,7 +213,7 @@ def qaction(self): The underlying qaction. Returns: - BaseAction: qaction object. + GraphAction: qaction object. """ return self._qaction diff --git a/docs/examples/ex_node.rst b/docs/examples/ex_node.rst index df79d54e..5d47a2d0 100644 --- a/docs/examples/ex_node.rst +++ b/docs/examples/ex_node.rst @@ -1,2 +1,83 @@ Node Examples ############# + +Creating Nodes +************** + +| Creating is done simply by calling the :func:`NodeGraphQt.NodeGraph.create_node` function. +| (`see example below` ``line22``) + +.. code-block:: python + :linenos: + + from NodeGraphQt import BaseNode, NodeGraph, QtWidgets + + class MyNode(BaseNode): + + __identifier__ = 'com.chantasticvfx' + NODE_NAME = 'my node' + + def __init__(self): + super(MyNode, self).__init__() + self.add_input('foo') + self.add_input('hello') + self.add_output('bar') + self.add_output('world') + + if __name__ == '__main__': + app = QtWidgets.QApplication([]) + + node_graph = NodeGraph() + node_graph.register_node(MyNode) + node_graph.widget.show() + + # here we create a couple nodes in the node graph. + node_a = node_graph.create_node('com.chantasticvfx.MyNode', name='node a') + node_b = node_graph.create_node('com.chantasticvfx.MyNode', name='node b', pos=[300, 100]) + + app.exec_() + + +Connecting Nodes +**************** + +There a multiple ways for connecting node ports here are a few examples below. + +connecting nodes by the port index: + +.. code-block:: python + + node_b.set_input(0, node_a.output(0)) + +connect nodes by the port name: + +.. code-block:: python + + node_a.outputs()['bar'].connect_to(node_b.inputs()['foo']) + +connecting nodes with the port objects: + +.. code-block:: python + + # node_a "bar" output port. + port_a = node_a.output(0) + # node_b "foo" input port. + port_b = node_b.inputs()['foo'] + # make the connection. + port_a.connect_to(port_b) + + +See functions: + - :func:`NodeGraphQt.BaseNode.input`, + - :func:`NodeGraphQt.BaseNode.output` + - :func:`NodeGraphQt.BaseNode.set_input`, + - :func:`NodeGraphQt.BaseNode.set_output`, + - :func:`NodeGraphQt.BaseNode.inputs`, + - :func:`NodeGraphQt.BaseNode.outputs` + - :func:`NodeGraphQt.Port.connect_to`, + - :func:`NodeGraphQt.Port.disconnect_from` + + +Properties Bin Setup +******************** + diff --git a/docs/menu.rst b/docs/menu.rst index b8f90309..115208d6 100644 --- a/docs/menu.rst +++ b/docs/menu.rst @@ -18,6 +18,7 @@ The context menu triggered from the node graph. .. autoclass:: NodeGraphMenu :members: + :exclude-members: qmenu Nodes Menu @@ -34,3 +35,4 @@ Command .. autoclass:: NodeGraphCommand :members: + :exclude-members: qaction diff --git a/docs/widgets.rst b/docs/widgets.rst index ab727ad8..32d76e61 100644 --- a/docs/widgets.rst +++ b/docs/widgets.rst @@ -2,8 +2,8 @@ Widgets ####### -PropertiesBinWidget -******************* +Properties Bin +************** The ``PropertiesBinWidget`` is a list widget for displaying and editing a nodes properties. @@ -31,8 +31,8 @@ example :members: :exclude-members: property_changed -NodeTreeWidget -************** +Nodes Tree +********** The ``NodeTreeWidget`` is a widget for displaying all registered nodes from the node graph with this widget a user can create nodes by dragging and dropping. From 765e63e584fa8746c23f591c09f6025bbf88d704 Mon Sep 17 00:00:00 2001 From: jchanvfx Date: Thu, 9 Jan 2020 00:05:28 +1300 Subject: [PATCH 3/6] added properties bin example --- docs/examples/ex_node.rst | 61 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/docs/examples/ex_node.rst b/docs/examples/ex_node.rst index 5d47a2d0..9da94f28 100644 --- a/docs/examples/ex_node.rst +++ b/docs/examples/ex_node.rst @@ -67,7 +67,7 @@ connecting nodes with the port objects: port_a.connect_to(port_b) -See functions: +more on ports and connections: - :func:`NodeGraphQt.BaseNode.input`, - :func:`NodeGraphQt.BaseNode.output` - :func:`NodeGraphQt.BaseNode.set_input`, @@ -78,6 +78,61 @@ See functions: - :func:`NodeGraphQt.Port.disconnect_from` -Properties Bin Setup -******************** +Widget Example +************** + +Here's an example where we subclass the ``NodeGraph`` and connect it up to a +``PropertiesBinWidget`` and have it show when a node is double clicked. + +.. code-block:: python + :linenos: + + from NodeGraphQt import BaseNode, NodeGraph, PropertiesBinWidget, QtCore, QtWidgets + + + class MyNode(BaseNode): + + __identifier__ = 'com.chantasticvfx' + NODE_NAME = 'my node' + + def __init__(self): + super(MyNode, self).__init__() + self.add_input('in') + self.add_output('out') + + + class MyNodeGraph(NodeGraph): + + def __init__(self, parent=None): + super(MyNodeGraph, self).__init__(parent) + + # properties bin widget. + self._prop_bin = PropertiesBinWidget(node_graph=self) + self._prop_bin.setWindowFlags(QtCore.Qt.Tool) + + # wire signal. + self.node_double_clicked.connect(self.display_prop_bin) + + def display_prop_bin(self, node): + """ + function for displaying the properties bin when a node + is double clicked + """ + if not self._prop_bin.isVisible(): + self._prop_bin.show() + + + if __name__ == '__main__': + app = QtWidgets.QApplication([]) + + node_graph = MyNodeGraph() + node_graph.register_node(MyNode) + node_graph.widget.show() + + node_a = node_graph.create_node('com.chantasticvfx.MyNode') + + app.exec_() +more on the properties bin and node_double_clicked signal: + - :class:`NodeGraphQt.PropertiesBinWidget` + - :attr:`NodeGraphQt.NodeGraph.node_double_clicked` \ No newline at end of file From fae527e7b783b361c5131893a048b5c7d3304d11 Mon Sep 17 00:00:00 2001 From: jchanvfx Date: Thu, 9 Jan 2020 01:13:32 +1300 Subject: [PATCH 4/6] toc updates --- docs/examples/ex_index.rst | 10 ++++------ docs/examples/ex_overview.rst | 7 +++++-- docs/examples/ex_toc.rst | 9 +++++++++ docs/index.rst | 15 +++++++++++---- 4 files changed, 29 insertions(+), 12 deletions(-) create mode 100644 docs/examples/ex_toc.rst diff --git a/docs/examples/ex_index.rst b/docs/examples/ex_index.rst index 4eae9919..af00760c 100644 --- a/docs/examples/ex_index.rst +++ b/docs/examples/ex_index.rst @@ -1,5 +1,5 @@ -Examples -######## +NodeGraphQt Examples +#################### .. image:: ../_images/overview.png :width: 70% @@ -7,10 +7,8 @@ Examples ---- .. toctree:: - :name: examplestoc + :caption: Module Examples :maxdepth: 3 - ex_overview - ex_menu - ex_node + ex_toc diff --git a/docs/examples/ex_overview.rst b/docs/examples/ex_overview.rst index 073c61fc..b58fedf5 100644 --- a/docs/examples/ex_overview.rst +++ b/docs/examples/ex_overview.rst @@ -1,8 +1,11 @@ -Graph UI Overview -################# +Graph Overview +############## User interface overview for the node graph. +.. image:: ../_images/overview.png + :width: 70% + Navigation ********** diff --git a/docs/examples/ex_toc.rst b/docs/examples/ex_toc.rst new file mode 100644 index 00000000..23ceed93 --- /dev/null +++ b/docs/examples/ex_toc.rst @@ -0,0 +1,9 @@ + +.. toctree:: + :caption: Module Examples + :maxdepth: 3 + + ex_overview + ex_menu + ex_node + diff --git a/docs/index.rst b/docs/index.rst index 45870e80..7e632705 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,17 +6,24 @@ Welcome to the NodeGraphQt documentation. .. image:: _images/screenshot.png :width: 95% -Project Repo: https://github.com/jchanvfx/NodeGraphQt +GitHub Project: https://github.com/jchanvfx/NodeGraphQt ---- .. toctree:: - :name: mastertoc - :maxdepth: 3 + :caption: Module Contents + :name: moduletoc + :maxdepth: 2 constants graph nodes menu widgets - examples/ex_index + +.. toctree:: + :caption: Module Examples + :name: examplestoc + :maxdepth: 3 + + examples/ex_toc From 72234eb74aa5a73cbc92863dc6814a9c2023378e Mon Sep 17 00:00:00 2001 From: jchanvfx Date: Thu, 9 Jan 2020 11:02:09 +1300 Subject: [PATCH 5/6] doc formatting tweaks. --- NodeGraphQt/base/graph.py | 7 ++++--- docs/examples/ex_index.rst | 14 -------------- docs/examples/ex_node.rst | 23 ++++++++++++----------- docs/examples/ex_overview.rst | 5 +++-- docs/examples/ex_toc.rst | 9 --------- docs/graph.rst | 4 ++++ docs/index.rst | 4 +++- 7 files changed, 26 insertions(+), 40 deletions(-) delete mode 100644 docs/examples/ex_index.rst delete mode 100644 docs/examples/ex_toc.rst diff --git a/NodeGraphQt/base/graph.py b/NodeGraphQt/base/graph.py index da091170..1d32d738 100644 --- a/NodeGraphQt/base/graph.py +++ b/NodeGraphQt/base/graph.py @@ -26,7 +26,7 @@ class NodeGraph(QtCore.QObject): """ The ``NodeGraph`` class is the main controller for managing all nodes. - Inherited from: ``PySide2.QtCore.QObject`` + Inherited from: :class:`PySide2.QtCore.QObject` .. image:: _images/graph.png :width: 60% @@ -86,6 +86,7 @@ class NodeGraph(QtCore.QObject): Signal is triggered when data has been dropped to the graph. :parameters: :class:`PySide2.QtCore.QMimeData`, :class:`PySide2.QtCore.QPoint` + :emits: mime data, node graph position """ def __init__(self, parent=None): @@ -309,7 +310,7 @@ def viewer(self): See Also: :attr:`NodeGraph.widget` for adding the node graph into a - QtWidgets.QLayout. + :class:`PySide2.QtWidgets.QLayout`. Returns: NodeGraphQt.widgets.viewer.NodeViewer: viewer interface. @@ -468,7 +469,7 @@ def get_context_menu(self, menu): menu (str): menu name. Returns: - NodeGraphMenu or NodesMenu: context menu object. + NodeGraphQt.NodeGraphMenu or NodeGraphQt.NodesMenu: context menu object. """ menus = self._viewer.context_menus() if menus.get(menu): diff --git a/docs/examples/ex_index.rst b/docs/examples/ex_index.rst deleted file mode 100644 index af00760c..00000000 --- a/docs/examples/ex_index.rst +++ /dev/null @@ -1,14 +0,0 @@ -NodeGraphQt Examples -#################### - -.. image:: ../_images/overview.png - :width: 70% - ----- - -.. toctree:: - :caption: Module Examples - :maxdepth: 3 - - ex_toc - diff --git a/docs/examples/ex_node.rst b/docs/examples/ex_node.rst index 9da94f28..e4cb6c72 100644 --- a/docs/examples/ex_node.rst +++ b/docs/examples/ex_node.rst @@ -5,7 +5,7 @@ Creating Nodes ************** | Creating is done simply by calling the :func:`NodeGraphQt.NodeGraph.create_node` function. -| (`see example below` ``line22``) +| (`see example below` ``line: 22``) .. code-block:: python :linenos: @@ -66,16 +66,16 @@ connecting nodes with the port objects: # make the connection. port_a.connect_to(port_b) +`more on ports and connections.` -more on ports and connections: - - :func:`NodeGraphQt.BaseNode.input`, - - :func:`NodeGraphQt.BaseNode.output` - - :func:`NodeGraphQt.BaseNode.set_input`, - - :func:`NodeGraphQt.BaseNode.set_output`, - - :func:`NodeGraphQt.BaseNode.inputs`, - - :func:`NodeGraphQt.BaseNode.outputs` - - :func:`NodeGraphQt.Port.connect_to`, - - :func:`NodeGraphQt.Port.disconnect_from` + - :func:`NodeGraphQt.BaseNode.input` + - :func:`NodeGraphQt.BaseNode.output` + - :func:`NodeGraphQt.BaseNode.set_input` + - :func:`NodeGraphQt.BaseNode.set_output` + - :func:`NodeGraphQt.BaseNode.inputs` + - :func:`NodeGraphQt.BaseNode.outputs` + - :func:`NodeGraphQt.Port.connect_to` + - :func:`NodeGraphQt.Port.disconnect_from` Widget Example @@ -133,6 +133,7 @@ Here's an example where we subclass the ``NodeGraph`` and connect it up to a app.exec_() -more on the properties bin and node_double_clicked signal: +`more on the properties bin and node_double_clicked signal` + - :class:`NodeGraphQt.PropertiesBinWidget` - :attr:`NodeGraphQt.NodeGraph.node_double_clicked` \ No newline at end of file diff --git a/docs/examples/ex_overview.rst b/docs/examples/ex_overview.rst index b58fedf5..79c9b0a6 100644 --- a/docs/examples/ex_overview.rst +++ b/docs/examples/ex_overview.rst @@ -1,5 +1,5 @@ -Graph Overview -############## +NodeGraph Overview +################## User interface overview for the node graph. @@ -68,6 +68,7 @@ Here's a basic example snippet for creating two nodes and connecting them togeth from NodeGraphQt import NodeGraph, BaseNode, setup_context_menu + # create a node class object inherited from BaseNode. class FooNode(BaseNode): # unique node identifier domain. diff --git a/docs/examples/ex_toc.rst b/docs/examples/ex_toc.rst deleted file mode 100644 index 23ceed93..00000000 --- a/docs/examples/ex_toc.rst +++ /dev/null @@ -1,9 +0,0 @@ - -.. toctree:: - :caption: Module Examples - :maxdepth: 3 - - ex_overview - ex_menu - ex_node - diff --git a/docs/graph.rst b/docs/graph.rst index ff973465..7959c535 100644 --- a/docs/graph.rst +++ b/docs/graph.rst @@ -1,6 +1,10 @@ NodeGraph ######### +`See` :ref:`Basic Example` `from the NodeGraph overview.` + +---- + .. autoclass:: NodeGraphQt.NodeGraph :members: :exclude-members: model, widget diff --git a/docs/index.rst b/docs/index.rst index 7e632705..7d2446e8 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,4 +26,6 @@ GitHub Project: https://github.com/jchanvfx/NodeGraphQt :name: examplestoc :maxdepth: 3 - examples/ex_toc + examples/ex_overview + examples/ex_node + examples/ex_menu From e2569084c6b251dcd7a946d8bac023da6d72fc3a Mon Sep 17 00:00:00 2001 From: jchanvfx Date: Thu, 9 Jan 2020 11:33:57 +1300 Subject: [PATCH 6/6] node attr tweaks --- docs/nodes/NodeObject.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/nodes/NodeObject.rst b/docs/nodes/NodeObject.rst index b101a1e2..7ecb032a 100644 --- a/docs/nodes/NodeObject.rst +++ b/docs/nodes/NodeObject.rst @@ -6,18 +6,22 @@ NodeObject :exclude-members: NODE_NAME, graph, id, model, type_, view .. autoattribute:: NODE_NAME - :annotation: = (re-implementation required) + :annotation: Initial base node name. + .. note:: re-implement this attribute to provide a base node name. + :return: node name :rtype: str .. autoattribute:: __identifier__ - :annotation: = (re-implementation required) + :annotation: Unique node identifier domain. eg. ``"com.chantacticvfx"`` + .. note:: re-implement this attribute to provide a unique node type. + :return: node identifer domain :rtype: str @@ -25,11 +29,11 @@ NodeObject .. autoattribute:: id .. autoattribute:: model .. autoattribute:: type_ - :annotation: = (__identifier__.__className__) + :annotation: Node type identifier followed by the class name. `eg.` ``"com.chantacticvfx.NodeObject"`` - :return: node type + :return: node type (``__identifier__.__className__``) :rtype: str .. autoattribute:: view