Beispiel #1
0
    def __init__(self, parent=None):
        super(PyFlow, self).__init__(parent=parent)
        self.setupUi(self)
        self.setWindowIcon(QtGui.QIcon(":/LogoBpApp.png"))
        self._tools = set()
        self.currentTempDir = ""

        self.preferencesWindow = PreferencesWindow(self)
        self.graphManager = GraphManager()
        self.canvasWidget = Canvas(self.graphManager, self)
        self.canvasWidget.requestFillProperties.connect(
            self.onRequestFillProperties)
        self.canvasWidget.requestClearProperties.connect(
            self.onRequestClearProperties)
        self.graphManager.graphChanged.connect(self.updateGraphTreeLocation)
        self.updateGraphTreeLocation()
        self.SceneLayout.addWidget(self.canvasWidget)

        rxLettersAndNumbers = QtCore.QRegExp('^[a-zA-Z0-9]*$')
        nameValidator = QtGui.QRegExpValidator(rxLettersAndNumbers,
                                               self.leCompoundName)
        self.leCompoundName.setValidator(nameValidator)
        self.leCompoundName.returnPressed.connect(
            self.onActiveCompoundNameAccepted)

        rxLetters = QtCore.QRegExp('^[a-zA-Z]*$')
        categoryValidator = QtGui.QRegExpValidator(rxLetters,
                                                   self.leCompoundCategory)
        self.leCompoundCategory.setValidator(categoryValidator)
        self.leCompoundCategory.returnPressed.connect(
            self.onActiveCompoundCategoryAccepted)

        self.setMouseTracking(True)

        self._lastClock = 0.0
        self.fps = EDITOR_TARGET_FPS
        self.tick_timer = QtCore.QTimer()
        self._current_file_name = 'Untitled'
        self.populateMenu()
Beispiel #2
0
def run(filePath):
    app = QApplication(sys.argv)
    app.setStyle(QStyleFactory.create("plastique"))
    app.setStyleSheet(editableStyleSheet().getStyleSheet())

    msg = QMessageBox()
    msg.setWindowIcon(QtGui.QIcon(":/LogoBpApp.png"))
    msg.setIcon(QMessageBox.Critical)

    if os.path.exists(filePath):
        with open(filePath, 'r') as f:
            data = json.load(f)

        # Window to display inputs
        prop = QDialog()
        prop.setLayout(QVBoxLayout())
        prop.setWindowTitle(filePath)
        prop.setWindowIcon(QtGui.QIcon(":/LogoBpApp.png"))

        # Initalize packages
        try:
            INITIALIZE()
            man = GraphManager()
            man.deserialize(data)
            grph = man.findRootGraph()
            inputs = grph.getNodesByClassName("graphInputs")

            # If no GraphInput Nodes Exit propgram
            if len(inputs) > 0:
                for inp in inputs:
                    uiNode = getUINodeInstance(inp)
                    uiNodeJsonTemplate = inp.serialize()
                    uiNodeJsonTemplate["wrapper"] = inp.wrapperJsonData
                    uiNode.postCreate(uiNodeJsonTemplate)
                    cat = uiNode.createOutputWidgets(prop.layout(), inp.name)
                    prop.show()

                def programLoop():
                    while True:
                        man.Tick(deltaTime=0.02)
                        time.sleep(0.02)
                        if man.terminationRequested:
                            break

                t = threading.Thread(target=programLoop)
                t.start()

                def quitEvent():
                    man.terminationRequested = True
                    t.join()

                app.aboutToQuit.connect(quitEvent)
            else:
                msg.setInformativeText(filePath)
                msg.setDetailedText(
                    "The file doesn't containt graphInputs nodes")
                msg.setWindowTitle("PyFlow Ui Graph Parser")
                msg.setStandardButtons(QMessageBox.Ok)
                msg.show()

        except Exception as e:
            msg.setText("Error reading Graph")
            msg.setInformativeText(filePath)
            msg.setDetailedText(str(e))
            msg.setWindowTitle("PyFlow Ui Graph Parser")
            msg.setStandardButtons(QMessageBox.Ok)
            msg.show()

    else:
        msg.setText("File Not Found")
        msg.setInformativeText(filePath)
        msg.setWindowTitle("PyFlow Ui Graph Parser")
        msg.setStandardButtons(QMessageBox.Ok)
        msg.show()

    try:
        sys.exit(app.exec_())
    except Exception as e:
        print(e)
Beispiel #3
0
class PyFlow(QMainWindow, GraphEditor_ui.Ui_MainWindow):
    newFileExecuted = QtCore.Signal(bool)

    def __init__(self, parent=None):
        super(PyFlow, self).__init__(parent=parent)
        self.setupUi(self)
        self.setWindowIcon(QtGui.QIcon(":/LogoBpApp.png"))
        self._tools = set()
        self.currentTempDir = ""

        self.preferencesWindow = PreferencesWindow(self)
        self.graphManager = GraphManager()
        self.canvasWidget = Canvas(self.graphManager, self)
        self.canvasWidget.requestFillProperties.connect(
            self.onRequestFillProperties)
        self.canvasWidget.requestClearProperties.connect(
            self.onRequestClearProperties)
        self.graphManager.graphChanged.connect(self.updateGraphTreeLocation)
        self.updateGraphTreeLocation()
        self.SceneLayout.addWidget(self.canvasWidget)

        rxLettersAndNumbers = QtCore.QRegExp('^[a-zA-Z0-9]*$')
        nameValidator = QtGui.QRegExpValidator(rxLettersAndNumbers,
                                               self.leCompoundName)
        self.leCompoundName.setValidator(nameValidator)
        self.leCompoundName.returnPressed.connect(
            self.onActiveCompoundNameAccepted)

        rxLetters = QtCore.QRegExp('^[a-zA-Z]*$')
        categoryValidator = QtGui.QRegExpValidator(rxLetters,
                                                   self.leCompoundCategory)
        self.leCompoundCategory.setValidator(categoryValidator)
        self.leCompoundCategory.returnPressed.connect(
            self.onActiveCompoundCategoryAccepted)

        self.setMouseTracking(True)

        self._lastClock = 0.0
        self.fps = EDITOR_TARGET_FPS
        self.tick_timer = QtCore.QTimer()
        self._current_file_name = 'Untitled'
        self.populateMenu()

    def getTempDirectory(self):
        """Returns unique temp directory for application instance.

        This folder and all it's content will be removed from disc on application shutdown.
        """
        return self.currentTempDir

    def populateMenu(self):
        fileMenu = self.menuBar.addMenu("File")
        newFileAction = fileMenu.addAction("New file")
        newFileAction.setIcon(QtGui.QIcon(":/new_file_icon.png"))
        newFileAction.triggered.connect(self.newFile)

        loadAction = fileMenu.addAction("Load")
        loadAction.setIcon(QtGui.QIcon(":/folder_open_icon.png"))
        loadAction.triggered.connect(self.load)

        saveAction = fileMenu.addAction("Save")
        saveAction.setIcon(QtGui.QIcon(":/save_icon.png"))
        saveAction.triggered.connect(self.save)

        saveAsAction = fileMenu.addAction("Save as")
        saveAsAction.setIcon(QtGui.QIcon(":/save_as_icon.png"))
        saveAsAction.triggered.connect(lambda: self.save(True))

        editMenu = self.menuBar.addMenu("Edit")
        preferencesAction = editMenu.addAction("Preferences")
        preferencesAction.setIcon(QtGui.QIcon(":/options_icon.png"))
        preferencesAction.triggered.connect(self.showPreferencesWindow)

        helpMenu = self.menuBar.addMenu("Help")
        shortcutsAction = helpMenu.addAction("Shortcuts")
        shortcutsAction.setIcon(QtGui.QIcon(":/shortcuts_icon.png"))
        shortcutsAction.triggered.connect(self.shortcuts_info)

    def showPreferencesWindow(self):
        self.preferencesWindow.show()

    def registerToolInstance(self, instance):
        """Registers tool instance reference

        This needed to prevent classes from being garbage collected and to save widgets state

        Args:

            instance (ToolBase): Tool to be registered
        """
        self._tools.add(instance)

    def unregisterToolInstance(self, instance):
        if instance in self._tools:
            self._tools.remove(instance)

    def onRequestFillProperties(self, propertiesFillDelegate):
        for toolInstance in self._tools:
            if isinstance(toolInstance, PropertiesTool):
                toolInstance.clear()
                toolInstance.assignPropertiesWidget(propertiesFillDelegate)

    def onRequestClearProperties(self):
        for toolInstance in self._tools:
            if isinstance(toolInstance, PropertiesTool):
                toolInstance.clear()

    def getToolbar(self):
        return self.toolBar

    def getCanvas(self):
        return self.canvasWidget

    def setCompoundPropertiesWidgetVisible(self, bVisible):
        if bVisible:
            self.CompoundPropertiesWidget.show()
            self.leCompoundName.setText(self.graphManager.activeGraph().name)
            self.leCompoundCategory.setText(
                self.graphManager.activeGraph().category)
        else:
            self.CompoundPropertiesWidget.hide()

    def keyPressEvent(self, event):
        modifiers = event.modifiers()
        currentInputAction = InputAction(name="temp",
                                         actionType=InputActionType.Keyboard,
                                         key=event.key(),
                                         modifiers=modifiers)

        actionSaveVariants = InputManager()["App.Save"]
        actionNewFileVariants = InputManager()["App.NewFile"]
        actionLoadVariants = InputManager()["App.Load"]
        actionSaveAsVariants = InputManager()["App.SaveAs"]

        if currentInputAction in actionNewFileVariants:
            self.newFile()
        if currentInputAction in actionSaveVariants:
            self.save()
        if currentInputAction in actionLoadVariants:
            self.load()
        if currentInputAction in actionSaveAsVariants:
            self.save_as()

    def loadFromData(self, data, fpath=""):
        self.newFile(keepRoot=False)
        # load raw data
        self.graphManager.deserialize(data)
        # create ui nodes
        for graph in self.graphManager.getAllGraphs():
            self.canvasWidget.createWrappersForGraph(graph)
        self.graphManager.selectRootGraph()

    def load(self):
        name_filter = "Graph files (*.json)"
        savepath = QFileDialog.getOpenFileName(filter=name_filter)
        if type(savepath) in [tuple, list]:
            fpath = savepath[0]
        else:
            fpath = savepath
        if not fpath == '':
            with open(fpath, 'r') as f:
                data = json.load(f)
                self.loadFromData(data, fpath)

    def save(self, save_as=False):
        if save_as:
            name_filter = "Graph files (*.json)"
            savepath = QFileDialog.getSaveFileName(filter=name_filter)
            if type(savepath) in [tuple, list]:
                pth = savepath[0]
            else:
                pth = savepath
            if not pth == '':
                self._current_file_name = pth
            else:
                self._current_file_name = "Untitled"
        else:
            if not os.path.isfile(self._current_file_name):
                name_filter = "Graph files (*.json)"
                savepath = QFileDialog.getSaveFileName(filter=name_filter)
                if type(savepath) in [tuple, list]:
                    pth = savepath[0]
                else:
                    pth = savepath
                if not pth == '':
                    self._current_file_name = pth
                else:
                    self._current_file_name = "Untitled"

        if self._current_file_name in ["", "Untitled"]:
            return

        if not self._current_file_name == '':
            with open(self._current_file_name, 'w') as f:
                saveData = self.graphManager.serialize()
                json.dump(saveData, f, indent=4)

            print(str("// saved: '{0}'".format(self._current_file_name)))

    def newFile(self, keepRoot=True):
        self.tick_timer.stop()
        self.tick_timer.timeout.disconnect()

        # broadcast
        self.graphManager.clear(keepRoot=keepRoot)
        self.newFileExecuted.emit(keepRoot)
        self._current_file_name = 'Untitled'
        self.onRequestClearProperties()

        self.startMainLoop()

    def updateGraphTreeLocation(self, *args, **kwargs):
        location = self.canvasWidget.location()
        clearLayout(self.layoutGraphPath)
        spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding,
                                 QSizePolicy.Minimum)
        self.layoutGraphPath.addItem(spacerItem)
        for folderName in location:
            index = self.layoutGraphPath.count() - 1
            btn = QPushButton(folderName)

            def onClicked(checked, name=None):
                self.canvasWidget.stepToCompound(name)

            btn.clicked.connect(
                lambda chk=False, name=folderName: onClicked(chk, name))
            self.layoutGraphPath.insertWidget(index, btn)

        self.setCompoundPropertiesWidgetVisible(
            self.graphManager.activeGraph().depth() > 1)

    def onActiveCompoundNameAccepted(self):
        newName = self.graphManager.getUniqName(self.leCompoundName.text())
        self.graphManager.activeGraph().name = newName
        self.leCompoundName.blockSignals(True)
        self.leCompoundName.setText(newName)
        self.leCompoundName.blockSignals(False)
        self.updateGraphTreeLocation()

    def onActiveCompoundCategoryAccepted(self):
        newCategoryName = self.leCompoundCategory.text()
        self.graphManager.activeGraph().category = newCategoryName

    def startMainLoop(self):
        self.tick_timer.timeout.connect(self.mainLoop)
        self.tick_timer.start(1000 / EDITOR_TARGET_FPS)

    def stopMainLoop(self):
        self.tick_timer.stop()
        self.tick_timer.timeout.disconnect()

    def mainLoop(self):
        deltaTime = clock() - self._lastClock
        ds = (deltaTime * 1000.0)
        if ds > 0:
            self.fps = int(1000.0 / ds)

        # Tick all graphs
        # each graph will tick owning raw nodes
        # each raw node will tick it's ui wrapper if it exists
        self.graphManager.Tick(deltaTime)

        # Tick canvas. Update ui only stuff such animation etc.
        self.canvasWidget.Tick(deltaTime)

        self._lastClock = clock()

    def createPopupMenu(self):
        pass

    def newPlugin(self, pluginType):
        name, result = QInputDialog.getText(self, 'Plugin name',
                                            'Enter plugin name')
        if result:
            _implementPlugin(name, pluginType)

    def getToolClassByName(self, packageName, toolName, toolClass=DockTool):
        registeredTools = GET_TOOLS()
        for ToolClass in registeredTools[packageName]:
            if issubclass(ToolClass, toolClass):
                if ToolClass.name() == toolName:
                    return ToolClass
        return None

    def createToolInstanceByClass(self,
                                  packageName,
                                  toolName,
                                  toolClass=DockTool):
        registeredTools = GET_TOOLS()
        for ToolClass in registeredTools[packageName]:
            if issubclass(ToolClass, toolClass):
                if ToolClass.name() == toolName:
                    return ToolClass()
        return None

    def invokeDockToolByName(self, packageName, name, settings=None):
        # invokeDockToolByName Invokes dock tool by tool name and package name
        # If settings provided QMainWindow::restoreDockWidget will be called instead QMainWindow::addDockWidget
        toolClass = self.getToolClassByName(packageName, name, DockTool)
        isSingleton = toolClass.isSingleton()
        if isSingleton:
            # check if already registered
            if name in [t.name() for t in self._tools]:
                for tool in self._tools:
                    if tool.name() == name:
                        # Highlight window
                        print("highlight", tool.uniqueName())
                return
        ToolInstance = self.createToolInstanceByClass(packageName, name,
                                                      DockTool)
        if ToolInstance:
            self.registerToolInstance(ToolInstance)
            if settings is not None:
                ToolInstance.restoreState(settings)
                if not self.restoreDockWidget(ToolInstance):
                    # handle if ui state was not restored
                    pass
            else:
                self.addDockWidget(ToolInstance.defaultDockArea(),
                                   ToolInstance)
            ToolInstance.setCanvas(self.canvasWidget)
            ToolInstance.onShow()
        return ToolInstance

    def closeEvent(self, event):
        self.tick_timer.stop()
        self.tick_timer.timeout.disconnect()
        self.canvasWidget.shoutDown()
        # save editor config
        settings = QtCore.QSettings(ConfigManager().APP_SETTINGS_PATH,
                                    QtCore.QSettings.IniFormat, self)
        # clear file each time to capture opened dock tools
        settings.clear()
        settings.sync()

        settings.beginGroup('Editor')
        settings.setValue("geometry", self.saveGeometry())
        settings.setValue("state", self.saveState())
        settings.endGroup()

        # save tools state
        settings.beginGroup('Tools')
        for tool in self._tools:
            if isinstance(tool, ShelfTool):
                settings.beginGroup("ShelfTools")
                settings.beginGroup(tool.name())
                tool.saveState(settings)
                settings.endGroup()
                settings.endGroup()
            if isinstance(tool, DockTool):
                settings.beginGroup("DockTools")
                settings.beginGroup(tool.uniqueName())
                tool.saveState(settings)
                settings.endGroup()
                settings.endGroup()
            tool.onDestroy()
        settings.endGroup()
        settings.sync()

        # remove temp directory
        shutil.rmtree(self.currentTempDir, ignore_errors=True)

        QMainWindow.closeEvent(self, event)

    def shortcuts_info(self):

        data = "Tab - togle node box\n"
        data += "Ctrl+N - new file\n"
        data += "Ctrl+S - save\n"
        data += "Ctrl+Shift+S - save as\n"
        data += "Ctrl+O - open file\n"
        data += "F - frame selected\n"
        data += "H - frame all\n"
        data += "C - comment selected nodes\n"
        data += "Delete - kill selected nodes\n"
        data += "Ctrl+C - Copy\n"
        data += "Ctrl+V - Paste\n"
        data += "Alt+Drag - Duplicate\n"
        data += "Ctrl+Z - Undo\n"
        data += "Ctrl+Y - Redo\n"
        data += "Alt+Click - Disconnect Pin\n"
        data += "Ctrl+Shift+ArrowLeft - Align left\n"
        data += "Ctrl+Shift+ArrowUp - Align Up\n"
        data += "Ctrl+Shift+ArrowRight - Align right\n"
        data += "Ctrl+Shift+ArrowBottom - Align Bottom\n"

        QMessageBox.information(self, "Shortcuts", data)

    @staticmethod
    def instance(parent=None):
        instance = PyFlow(parent)
        instance.startMainLoop()
        INITIALIZE()

        # create app folder in documents
        # random string used for cases when multiple instances of app are running in the same time
        prefs = QtCore.QSettings(ConfigManager().PREFERENCES_CONFIG_PATH,
                                 QtCore.QSettings.IniFormat)
        tempDirPath = prefs.value("Preferences/General/TempFilesDir")
        if tempDirPath == None:
            tempDirPath = "/tmp/pyflow"
        if tempDirPath[-1:] in ('/', '\\'):
            tempDirPath = tempDirPath[:-1]
        instance.currentTempDir = "{0}_{1}".format(tempDirPath,
                                                   generateRandomString())

        if not os.path.exists(instance.currentTempDir):
            os.makedirs(instance.currentTempDir)

        # populate tools
        canvas = instance.getCanvas()
        toolbar = instance.getToolbar()

        settings = QtCore.QSettings(ConfigManager().APP_SETTINGS_PATH,
                                    QtCore.QSettings.IniFormat)
        geo = settings.value('Editor/geometry')
        if geo is not None:
            instance.restoreGeometry(geo)
        state = settings.value('Editor/state')
        if state is not None:
            instance.restoreState(state)
        settings.beginGroup("Tools")
        for packageName, registeredToolSet in GET_TOOLS().items():
            for ToolClass in registeredToolSet:
                if issubclass(ToolClass, ShelfTool):
                    ToolInstance = ToolClass()
                    # prevent to be garbage collected
                    instance.registerToolInstance(ToolInstance)
                    ToolInstance.setCanvas(canvas)
                    action = QAction(instance)
                    action.setIcon(ToolInstance.getIcon())
                    action.setText(ToolInstance.name())
                    action.setToolTip(ToolInstance.toolTip())
                    action.setObjectName(ToolInstance.name())
                    action.triggered.connect(ToolInstance.do)
                    # check if context menu data available
                    menuBuilder = ToolInstance.contextMenuBuilder()
                    if menuBuilder:
                        menuGenerator = ContextMenuGenerator(menuBuilder)
                        menu = menuGenerator.generate()
                        action.setMenu(menu)
                    toolbar.addAction(action)

                    # step to ShelfTools/ToolName group and pass settings inside
                    settings.beginGroup("ShelfTools")
                    settings.beginGroup(ToolClass.name())
                    ToolInstance.restoreState(settings)
                    settings.endGroup()
                    settings.endGroup()

                if issubclass(ToolClass, DockTool):
                    menus = instance.menuBar.findChildren(QMenu)
                    helpMenuAction = [m for m in menus
                                      if m.title() == "Help"][0].menuAction()
                    toolsMenu = getOrCreateMenu(instance.menuBar, "Tools")
                    instance.menuBar.insertMenu(helpMenuAction, toolsMenu)
                    packageSubMenu = getOrCreateMenu(toolsMenu, packageName)
                    toolsMenu.addMenu(packageSubMenu)
                    showToolAction = packageSubMenu.addAction(ToolClass.name())
                    icon = ToolClass.getIcon()
                    if icon:
                        showToolAction.setIcon(icon)
                    showToolAction.triggered.connect(
                        lambda pkgName=packageName, toolName=ToolClass.name(
                        ): instance.invokeDockToolByName(pkgName, toolName))

                    settings.beginGroup("DockTools")
                    childGroups = settings.childGroups()
                    for dockToolGroupName in childGroups:
                        # This dock tool data been saved on last shutdown
                        settings.beginGroup(dockToolGroupName)
                        if dockToolGroupName in [
                                t.uniqueName() for t in instance._tools
                        ]:
                            continue
                        toolName = dockToolGroupName.split("::")[0]
                        ToolInstance = instance.invokeDockToolByName(
                            packageName, toolName, settings)
                        settings.endGroup()
                    settings.endGroup()

        return instance
Beispiel #4
0
    if not fpath == '':
        with open(fpath, 'r') as f:
            data = json.load(f)
        # Window to display Inputs
        prop = QDialog()
        prop.setLayout(QVBoxLayout())
        prop.setWindowTitle(fpath)
        prop.setWindowIcon(QtGui.QIcon(":/LogoBpApp.png"))
        msg = QMessageBox()
        msg.setWindowIcon(QtGui.QIcon(":/LogoBpApp.png"))
        msg.setIcon(QMessageBox.Critical)
        # Initalize Packages
        try:
            INITIALIZE()
            man = GraphManager()
            man.deserialize(data)
            grph = man.findRootGraph()
            inputs = grph.getNodesByClassName("graphInputs")
            # If no GraphInput Nodes Exit propgram
            if len(inputs) > 0:
                for inp in inputs:
                    uiNode = getUINodeInstance(inp)
                    uiNodeJsonTemplate = inp.serialize()
                    uiNodeJsonTemplate["wrapper"] = inp.wrapperJsonData
                    uiNode.postCreate(uiNodeJsonTemplate)
                    cat = uiNode.createOutputWidgets(prop.layout(), inp.name)
                    prop.show()
            else:
                msg.setInformativeText(fpath)
                msg.setDetailedText(