Ejemplo n.º 1
0
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()
Ejemplo n.º 2
0
    # 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_()