Skip to content

Commit e620522

Browse files
committed
Support for live groups and bug fixes
1 parent cc21a53 commit e620522

File tree

4 files changed

+286
-38
lines changed

4 files changed

+286
-38
lines changed

nkview/nuke_parser/nkview/graph_view.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,36 @@
1414
from __future__ import annotations
1515

1616
import sys
17-
from typing import List, Optional, Union
17+
from typing import List, Optional, Union, Tuple
1818

1919
from nuke_parser.nkview.gui_nodes import GroupNode, GuiNode
2020
from nuke_parser.nkview.navbar import NavigationBar
2121
from nuke_parser.nkview.qt import QtCore, QtGui, QtWidgets
2222
from nuke_parser.nkview.utils import qt_cursor
23-
from nuke_parser.parser import parseNk
23+
from nuke_parser.parser import parseNk, Node
2424

25-
INT_MIN = -21474836
26-
INT_MAX = 21474836
25+
INT_MAX = sys.maxsize // 2
26+
INT_MIN = ~sys.maxsize // 2
27+
28+
29+
class _PrivateApi:
30+
"""Class to expose private methods of the node graph.
31+
Use it on your own risk."""
32+
33+
def __init__(self, node_view: _NkGraphView):
34+
self._node_view = node_view
35+
36+
def addGraphicsItemToScene(self, item: QtWidgets.QGraphicsItem) -> None:
37+
self._node_view.scene().addItem(item)
38+
39+
def removeGraphicsItemFromScene(self, item):
40+
self._node_view.scene().removeItem(item)
41+
42+
def selectedItems(self) -> List[QtWidgets.QGraphicsItem]:
43+
return self._node_view.scene().selectedItems()
44+
45+
def guiNodeFromPath(self, path: str) -> Optional[GuiNode]:
46+
return self._node_view._scene_map.get(path)
2747

2848

2949
class _NkGraphView(QtWidgets.QGraphicsView):
@@ -192,6 +212,8 @@ def loadNk(self, file_path: str) -> None:
192212
193213
"""
194214
if not file_path.endswith(".nk"):
215+
self.setScene(QtWidgets.QGraphicsScene())
216+
self.sceneLoaded.emit(None)
195217
return
196218

197219
self._scene_map = {}
@@ -299,6 +321,8 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None):
299321
layout.addWidget(self._view)
300322
self.setLayout(layout)
301323

324+
self._private_api = _PrivateApi(self._view)
325+
302326
self._view.sceneChanged.connect(self.nav_bar.addItem)
303327
self._view.sceneLoaded.connect(self._sceneLoadedCallback)
304328
self._view.sceneLoaded.connect(self.sceneLoaded.emit)
@@ -309,6 +333,20 @@ def __init__(self, parent: Optional[QtWidgets.QWidget] = None):
309333
self.loadNk = self._view.loadNk
310334
self.frameSelected = self._view.frameSelected
311335

336+
def privateApi(self) -> _PrivateApi:
337+
"""API to interact with `QGraphicsScene` and `QGraphicsView`."""
338+
return self._private_api
339+
340+
def selectedNodes(self) -> Tuple[Node, ...]:
341+
"""Get selected nodes as parser nodes."""
342+
return tuple(
343+
[
344+
node.nk_node
345+
for node in self._view.scene().selectedItems()
346+
if isinstance(node, GuiNode)
347+
]
348+
)
349+
312350
def clearSelection(self) -> None:
313351
self._view.scene().clearSelection()
314352

nkview/nuke_parser/nkview/gui_nodes.py

Lines changed: 102 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import networkx as nx
2121

2222
from nuke_parser.nkview import constants
23-
from nuke_parser.nkview.qt import QtCore, QtGui, QtWidgets
23+
from nuke_parser.nkview.qt import QtCore, QtGui, QtWidgets, PYSIDE6
2424
from nuke_parser.parser import Node
2525
from nuke_parser.stack import Stack
2626

@@ -66,17 +66,18 @@ class Shape:
6666
selected: QtGui.QPolygonF # Polygon to draw selected node.
6767

6868

69-
def _createShape(points: Tuple[Tuple[float, float], ...]) -> Shape:
69+
def _createShape(points: Tuple[Tuple[float, float], ...], scale: float = 1.0) -> Shape:
7070
"""Create shape object from node points
7171
7272
Args:
7373
points: 2D node vertices of shape.
74+
scale: Vertical scale of node shape.
7475
7576
"""
7677
outline = QtGui.QPolygonF()
7778
selected = QtGui.QPolygonF()
7879

79-
for p in [QtCore.QPointF(*p) * SCALE for p in points]:
80+
for p in [QtCore.QPointF(p[0] * SCALE, p[1] * (SCALE * scale)) for p in points]:
8081
outline.append(p)
8182
selected.append(p)
8283

@@ -95,30 +96,39 @@ def _createShape(points: Tuple[Tuple[float, float], ...]) -> Shape:
9596
return Shape(outline, outline_path, selected)
9697

9798

98-
def shapeFromClass(node: Node) -> Shape:
99-
"""Get shape class from node."""
99+
def shapeFromClass(node: Node, scale: float = 1.0) -> Shape:
100+
"""Get shape class from node.
101+
102+
Args:
103+
node: Node to get shape for.
104+
scale: Vertical scale of node shape.
105+
106+
Returns:
107+
Shape of node.
108+
109+
"""
100110

101111
if node.isGizmo():
102-
return _createShape(constants.GIZMO_SHAPE)
112+
return _createShape(constants.GIZMO_SHAPE, scale)
103113

104114
class_name = node.Class()
105115
if class_name in ("Scene", "GeoScene", "Camera3"):
106116
return _createShape(constants.SCENE_3D_SHAPE)
107117
elif class_name in ("CameraTrackerPointCloud", "ModelBuilder"):
108-
return _createShape(constants.GEO_SHAPE)
118+
return _createShape(constants.GEO_SHAPE, scale)
109119
elif class_name in ("Viewer", "Switch"):
110-
return _createShape(constants.VIEWER_SHAPE)
120+
return _createShape(constants.VIEWER_SHAPE, scale)
111121
elif class_name == "Read":
112122
return _createShape(constants.READ_SHAPE)
113123
elif class_name == "Group":
114-
return _createShape(constants.GROUP_SHAPE)
124+
return _createShape(constants.GROUP_SHAPE, scale)
115125
elif class_name == "Output":
116-
return _createShape(constants.OUTPUT_SHAPE)
126+
return _createShape(constants.OUTPUT_SHAPE, scale)
117127
elif class_name == "Input":
118-
return _createShape(constants.INPUT_SHAPE)
128+
return _createShape(constants.INPUT_SHAPE, scale)
119129
elif class_name == "Dot":
120130
return _createShape(constants.DOT_SHAPE)
121-
return _createShape(constants.BASE_SHAPE)
131+
return _createShape(constants.BASE_SHAPE, scale)
122132

123133

124134
def nukeColorToRgb(num: str) -> QtGui.QColor:
@@ -303,11 +313,11 @@ def __init__(self, nk_node: Node):
303313
QtCore.QRect(
304314
0,
305315
0,
306-
self.nk_node.knob("bdwidth"),
307-
self.nk_node.knob("bdheight"),
316+
self.nk_node.knob("bdwidth", 0),
317+
self.nk_node.knob("bdheight", 0),
308318
)
309319
)
310-
z_value = (self.nk_node.knob("z_order") or 0) / 10
320+
z_value = (self.nk_node.knob("z_order") or 0) / 1000
311321
self.setZValue(z_value)
312322

313323
color_text = self.nk_node.knob("tile_color")
@@ -352,6 +362,41 @@ def paint(
352362
self.nk_node.nodeName(),
353363
)
354364

365+
# Draw corners
366+
367+
top_left = self.rect().topLeft() + QtCore.QPointF(1, 1)
368+
top_right = self.rect().topRight() + QtCore.QPointF(-1, 1)
369+
bottom_right = self.rect().bottomRight() + QtCore.QPointF(-1, -1)
370+
bottom_left = self.rect().bottomLeft() + QtCore.QPointF(1, -1)
371+
poyls = (
372+
[
373+
top_left,
374+
top_left + QtCore.QPointF(offset, 0),
375+
top_left + QtCore.QPointF(0, offset),
376+
], # Top left
377+
[
378+
top_right,
379+
top_right + QtCore.QPointF(0, offset),
380+
top_right + QtCore.QPointF(-offset, 0),
381+
], # Top right
382+
[
383+
bottom_right,
384+
bottom_right + QtCore.QPointF(-offset, 0),
385+
bottom_right + QtCore.QPointF(0, -offset),
386+
], # Bottom right
387+
[
388+
bottom_left,
389+
bottom_left + QtCore.QPointF(0, -offset),
390+
bottom_left + QtCore.QPointF(offset, 0),
391+
],
392+
)
393+
painter.save()
394+
painter.setBrush(self.brush().color().lighter(130))
395+
painter.setPen(QtCore.Qt.NoPen)
396+
for points in poyls:
397+
painter.drawPolygon(points)
398+
painter.restore()
399+
355400
font = QtGui.QFont()
356401
font.setPointSize(self.nk_node.knob("note_font_size", 14) * 0.5)
357402
painter.save()
@@ -383,6 +428,7 @@ class DagNode(QtWidgets.QGraphicsItem):
383428
def __init__(self, nk_node: Node):
384429
super(DagNode, self).__init__()
385430
self._shape_cls = shapeFromClass(nk_node)
431+
386432
self.nk_node = nk_node
387433
if nk_node.Class() != "Root":
388434
self.setZValue(1)
@@ -397,11 +443,17 @@ def __init__(self, nk_node: Node):
397443
else defaultNodeColor(self.nk_node.Class())
398444
)
399445

446+
def nodeText(self) -> str:
447+
return self.nk_node.name()
448+
449+
def nodeShapePointsInWorldSpace(self) -> List[QtCore.QPointF]:
450+
return [self.pos() + point for point in self._shape_cls.polygon]
451+
400452
def boundingRect(self) -> QtCore.QRectF:
401453
"""Get bounding rect of node."""
402454
font = QtGui.QFont()
403455
fm = QtGui.QFontMetrics(font)
404-
width = fm.horizontalAdvance(self.nk_node.knob("name"))
456+
width = fm.horizontalAdvance(self.nodeText())
405457
rect = self._shape_cls.polygon.boundingRect()
406458
if width > rect.width():
407459
delta = width - rect.width()
@@ -451,9 +503,9 @@ def paint(
451503
painter.drawPolygon(self._shape_cls.selected)
452504
painter.restore()
453505

454-
if self.nk_node.knob("disable"):
506+
if self.nk_node.disable():
455507
painter.save()
456-
painter.setPen(QtGui.QPen(QtCore.Qt.black, 1))
508+
painter.setPen(QtGui.QPen(QtCore.Qt.black, 3))
457509
rect = self._shape_cls.polygon.boundingRect()
458510

459511
lines = [
@@ -463,6 +515,23 @@ def paint(
463515
painter.drawLines(lines)
464516
painter.restore()
465517

518+
# Draw clone icon
519+
if self.nk_node.isClone():
520+
top_left = self._shape_cls.polygon.boundingRect().topLeft()
521+
text_rect = QtCore.QRectF(top_left, top_left + QtCore.QPointF(10, 10))
522+
painter.save()
523+
painter.setPen(QtGui.QPen(QtCore.Qt.black, 1))
524+
font = QtGui.QFont()
525+
font.setPixelSize(9)
526+
painter.setFont(font)
527+
painter.setBrush(QtGui.QColor(208, 112, 80))
528+
painter.drawEllipse(text_rect)
529+
painter.setPen(QtCore.Qt.white)
530+
painter.drawText(
531+
text_rect, QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter, "c"
532+
)
533+
painter.restore()
534+
466535
text_rect = QtCore.QRectF(self.boundingRect())
467536
text_rect.setTop(text_rect.top() + 2)
468537
if self.nk_node.Class() != "Dot":
@@ -478,10 +547,12 @@ def paint(
478547
else QtCore.Qt.black
479548
)
480549
painter.setPen(text_color)
550+
font = QtGui.QFont()
551+
font.setPointSize(self.nk_node.knob("note_font_size", 10) * 0.5)
481552

482553
painter.drawText(
483554
text_rect,
484-
self.nk_node.knob("name"),
555+
self.nodeText(),
485556
QtCore.Qt.AlignHCenter | vlayout,
486557
)
487558
painter.restore()
@@ -580,6 +651,18 @@ def __init__(self, root: Node, scene_map: Dict[str, DagNode]):
580651
)
581652
self.addItem(line)
582653

654+
# Add clone line.
655+
clone_source_nk = gui_node.nk_node._source_node
656+
if clone_source_nk and node_map[clone_source_nk.path()]:
657+
source = node_map[clone_source_nk.path()]
658+
659+
line = QtWidgets.QGraphicsLineItem(
660+
QtCore.QLineF(gui_node.center(), source.center())
661+
)
662+
pen = QtGui.QPen(QtGui.QColor(211, 110, 75), 2)
663+
line.setPen(pen)
664+
self.addItem(line)
665+
583666
@staticmethod
584667
def _assingLevels(G: nx.DiGraph) -> Dict[int, List[Node]]:
585668
"""Assign levels to nodes in the graph based on their positions

nkview/nuke_parser/nkview/qt.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,24 @@
1111
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
14+
15+
PYSIDE6 = False
16+
PYSIDE2 = False
1417
try:
1518
from PySide6 import QtCore, QtGui, QtWebEngineWidgets, QtWidgets
1619

20+
PYSIDE6 = True
21+
1722
def get_pos(self):
1823
return self.position().toPoint()
1924

2025
QtGui.QMouseEvent.pos = get_pos
21-
2226
QtWidgets.QAction = QtGui.QAction
2327

24-
2528
except ImportError:
2629
from PySide2 import QtCore, QtGui, QtWebEngineWidgets, QtWidgets
2730

31+
PYSIDE2 = True
2832
if not hasattr(QtWidgets.QApplication, "exec"):
2933
QtWidgets.QApplication.exec = QtWidgets.QApplication.exec_
3034

0 commit comments

Comments
 (0)