class VisualScriptingViewer(QtCore.QObject): def __init__(self, visualScripting: VisualScripting, parentWindow=None): super().__init__() self.graph = NodeGraph() self.graphViewer = self.graph.viewer() self.graphManager = visualScripting.graphManager self.initNodes() self.setupPropertiesBin() self.window = asset_manager.loadUI("graphQt.ui") self.splitter = QtWidgets.QSplitter() self.window.bottomLayout.addWidget(self.splitter) self.splitter.addWidget(self.nodeTree) self.splitter.addWidget(self.graphViewer) self.splitter.addWidget(self.propertiesBin) self.setupMenuBar(self.graph) self.onSaveEvent = Event() self.dockWidgets: List[QtWidgets.QDockWidget] = [] self.settingsViewer = SettingsViewer(self.window, visualScripting) self.setupDockWidget(self.settingsViewer.dockWidget) def onOpenInVisualStudioCode(self): QThreadPool.globalInstance().start( core.LambdaTask(self.openInVisualStudioCode)) def openInVisualStudioCode(self): session = self.graphManager.curSession if session != None: codePath = self.graphManager.getPythonCodePath( session.graphSettings) try: subprocess.Popen(f'code \"{os.path.normpath(codePath)}\"', shell=True) except Exception as e: print( f"Failed: {e} - Please make sure Visual Studio Code is installed and 'code' is registered as a command." ) # Modified setup_context_menu from NodeGraphQt.base.actions def setupMenuBar(self, graph: NodeGraph): rootMenu = graph.context_menu() fileMenu = rootMenu.add_menu('&File') editMenu = rootMenu.add_menu('&Edit') # create "File" menu. fileMenu.add_command('Open Graph...', lambda: actions._open_session(graph), QtGui.QKeySequence.Open) fileMenu.add_command('Export Graph As...', lambda: actions._save_session_as(graph), 'Alt+Shift+s') fileMenu.add_command('Clear', lambda: actions._clear_session(graph)) fileMenu.add_separator() fileMenu.add_command('Zoom In', lambda: actions._zoom_in(graph), '=') fileMenu.add_command('Zoom Out', lambda: actions._zoom_out(graph), '-') fileMenu.add_command('Reset Zoom', graph.reset_zoom, 'h') # create "Edit" menu. undo_actn = graph.undo_stack().createUndoAction( graph.viewer(), '&Undo') if LooseVersion(QtCore.qVersion()) >= LooseVersion('5.10'): undo_actn.setShortcutVisibleInContextMenu(True) undo_actn.setShortcuts(QtGui.QKeySequence.Undo) editMenu.qmenu.addAction(undo_actn) redo_actn = graph.undo_stack().createRedoAction( graph.viewer(), '&Redo') if LooseVersion(QtCore.qVersion()) >= LooseVersion('5.10'): redo_actn.setShortcutVisibleInContextMenu(True) redo_actn.setShortcuts(QtGui.QKeySequence.Redo) editMenu.qmenu.addAction(redo_actn) editMenu.add_separator() editMenu.add_command('Clear Undo History', lambda: actions._clear_undo(graph)) editMenu.add_separator() editMenu.add_command('Copy', graph.copy_nodes, QtGui.QKeySequence.Copy) editMenu.add_command('Paste', graph.paste_nodes, QtGui.QKeySequence.Paste) editMenu.add_command( 'Delete', lambda: graph.delete_nodes(graph.selected_nodes()), QtGui.QKeySequence.Delete) editMenu.add_separator() editMenu.add_command('Select all', graph.select_all, 'Ctrl+A') editMenu.add_command('Deselect all', graph.clear_selection, 'Ctrl+Shift+A') editMenu.add_command( 'Enable/Disable', lambda: graph.disable_nodes(graph.selected_nodes()), 'd') editMenu.add_command( 'Duplicate', lambda: graph.duplicate_nodes(graph.selected_nodes()), 'Alt+c') editMenu.add_command('Center Selection', graph.fit_to_selection, 'f') editMenu.add_separator() menuBar = QtWidgets.QMenuBar() sessionMenu = QtWidgets.QMenu("Session") menuBar.addMenu(fileMenu.qmenu) menuBar.addMenu(editMenu.qmenu) menuBar.addMenu(sessionMenu) self.saveAction = QtWidgets.QAction("Save") self.saveAction.setShortcut(QtGui.QKeySequence.Save) self.saveAction.triggered.connect(self.onSave) self.saveAsAction = QtWidgets.QAction("Save As...") self.saveAsAction.setShortcut("Ctrl+Shift+S") self.saveAsAction.triggered.connect(self.onSaveAs) self.loadAction = QtWidgets.QAction("Load") self.loadAction.triggered.connect(self.onLoad) self.runAction = QtWidgets.QAction("Run") self.runAction.triggered.connect(self.onRun) self.runAction.setShortcut(QtGui.QKeySequence("R")) self.openInCode = QtWidgets.QAction("Show Code In Visual Studio Code") self.openInCode.triggered.connect(self.onOpenInVisualStudioCode) self.openInCode.setShortcut(QtGui.QKeySequence("Q")) sessionMenu.addAction(self.saveAction) sessionMenu.addAction(self.saveAsAction) sessionMenu.addAction(self.loadAction) sessionMenu.addAction(self.runAction) sessionMenu.addAction(self.openInCode) self.sessionMenu = sessionMenu self.viewMenu = QtWidgets.QMenu("View") menuBar.addMenu(self.viewMenu) self.window.verticalLayout.insertWidget(0, menuBar) def onRun(self): if self.graphManager.curSession != None: self.graphManager.executeGraph() else: QMessageBox.critical(None, "Unsaved state", "Please save the graph first.") def setupCategoryComboBox(self, comboBox): categories = self.graphManager.graphCategoryToNamesMap.keys() for category in categories: comboBox.addItem(category) def setupFolderComboBox(self, comboBox): for folder in self.graphManager.serializationFolders: comboBox.addItem(folder) def loadDialog(self, relUIPath) -> QDialog: dialog = asset_manager.loadUI(relUIPath) dialog.setWindowFlags(Qt.Dialog | Qt.MSWindowsFixedSizeDialogHint) return dialog def saveAs(self): currentGraphName = self.graphManager.getSessionGraphName() currentStartNodeName = self.graphManager.getSessionStartNodeName() currentCategory = self.graphManager.getSessionCategory() currentFolder = self.graphManager.getSessionGraphFolder() dialog = self.loadDialog("saveGraphDialog.ui") dialog.graphNameLineEdit.setText(currentGraphName) self.setupCategoryComboBox(dialog.categoryComboBox) self.setupFolderComboBox(dialog.folderComboBox) dialog.categoryComboBox.setCurrentText(currentCategory) dialog.folderComboBox.setCurrentText(currentFolder) scriptingNodes = [ n for n in self.graph.all_nodes() if n.isScriptingNode ] allGraphNodeNames = [ n.name() for n in scriptingNodes if n.isViableStartNode ] for n in allGraphNodeNames: dialog.startNodeComboBox.addItem(n) if currentStartNodeName in allGraphNodeNames: dialog.startNodeComboBox.setCurrentText(currentStartNodeName) ok = dialog.exec_() graphName = dialog.graphNameLineEdit.text() graphCategory = dialog.categoryComboBox.currentText() if ok and graphName and len(graphName) > 0: startNodeName = dialog.startNodeComboBox.currentText() graphFolder = dialog.folderComboBox.currentText() writeGraph = True if self.graphManager.doesGraphExist(graphName, graphCategory): ret = QMessageBox.question( None, "Name already exists.", "Are you sure you want to overwrite the existing graph with the same name?" ) writeGraph = ret == QMessageBox.Yes if writeGraph: self.graphManager.saveGraph(self.graph, graphFolder, graphName, graphCategory, startNodeName=startNodeName) self.onSaveEvent() def onSave(self): if self.graphManager.curSession != None: self.graphManager.saveCurrentSession() self.onSaveEvent() return self.saveAs() def onSaveAs(self): self.saveAs() def fillListView(self, listView, category): items = self.graphManager.graphCategoryToNamesMap.get(category) if items == None: return model = QStandardItemModel() listView.setModel(model) for item in items: sItem = QStandardItem(item) model.appendRow(sItem) def onLoad(self): dialog = self.loadDialog("loadGraphDialog.ui") self.setupCategoryComboBox(dialog.categoryComboBox) self.fillListView(dialog.graphListView, dialog.categoryComboBox.currentText()) dialog.categoryComboBox.currentIndexChanged.connect( lambda: self.fillListView(dialog.graphListView, dialog.categoryComboBox.currentText())) ok = dialog.exec_() if ok: selectionModel = dialog.graphListView.selectionModel() selectedRows = selectionModel.selectedRows() if len(selectedRows) > 0: listItem = dialog.graphListView.model().item( selectedRows[0].row()) graphName = listItem.text() category = dialog.categoryComboBox.currentText() self.graphManager.loadGraph(self.graph, graphName, category) def setupPropertiesBin(self): self.propertiesBin = PropertiesBinWidget(node_graph=self.graph) self.propertiesBin.setWindowFlags(QtCore.Qt.Tool) # Show node properties on node-double-click: def showPropertyBin(node): if not self.propertiesBin.isVisible(): self.propertiesBin.show() self.graph.node_double_clicked.connect(showPropertyBin) def initNodes(self): for n in node_exec.nodes_cfg.NODE_CLASSES_TO_REGISTER: try: self.graph.register_node(n) except: pass self.graph.register_node(BackdropNode) self.nodeTree = NodeTreeWidget(node_graph=self.graph) self.nodeTree.update() node_exec.nodes_cfg.NODE_CLASS_ADDED_EVENT.subscribe( self.tryRegisterNode) def updateNodeRegistration(self): for n in node_exec.nodes_cfg.NODE_CLASSES_TO_REGISTER: self.tryRegisterNode(n) def tryRegisterNode(self, n): try: self.graph.register_node(n) except: pass def getAsDockWidget(self, parent): self.dockWidget = QtWidgets.QDockWidget("Visual Scripting", parent) self.dockWidget.setWidget(self.window) self.dockWidget.setObjectName("visualScriptingDockWidget") return self.dockWidget def getSettingsAsDockWidget(self): return self.settingsViewer.dockWidget def saveWindowState(self, settings): settings.setValue("visual_scripting_splitter_sizes", self.splitter.saveState()) def restoreWindowState(self, settings): self.splitter.restoreState( settings.value("visual_scripting_splitter_sizes")) def setupDockWidget(self, dockWidget: QtWidgets.QDockWidget): self.dockWidgets.append(dockWidget) # Add visibility checkbox to view main menu: self.viewMenu.addAction(dockWidget.toggleViewAction()) # Allow window functionality (e.g. maximize) dockWidget.topLevelChanged.connect(self.dockWidgetTopLevelChanged) self.setDockWidgetFlags(dockWidget) dockWidget.toggleViewAction() def dockWidgetTopLevelChanged(self, changed): self.setDockWidgetFlags(self.sender()) def setDockWidgetFlags(self, dockWidget): if dockWidget.isFloating(): dockWidget.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.Window | QtCore.Qt.WindowMinimizeButtonHint | QtCore.Qt.WindowMaximizeButtonHint | QtCore.Qt.WindowCloseButtonHint) dockWidget.show()
class MyGraphWindow(QMainWindow): def __init__(self, parent=None): super(MyGraphWindow, self).__init__(parent) mainwidget = QWidget(self) self.setWindowTitle("MyGraph 1.0Alpha") # Build menu self.menubar = QMenuBar(self) file_menu = QMenu("&File") open_action = QAction("&Open", self, shortcut="Ctrl+O", triggered=self.open_session) file_menu.addAction(open_action) self.menubar.addMenu(file_menu) self.menubar.addAction('&Edit') self.view_menu = QMenu('&View') self.menubar.addMenu(self.view_menu) self.panel_submenu = QMenu("&Panels") self.view_menu.addMenu(self.panel_submenu) self.run_menu = self.menubar.addAction('&Run') mainLayout = QHBoxLayout() # main UI self.graph = NodeGraph() self.viewer = self.graph.viewer() mainwidget.setLayout(mainLayout) mainLayout.addWidget(self.viewer) self.setCentralWidget(mainwidget) self.setMenuBar(self.menubar) # set up default menu and commands. setup_context_menu(self.graph) # Add properties bin widget self.properties_bin = PropertiesBinWidget(node_graph=self.graph) self.properties_bin.setWindowFlags(Qt.Tool) side_widget = self.build_right_sidebar() self.add_dockWidget(name="Properties", widget=side_widget, addArea=Qt.RightDockWidgetArea) # Node tree widget self.node_tree = NodeTreeWidget(node_graph=self.graph) self.add_dockWidget(name="Node tree", widget=self.node_tree, addArea=Qt.LeftDockWidgetArea) # Connect to console sys.stdout = EmittingStream() sys.stderr = EmittingStream() sys.stdout.textWritten.connect( lambda text: self.console.append(text, isError=False)) sys.stderr.textWritten.connect( lambda text: self.console.append(text, isError=True)) self.console = widgets.Console() self.add_dockWidget(name="Console", widget=self.console, addArea=Qt.RightDockWidgetArea) self.registryNodes() self.do_connection() self.node_tree.update() def do_connection(self): # Connection def show_prop_bin(node): if not self.properties_bin.isVisible(): self.properties_bin.show() def show_nodes_list(node): if not self.node_tree.isVisible(): self.node_tree.update() self.node_tree.show() self.graph.node_double_clicked.connect(show_prop_bin) self.graph.node_double_clicked.connect(show_nodes_list) self.run_menu.triggered.connect(self.executeNode) self.run_button.clicked.connect(self.executeNode) def add_dockWidget(self, name, widget, allowedAreas=None, addArea=None): if not allowedAreas: allowedAreas = Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea if not addArea: addArea = Qt.LeftDockWidgetArea dockWidget = QDockWidget(name, self) dockWidget.setAllowedAreas(allowedAreas) dockWidget.setWidget(widget) self.addDockWidget(addArea, dockWidget) self.panel_submenu.addAction(dockWidget.toggleViewAction()) def build_right_sidebar(self): widget = QWidget() layout = QVBoxLayout() button_layout = QHBoxLayout() self.save_button = QPushButton(text='Save') self.run_button = QPushButton(text='run') # self.run_button.setMinimumHeight(40) button_layout.addWidget(self.run_button) button_layout.addWidget(self.save_button) button_layout.setStretchFactor(self.run_button, 1) layout.addLayout(button_layout) layout.addWidget(self.properties_bin) layout.setStretchFactor(self.properties_bin, 1) widget.setLayout(layout) return widget def open_session(self): """ Prompts a file open dialog to load a session. Args: graph (NodeGraphQt.NodeGraph): node graph. """ graph = self.graph current = graph.current_session() viewer = graph.viewer() file_path = viewer.load_dialog(current) if file_path: graph.load_session(file_path) def executeNode(self): ''' Execute selected node Returns: ''' _selectedNode = self.graph.selected_nodes() # type: list if not _selectedNode: QMessageBox.warning(self, 'Warning', "No selected node.") return elif len(_selectedNode) > 1: QMessageBox.warning(self, 'Warning', "Cannot execute multiple node.") return else: selected_node = _selectedNode[0] msgBox = QMessageBox.information( self, self.tr("Run"), "You want to run selected node?.\n>> %s" % selected_node.NODE_NAME, QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Yes) if msgBox == QMessageBox.Yes: command.executeNode(selected_node) def registryNodes(self): # Find all node class in given node directory, then registry to graph try: for node in os.listdir(nodesDir): if not node.endswith('.py') or node.startswith('.'): continue __modulepath = nodesDir + '/' + node __modulename = os.path.splitext(node)[0] try: # importlib.import_module(__modulename, __modulepath) imp.load_source(__modulename, __modulepath) except ImportError as e: print(traceback.format_exc(e)) raise (e) for name, obj in inspect.getmembers(sys.modules[__modulename]): if inspect.isclass(obj): if issubclass(obj, BaseNode) and obj != BaseNode: self.graph.register_node(obj) except Exception as e: print traceback.format_exc(e) print(e) self.graph.register_node(BackdropNode)