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()
# show the properties bin when a node is "double clicked" in the graph. properties_bin = PropertiesBinWidget(node_graph=graph) properties_bin.setWindowFlags(QtCore.Qt.Tool) def show_prop_bin(node): if not properties_bin.isVisible(): properties_bin.show() graph.node_double_clicked.connect(show_prop_bin) # show the nodes list when a node is "double clicked" in the graph. node_tree = NodeTreeWidget(node_graph=graph) def show_nodes_list(node): if not node_tree.isVisible(): node_tree.update() node_tree.show() graph.node_double_clicked.connect(show_nodes_list) # registered nodes. [graph.register_node(n) for n in Nodes] # load preset session graph.load_session(r'example_nodes\networks\example.nodes') # update nodes update_nodes_by_down(graph.all_nodes()) app.exec_()