Skip to content

Commit 482d418

Browse files
committed
init
1 parent 170e46a commit 482d418

File tree

11 files changed

+763
-0
lines changed

11 files changed

+763
-0
lines changed

main.py

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import sys
2+
3+
from PySide2 import QtWidgets, QtCore, QtGui
4+
5+
import qdarkstyle
6+
import logging
7+
import os
8+
9+
from node_editor.gui.node_widget import NodeWidget
10+
from node_editor.gui.palette import palette
11+
12+
logging.basicConfig(level=logging.DEBUG)
13+
14+
15+
class NodeEditor(QtWidgets.QMainWindow):
16+
def __init__(self, parent=None):
17+
super(NodeEditor, self).__init__(parent)
18+
self.settings = None
19+
20+
icon = QtGui.QIcon("resources\\app.ico")
21+
self.setWindowIcon(icon)
22+
23+
self.setWindowTitle("Node Editor")
24+
settings = QtCore.QSettings("Howard", "NodeEditor")
25+
26+
try:
27+
self.restoreGeometry(settings.value("geometry"))
28+
except AttributeError:
29+
logging.warning("Unable to load settings. First time opening the tool?")
30+
31+
# Layouts
32+
main_widget = QtWidgets.QWidget()
33+
main_layout = QtWidgets.QVBoxLayout()
34+
35+
self.setCentralWidget(main_widget)
36+
main_widget.setLayout(main_layout)
37+
38+
self.node_widget = NodeWidget(self)
39+
main_layout.addWidget(self.node_widget)
40+
41+
def closeEvent(self, event):
42+
self.settings = QtCore.QSettings("node-editor", "NodeEditor")
43+
self.settings.setValue("geometry", self.saveGeometry())
44+
QtWidgets.QWidget.closeEvent(self, event)
45+
46+
47+
if __name__ == "__main__":
48+
app = QtWidgets.QApplication(sys.argv)
49+
app.setWindowIcon(QtGui.QIcon("resources\\app.ico"))
50+
app.setPalette(palette)
51+
launcher = NodeEditor()
52+
launcher.show()
53+
app.exec_()
54+
sys.exit()

node_editor/gui/__init__.py

Whitespace-only changes.

node_editor/gui/connection.py

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
from PySide2 import QtWidgets, QtGui, QtCore
2+
3+
4+
class Connection(QtWidgets.QGraphicsPathItem):
5+
def __init__(self, parent):
6+
super(Connection, self).__init__(parent)
7+
8+
self.setFlag(QtWidgets.QGraphicsPathItem.ItemIsSelectable)
9+
10+
self.setPen(QtGui.QPen(QtGui.QColor(200, 200, 200), 2))
11+
self.setBrush(QtCore.Qt.NoBrush)
12+
self.setZValue(-1)
13+
14+
self.m_port1 = None
15+
self.m_port2 = None
16+
17+
self.pos1 = QtCore.QPointF()
18+
self.pos2 = QtCore.QPointF()
19+
20+
self._do_highlight = False
21+
22+
def __del__(self):
23+
"""
24+
Skip this one
25+
:return:
26+
"""
27+
pass
28+
29+
def delete(self):
30+
"""Delete the connection.
31+
Remove any found connections ports by calling :any:`Port.remove_connection`. After connections
32+
have been removed set the stored :any:`Port` to None. Lastly call :any:`QGraphicsScene.removeItem`
33+
on the scene to remove this widget.
34+
"""
35+
if self.m_port1:
36+
self.m_port1.remove_connection(self)
37+
if self.m_port2:
38+
self.m_port2.remove_connection(self)
39+
40+
self.m_port1 = None
41+
self.m_port2 = None
42+
43+
self.scene().removeItem(self)
44+
45+
def set_pos_1(self, pos):
46+
"""Set the start position of the connection by setting :py:attr:`Connection.pos1` value.
47+
48+
:param pos: The position of the start point
49+
:type pos: QPointF
50+
"""
51+
self.pos1 = pos
52+
53+
def set_pos_2(self, pos):
54+
"""Set the end position of the connection by setting :py:attr:`Connection.pos2` value.
55+
56+
:param pos: The position of the end point
57+
:type pos: QPointF
58+
"""
59+
self.pos2 = pos
60+
61+
def set_port_1(self, port):
62+
"""Set the start port and store it on this connection. After call the :any:`Port.add_connection` on that port.
63+
64+
:param port: The start port
65+
:type port: Port
66+
"""
67+
self.m_port1 = port
68+
self.m_port1.add_connection(self)
69+
70+
def set_port_2(self, port):
71+
"""Set the end port and store it on this connection. After call the :any:`Port.add_connection` on that port.
72+
73+
:param port: The end port
74+
:type port: Port
75+
"""
76+
self.m_port2 = port
77+
self.m_port2.add_connection(self)
78+
79+
def update_pos_from_ports(self):
80+
"""
81+
Update the position from the stored ports.
82+
83+
Get the scene position of each stored :class:`.Port` and update the :py:attr:`Connection.pos1`
84+
and :py:attr:`Connection.pos2` attributes.
85+
"""
86+
self.pos1 = self.m_port1.scenePos()
87+
self.pos2 = self.m_port2.scenePos()
88+
89+
def update_path(self):
90+
"""
91+
Update the path line for the connection.
92+
93+
Create a :class:`QPainterPath` and move it to the start position. Create a direct line to the end
94+
position. Use the base class method :class:`QGraphicsPathItem.setPath` to set this newly created path.
95+
96+
.. note::
97+
Mike doesn't like smooth curves because he thinks they look ugly. LOL!
98+
99+
.. todo::
100+
Look into creating elbow joints like found in Nuke to make Mike happy :)
101+
102+
"""
103+
path = QtGui.QPainterPath()
104+
path.moveTo(self.pos1)
105+
106+
dx = self.pos2.x() - self.pos1.x()
107+
dy = self.pos2.y() - self.pos1.y()
108+
109+
ctr1 = QtCore.QPointF(self.pos1.x() + dx * 0.5, self.pos1.y())
110+
ctr2 = QtCore.QPointF(self.pos1.x() + dx * 0.5, self.pos1.y() + dy)
111+
path.cubicTo(ctr1, ctr2, self.pos2)
112+
113+
self.setPath(path)
114+
115+
def port1(self):
116+
"""
117+
Return the start :class:`.Port` associated with this :class:`Connection`.
118+
119+
:return: The start port
120+
:rtype: Port
121+
"""
122+
return self.m_port1
123+
124+
def port2(self):
125+
"""
126+
Return the end :class:`.Port` associated with this :class:`Connection`.
127+
128+
:return: The end port
129+
:rtype: Port
130+
"""
131+
return self.m_port2
132+
133+
def nodes(self):
134+
return [self.port1().node(), self.port2().node()]
135+
136+
def paint(self, painter, option=None, widget=None):
137+
"""
138+
Override the default paint member from the base class to show color when the connection is selected.
139+
140+
:param painter: The painter object
141+
:param option: Options that are not used
142+
:param widget: Widget that is not used
143+
:type painter: QPainter
144+
:type option: QStyleOptionGraphicsItem
145+
:type widget: QWidget
146+
"""
147+
if self.isSelected() or self._do_highlight:
148+
painter.setPen(QtGui.QPen(QtGui.QColor(255, 102, 0), 3))
149+
else:
150+
painter.setPen(QtGui.QPen(QtGui.QColor(0, 128, 255), 2))
151+
152+
painter.drawPath(self.path())

node_editor/gui/node.py

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
from PySide2 import QtWidgets, QtGui, QtCore
2+
3+
from node_editor.gui.port import Port
4+
5+
6+
class Node(QtWidgets.QGraphicsPathItem):
7+
def __init__(self):
8+
super(Node, self).__init__()
9+
10+
self.setFlag(QtWidgets.QGraphicsPathItem.ItemIsMovable)
11+
self.setFlag(QtWidgets.QGraphicsPathItem.ItemIsSelectable)
12+
13+
self._title_text = "Title"
14+
self._type_text = "base"
15+
16+
self._width = 30 # The Width of the node
17+
self._height = 30 # the height of the node
18+
self._ports = [] # A list of ports
19+
20+
self.node_color = QtGui.QColor(20, 20, 20, 200)
21+
22+
self.title_path = QtGui.QPainterPath() # The path for the title
23+
self.type_path = QtGui.QPainterPath() # The path for the type
24+
self.misc_path = QtGui.QPainterPath() # a bunch of other stuff
25+
26+
self.horizontal_margin = 30 # horizontal margin
27+
self.vertical_margin = 15 # vertical margin
28+
29+
def paint(self, painter, option=None, widget=None):
30+
if self.isSelected():
31+
painter.setPen(QtGui.QPen(QtGui.QColor(241, 175, 0), 2))
32+
painter.setBrush(self.node_color)
33+
else:
34+
painter.setPen(self.node_color.lighter())
35+
painter.setBrush(self.node_color)
36+
37+
painter.drawPath(self.path())
38+
painter.setPen(QtCore.Qt.NoPen)
39+
painter.setBrush(QtCore.Qt.white)
40+
41+
painter.drawPath(self.title_path)
42+
painter.drawPath(self.type_path)
43+
painter.drawPath(self.misc_path)
44+
45+
def add_port(self, name, is_output=False, flags=0, ptr=None):
46+
port = Port(self, self.scene())
47+
port.set_is_output(is_output)
48+
port.set_name(name)
49+
port.set_node(node=self)
50+
port.set_port_flags(flags)
51+
port.set_ptr(ptr)
52+
53+
self._ports.append(port)
54+
55+
def build(self):
56+
""" Build the node
57+
"""
58+
59+
self.title_path = QtGui.QPainterPath() # reset
60+
self.type_path = QtGui.QPainterPath() # The path for the type
61+
self.misc_path = QtGui.QPainterPath() # a bunch of other stuff
62+
63+
total_width = 0
64+
total_height = 0
65+
path = QtGui.QPainterPath() # The main path
66+
67+
# The fonts what will be used
68+
title_font = QtGui.QFont("Lucida Sans Unicode", pointSize=16)
69+
title_type_font = QtGui.QFont("Lucida Sans Unicode", pointSize=8)
70+
port_font = QtGui.QFont("Lucida Sans Unicode")
71+
72+
# Get the dimentions of the title and type
73+
title_dim = {
74+
"w": QtGui.QFontMetrics(title_font).width(self._title_text),
75+
"h": QtGui.QFontMetrics(title_font).height(),
76+
}
77+
78+
title_type_dim = {
79+
"w": QtGui.QFontMetrics(title_type_font).width("(" + self._type_text + ")"),
80+
"h": QtGui.QFontMetrics(title_type_font).height(),
81+
}
82+
83+
# Get the max width
84+
for dim in [title_dim["w"], title_type_dim["w"]]:
85+
if dim > total_width:
86+
total_width = dim
87+
88+
# Add both the title and type height together for the total height
89+
for dim in [title_dim["h"], title_type_dim["h"]]:
90+
total_height += dim
91+
92+
# Add the heigth for each of the ports
93+
for port in self._ports:
94+
port_dim = {
95+
"w": QtGui.QFontMetrics(port_font).width(port.name()),
96+
"h": QtGui.QFontMetrics(port_font).height(),
97+
}
98+
99+
if port_dim["w"] > total_width:
100+
total_width = port_dim["w"]
101+
102+
total_height += port_dim["h"]
103+
104+
# Add the margin to the total_width
105+
total_width += self.horizontal_margin
106+
total_height += self.vertical_margin
107+
108+
# Draw the background rectangle
109+
path.addRoundedRect(
110+
-total_width / 2, -total_height / 2, total_width, total_height, 5, 5
111+
)
112+
113+
# Draw the title
114+
self.title_path.addText(
115+
-title_dim["w"] / 2,
116+
(-total_height / 2) + title_dim["h"],
117+
title_font,
118+
self._title_text,
119+
)
120+
121+
# Draw the type
122+
self.type_path.addText(
123+
-title_type_dim["w"] / 2,
124+
(-total_height / 2) + title_dim["h"] + title_type_dim["h"],
125+
title_type_font,
126+
"(" + self._type_text + ")",
127+
)
128+
129+
y = (-total_height / 2) + title_dim["h"] + title_type_dim["h"] + port_dim["h"]
130+
131+
for port in self._ports:
132+
if port.is_output():
133+
port.setPos(total_width / 2 - 10, y)
134+
else:
135+
port.setPos(-total_width / 2 + 10, y)
136+
y += port_dim["h"]
137+
138+
self.setPath(path)
139+
140+
self._width = total_width
141+
self._height = total_height
142+
143+
def select_connections(self, value):
144+
for port in self._ports:
145+
for connection in port.connections():
146+
connection._do_highlight = value
147+
connection.update_path()
148+

0 commit comments

Comments
 (0)