-
Notifications
You must be signed in to change notification settings - Fork 201
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[cuegui] feat: Add job node graph plugin v2 (#1400)
**Link the Issue(s) this Pull Request is related to.** #888 (original PR) **Summarize your change.** This is an adapted PR to support QtPy and PySide6 It depends a fork of NodeGraphQt that has been adapted to use QtPy instead of Qt directly.
- Loading branch information
Showing
25 changed files
with
829 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
# Copyright Contributors to the OpenCue Project | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
|
||
"""Base class for CueGUI graph widgets.""" | ||
|
||
from qtpy import QtCore | ||
from qtpy import QtWidgets | ||
|
||
from NodeGraphQtPy import NodeGraph | ||
from NodeGraphQtPy.errors import NodeRegistrationError | ||
from cuegui.nodegraph import CueLayerNode | ||
from cuegui import app | ||
|
||
|
||
class AbstractGraphWidget(QtWidgets.QWidget): | ||
"""Base class for CueGUI graph widgets""" | ||
|
||
def __init__(self, parent=None): | ||
super(AbstractGraphWidget, self).__init__(parent=parent) | ||
self.graph = NodeGraph() | ||
self.setupUI() | ||
|
||
self.timer = QtCore.QTimer(self) | ||
# pylint: disable=no-member | ||
self.timer.timeout.connect(self.update) | ||
self.timer.setInterval(1000 * 20) | ||
|
||
self.graph.node_selection_changed.connect(self.onNodeSelectionChanged) | ||
app().quit.connect(self.timer.stop) | ||
|
||
def setupUI(self): | ||
"""Setup the UI.""" | ||
try: | ||
self.graph.register_node(CueLayerNode) | ||
except NodeRegistrationError: | ||
pass | ||
self.graph.viewer().installEventFilter(self) | ||
|
||
layout = QtWidgets.QVBoxLayout(self) | ||
layout.addWidget(self.graph.viewer()) | ||
|
||
def onNodeSelectionChanged(self): | ||
"""Slot run when a node is selected. | ||
Updates the nodes to ensure they're visualising current data. | ||
Can be used to notify other widgets of object selection. | ||
""" | ||
self.update() | ||
|
||
def handleSelectObjects(self, rpcObjects): | ||
"""Select incoming objects in graph. | ||
""" | ||
received = [o.name() for o in rpcObjects] | ||
current = [rpcObject.name() for rpcObject in self.selectedObjects()] | ||
if received == current: | ||
# prevent recursing | ||
return | ||
|
||
for node in self.graph.all_nodes(): | ||
node.set_selected(False) | ||
for rpcObject in rpcObjects: | ||
node = self.graph.get_node_by_name(rpcObject.name()) | ||
node.set_selected(True) | ||
|
||
def selectedObjects(self): | ||
"""Return the selected nodes rpcObjects in the graph. | ||
:rtype: [opencue.wrappers.layer.Layer] | ||
:return: List of selected layers | ||
""" | ||
rpcObjects = [n.rpcObject for n in self.graph.selected_nodes()] | ||
return rpcObjects | ||
|
||
def eventFilter(self, target, event): | ||
"""Override eventFilter | ||
Centre nodes in graph viewer on 'F' key press. | ||
@param target: widget event occurred on | ||
@type target: QtWidgets.QWidget | ||
@param event: Qt event | ||
@type event: QtCore.QEvent | ||
""" | ||
if hasattr(self, "graph"): | ||
viewer = self.graph.viewer() | ||
if target == viewer: | ||
if event.type() == QtCore.QEvent.KeyPress: | ||
if event.key() == QtCore.Qt.Key_F: | ||
self.graph.center_on() | ||
if event.key() == QtCore.Qt.Key_L: | ||
self.graph.auto_layout_nodes() | ||
|
||
return super(AbstractGraphWidget, self).eventFilter(target, event) | ||
|
||
def clearGraph(self): | ||
"""Clear all nodes from the graph | ||
""" | ||
for node in self.graph.all_nodes(): | ||
for port in node.output_ports(): | ||
port.unlock() | ||
for port in node.input_ports(): | ||
port.unlock() | ||
self.graph.clear_session() | ||
|
||
def createGraph(self): | ||
"""Create the graph to visualise OpenCue objects | ||
""" | ||
raise NotImplementedError() | ||
|
||
def update(self): | ||
"""Update nodes with latest data | ||
This is run every 20 seconds by the timer. | ||
""" | ||
raise NotImplementedError() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
# Copyright Contributors to the OpenCue Project | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
|
||
"""Node graph to display Layers of a Job""" | ||
|
||
|
||
from qtpy import QtWidgets | ||
|
||
import cuegui.Utils | ||
import cuegui.MenuActions | ||
from cuegui.nodegraph import CueLayerNode | ||
from cuegui.AbstractGraphWidget import AbstractGraphWidget | ||
|
||
|
||
class JobMonitorGraph(AbstractGraphWidget): | ||
"""Graph widget to display connections of layers in a job""" | ||
|
||
def __init__(self, parent=None): | ||
super(JobMonitorGraph, self).__init__(parent=parent) | ||
self.job = None | ||
self.setupContextMenu() | ||
|
||
# wire signals | ||
cuegui.app().select_layers.connect(self.handleSelectObjects) | ||
|
||
def onNodeSelectionChanged(self): | ||
"""Notify other widgets of Layer selection. | ||
Emit signal to notify other widgets of Layer selection, this keeps | ||
all widgets with selectable Layers in sync with each other. | ||
Also force updates the nodes, as the timed updates are infrequent. | ||
""" | ||
self.update() | ||
layers = self.selectedObjects() | ||
cuegui.app().select_layers.emit(layers) | ||
|
||
def setupContextMenu(self): | ||
"""Setup context menu for nodes in node graph""" | ||
self.__menuActions = cuegui.MenuActions.MenuActions( | ||
self, self.update, self.selectedObjects, self.getJob | ||
) | ||
|
||
menu = self.graph.context_menu().qmenu | ||
|
||
dependMenu = QtWidgets.QMenu("&Dependencies", self) | ||
self.__menuActions.layers().addAction(dependMenu, "viewDepends") | ||
self.__menuActions.layers().addAction(dependMenu, "dependWizard") | ||
dependMenu.addSeparator() | ||
self.__menuActions.layers().addAction(dependMenu, "markdone") | ||
menu.addMenu(dependMenu) | ||
menu.addSeparator() | ||
self.__menuActions.layers().addAction(menu, "useLocalCores") | ||
self.__menuActions.layers().addAction(menu, "reorder") | ||
self.__menuActions.layers().addAction(menu, "stagger") | ||
menu.addSeparator() | ||
self.__menuActions.layers().addAction(menu, "setProperties") | ||
menu.addSeparator() | ||
self.__menuActions.layers().addAction(menu, "kill") | ||
self.__menuActions.layers().addAction(menu, "eat") | ||
self.__menuActions.layers().addAction(menu, "retry") | ||
menu.addSeparator() | ||
self.__menuActions.layers().addAction(menu, "retryDead") | ||
|
||
def setJob(self, job): | ||
"""Set Job to be displayed | ||
@param job: Job to display as node graph | ||
@type job: opencue.wrappers.job.Job | ||
""" | ||
self.timer.stop() | ||
self.clearGraph() | ||
|
||
if job is None: | ||
self.job = None | ||
return | ||
|
||
job = cuegui.Utils.findJob(job) | ||
self.job = job | ||
self.createGraph() | ||
self.timer.start() | ||
|
||
def getJob(self): | ||
"""Return the currently set job | ||
:rtype: opencue.wrappers.job.Job | ||
:return: Currently set job | ||
""" | ||
return self.job | ||
|
||
def selectedObjects(self): | ||
"""Return the selected Layer rpcObjects in the graph. | ||
:rtype: [opencue.wrappers.layer.Layer] | ||
:return: List of selected layers | ||
""" | ||
layers = [n.rpcObject for n in self.graph.selected_nodes() if isinstance(n, CueLayerNode)] | ||
return layers | ||
|
||
def createGraph(self): | ||
"""Create the graph to visualise the grid job submission | ||
""" | ||
if not self.job: | ||
return | ||
|
||
layers = self.job.getLayers() | ||
|
||
# add job layers to tree | ||
for layer in layers: | ||
node = CueLayerNode(layer) | ||
self.graph.add_node(node) | ||
node.set_name(layer.name()) | ||
|
||
# setup connections | ||
self.setupNodeConnections() | ||
|
||
self.graph.auto_layout_nodes() | ||
self.graph.center_on() | ||
|
||
def setupNodeConnections(self): | ||
"""Setup connections between nodes based on their dependencies""" | ||
for node in self.graph.all_nodes(): | ||
for depend in node.rpcObject.getWhatDependsOnThis(): | ||
child_node = self.graph.get_node_by_name(depend.dependErLayer()) | ||
if child_node: | ||
# todo check if connection exists | ||
child_node.set_input(0, node.output(0)) | ||
|
||
for node in self.graph.all_nodes(): | ||
for port in node.output_ports(): | ||
port.lock() | ||
for port in node.input_ports(): | ||
port.lock() | ||
|
||
def update(self): | ||
"""Update nodes with latest Layer data | ||
This is run every 20 seconds by the timer. | ||
""" | ||
if self.job is not None: | ||
layers = self.job.getLayers() | ||
for layer in layers: | ||
node = self.graph.get_node_by_name(layer.name()) | ||
node.setRpcObject(layer) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Copyright Contributors to the OpenCue Project | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
|
||
"""nodegraph is an OpenCue specific extension of NodeGraphQtPy | ||
The docs for NodeGraphQtPy can be found at: | ||
http://chantasticvfx.com/nodeGraphQt/html/nodes.html | ||
""" | ||
from .nodes import CueLayerNode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# Copyright Contributors to the OpenCue Project | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
|
||
"""Module housing node implementations that work with NodeGraphQtPy""" | ||
|
||
|
||
from .layer import CueLayerNode |
Oops, something went wrong.