Welcome to NodeEditor’s documentation!¶
Welcome to PyQtNodeEditor¶
This package was created from the Node Editor written in PyQt5. The intention was to create a tutorial series describing the path to create a reusable nodeeditor which can be used in different projects. The tutorials are published on youtube for free. The full list of tutorials can be located here: https://www.blenderfreak.com/tutorials/node-editor-tutorial-series/
Features¶
- provides full framework for creating customizable graph, nodes, sockets and edges
- full support for undo / redo and serialization into files in a VCS friendly way
- support for implementing evaluation logic
- hovering effects, dragging edges, cutting lines and a bunch more…
- provided 2 examples on how node editor can be implemented
Requirements¶
- Python 3.x
- PyQt5 or PySide2 (using wrapper QtPy)
Installation¶
$ pip install nodeeditor
Or directly from source code to get the latest version
$ pip install git+https://gitlab.com/pavel.krupala/pyqt-node-editor.git
Or download the source code from gitlab:
git clone https://gitlab.com/pavel.krupala/pyqt-node-editor.git
Other links¶
Event system¶
Nodeeditor uses its own events (and tries to avoid using pyqtSignal
) to handle logic
happening inside the Scene. If a class does handle some events, they are usually described
at the top of the page in this documentation.
Any of the events is subscribable to and the methods for registering callback are called:
add<EventName>Listener(callback)
You can register to any of these events any time.
Events used in NodeEditor:¶
Scene
¶
- Has Been Modified
- when something has changed in the Scene
- Item Selected
- when Node or Edge is selected
- Items Deselected
- when deselect everything appears
- Drag Enter
- when something is Dragged onto the Scene. Here we do allow or deny the drag
- Drop
- when we Drop something into the Scene
SceneHistory
¶
- History Modified
- after History Stamp has been stored or restored
- History Stored
- after History Stamp has been stored
- History Restored
- after History Stamp has been restored
Serialization¶
All of serializable classes derive from Serializable
class.
Serializable does create commonly used parameters for our classes. In our case it is just id
attribute.
Serializable defines two methods which should be overriden in child classes:
According to Coding Standards we keep these two functions on the bottom of the class source code.
To contain all of the data we use OrderedDict
instead of regular dict. Mainly because we want
to retain the order of parameters serialized in files.
Classes which derive from Serializable
:
Scene
Node
QDMNodeContentWidget
Edge
Socket
Evaluation¶
TL;DR: The evaluation system uses
eval()
and
evalChildren()
. eval()
method is supposed to be overriden by your own
implementation. The evaluation logic uses Flags for marking the Nodes to be Dirty and/or Invalid.
Evaluation Functions¶
There are 2 main methods used for evaluation:
eval()
evalChildren()
These functions are mutually exclusive. That means that evalChildren
does not eval current Node,
but only children of the current Node.
By default the implementation of eval()
is “empty” and return 0. However
it seems logical, that eval (if successfull) resets the Node not to be Dirty nor Invalid.
This method is supposed to be overriden by your own implementation. As an example, you can check out
the repository’s examples/example_calculator
to have an inspiration how to setup the
Node evaluation on your own.
The evaluation takes advantage of Node flags described below.
Node
Flags¶
Each Node
has 2 flags:
Dirty
Invalid
The Invalid flag has always higher priority. That means when the Node is Invalid it doesn’t matter if it is Dirty or not.
To mark a node Dirty or Invalid there are respective methods markDirty()
and markInvalid()
. Both methods take bool parameter for the new state.
You can mark Node dirty by setting the parameter to True
. Also you can un-mark the state by passing
False
value.
For both flags there are 3 methods available:
markInvalid()
- to mark only the NodemarkChildrenInvalid()
- to mark only the direct (first level) children of the NodemarkDescendantsInvalid()
- to mark it self and all descendant children of the Node
The same goes for the Dirty flag of course:
markDirty()
- to mark only the NodemarkChildrenDirty()
- to mark only the direct (first level) children of the NodemarkDescendantsDirty()
- to mark it self and all descendant children of the Node
Descendants or Children are always connected to Output(s) of current Node.
When a node is marked Dirty or Invalid event methods
onMarkedInvalid()
onMarkedDirty()
are being called. By default, these methods do nothing.
But still they are implemented in case you would like to override them and use in you own evaluation system.
Coding Standards¶
The following rules and guidelines are used throughout the nodeeditor package:
File naming guidelines¶
- files in the nodeeditor package start with
node_
- files containing graphical representation (PyQt5 overridden classes) start with
node_graphics_
- files for window/widget start with
node_editor_
Coding guidelines¶
- methods use Camel case naming
- variables/properties use Snake case naming
- The constructor
__init__
always contains all class variables for the entire class. This is helpful for new users, so they can just look at the constructor and read about all properties that class is using in one place. Nobody wants any surprises hidden in the code later - nodeeditor uses custom callbacks and listeners. Methods for adding callback functions
are usually named
addXYListener
- custom events are usually named
onXY
- methods named
doXY
usually do certain tasks and also take care of low level operations - classes always contain methods in this order:
__init__
- python magic methods (i.e.
__str__
), setters and getters initXY
functions- listener functions
- nodeeditor event fuctions
- nodeeditor
doXY
andgetXY
helping functions - Qt5 event functions
- other functions
- optionally overridden Qt
paint
method serialize
anddeserialize
methods at the end
Release Notes¶
Note
Contributors please include release notes as needed or appropriate with your bug fixes, feature additions and tests.
1.0.0 (unreleased)¶
- Added first version of library
nodeeditor Package¶
node_content_widget
Module¶
node_edge
Module¶
Edge Validators¶
Edge Validator can be registered to Edge class using its method
registerEdgeValidator()
.
Each validator callback takes 2 params: start_socket and end_socket.
Validator also needs to return True or False. For example of validators
have a look in node_edge_validators
module.
Here is an example how you can register the Edge Validator callbacks:
from nodeeditor.node_edge_validators import *
Edge.registerEdgeValidator(edge_validator_debug)
Edge.registerEdgeValidator(edge_cannot_connect_two_outputs_or_two_inputs)
Edge.registerEdgeValidator(edge_cannot_connect_input_and_output_of_same_node)
Edge Class¶
node_edge_dragging
Module¶
node_edge_intersect
Module¶
node_edge_rerouting
Module¶
A module containing the Edge Rerouting functionality
-
class
nodeeditor.node_edge_rerouting.
EdgeRerouting
(grView: QGraphicsView)¶ Bases:
object
-
print
(*args)¶ Helper function to better control debug printing to console for this feature
-
getEdgeClass
()¶ Helper function to get the Edge class. Using what the Scene class provides
-
getAffectedEdges
() → list¶ Get a list of all edges connected to the self.start_socket where we started the re-routing
Returns: List of all edges affected by the rerouting started from this self.start_socket Socket
Return type: list
-
setAffectedEdgesVisible
(visibility: bool = True)¶ Show/Hide all edges connected to the self.start_socket where we started the re-routing
Parameters: visibility ( bool
) –True
if all the affectedEdge
(s) should be shown or hidden
-
resetRerouting
()¶ Reset to default state. Init this feature internal variables
-
clearReroutingEdges
()¶ Remove the helping dashed edges from the
Scene
-
updateScenePos
(x: float, y: float)¶ Update position of all the rerouting edges (dashed ones). Called from mouseMove event to update to new mouse position
Parameters: - x (
float
) – new X position - y (
float
) – new Y position
- x (
-
startRerouting
(socket: Socket)¶ Method to start the re-routing. Called from the grView’s state machine.
Parameters: socket ( Socket
) –Socket
where we started the re-routing
-
stopRerouting
(target: Socket = None)¶ Method for stopping the re-routing
Parameters: target ( Socket
orNone
) – Target where we ended the rerouting (usually released mouse button). ProvideSocket
orNone
to cancel
-
node_edge_snapping
Module¶
node_edge_validators
Module¶
A module containing the Edge Validator functions which can be registered as callbacks to
Edge
class.
Example of registering Edge Validator callbacks:¶
You can register validation callbacks once for example on the bottom of node_edge.py file or on the application start with calling this:
from nodeeditor.node_edge_validators import *
Edge.registerEdgeValidator(edge_validator_debug)
Edge.registerEdgeValidator(edge_cannot_connect_two_outputs_or_two_inputs)
Edge.registerEdgeValidator(edge_cannot_connect_input_and_output_of_same_node)
Edge.registerEdgeValidator(edge_cannot_connect_input_and_output_of_different_type)
-
nodeeditor.node_edge_validators.
print_error
(*args)¶ Helper method which prints to console if DEBUG is set to True
-
nodeeditor.node_edge_validators.
edge_validator_debug
(input: Socket, output: Socket) → bool¶ This will consider edge always valid, however writes bunch of debug stuff into console
-
nodeeditor.node_edge_validators.
edge_cannot_connect_two_outputs_or_two_inputs
(input: Socket, output: Socket) → bool¶ Edge is invalid if it connects 2 output sockets or 2 input sockets
-
nodeeditor.node_edge_validators.
edge_cannot_connect_input_and_output_of_same_node
(input: Socket, output: Socket) → bool¶ Edge is invalid if it connects the same node
-
nodeeditor.node_edge_validators.
edge_cannot_connect_input_and_output_of_different_type
(input: Socket, output: Socket) → bool¶ Edge is invalid if it connects sockets with different colors
node_editor_widget
Module¶
node_editor_window
Module¶
node_graphics_cutline
Module¶
node_graphics_edge_path
Module¶
GraphicsEdgePathBase base class¶
GraphicsEdgePathDirect class¶
GraphicsEdgePathBezier class¶
node_graphics_node
Module¶
node_graphics_scene
Module¶
node_graphics_socket
Module¶
node_node
Module¶
node_scene
Module¶
Events¶
- Has Been Modified
- when something has changed in the Scene
- Item Selected
- when Node or Edge is selected
- Items Deselected
- when deselect everything appears
- Drag Enter
- when something is Dragged onto the Scene. Here we do allow or deny the drag
- Drop
- when we Drop something into the Scene
Exceptions¶
Scene Class¶
node_scene_clipboard
Module¶
node_scene_history
Module¶
Events¶
- History Modified
- after History Stamp has been stored or restored
- History Stored
- after History Stamp has been stored
- History Restored
- after History Stamp has been restored
SceneHistory Class¶
node_serializable
Module¶
A module containing Serializable “Interface”. We pretend its an abstract class
-
class
nodeeditor.node_serializable.
Serializable
¶ Bases:
object
Default constructor automatically creates data which are common to any serializable object. In our case we create
self.id
which we use in every object in NodeEditor.-
id
¶ We set this property in the constructor because all of NodeEditor’s serializable objects use this attribute to unique object identification. It is handy for referencing objects.
-
serialize
() → collections.OrderedDict¶ Serialization method to serialize this class data into
OrderedDict
which can be easily stored in memory or file.Returns: data serialized in OrderedDict
Return type: OrderedDict
-
deserialize
(data: dict, hashmap: dict = {}, restore_id: bool = True) → bool¶ Deserialization method which take data in python
dict
format with helping hashmap containing references to existing entities.Parameters: - data (
dict
) – Dictionary containing serialized data - hashmap (
dict
) – Helper dictionary containing references (by id == key) to existing objects - restore_id (bool) – True if we are creating new Sockets. False is useful when loading existing Sockets of which we want to keep the existing object’s id.
Returns: True
if deserialization was successful, otherwiseFalse
Return type: bool
- data (
-