diff --git a/buttleofx/MainWindow.qml b/buttleofx/MainWindow.qml index 93ff2ccf..31d4e17f 100644 --- a/buttleofx/MainWindow.qml +++ b/buttleofx/MainWindow.qml @@ -381,18 +381,8 @@ ApplicationWindow { text: object.pluginLabel onTriggered: { - _buttleData.currentGraphIsGraph() - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - - // If before the viewer was showing an image from the brower, we change the currentView - if (_buttleData.currentViewerIndex > 9) { - _buttleData.currentViewerIndex = player.lastView - if (player.lastNodeWrapper != undefined) - _buttleData.currentViewerNodeWrapper = player.lastNodeWrapper - player.changeViewer(player.lastView) - } - - _buttleManager.nodeManager.creationNode("_buttleData.graph", object.pluginType, 0, 0) + _buttleData.setActiveGraphId("graphEditor") + _buttleManager.nodeManager.creationNode("graphEditor", object.pluginType, 0, 0) } } @@ -415,18 +405,8 @@ ApplicationWindow { text: object.pluginLabel onTriggered: { - _buttleData.currentGraphIsGraph() - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - - // If before the viewer was showing an image from the brower, we change the currentView - if (_buttleData.currentViewerIndex > 9) { - _buttleData.currentViewerIndex = player.lastView - if (player.lastNodeWrapper != undefined) - _buttleData.currentViewerNodeWrapper = player.lastNodeWrapper - player.changeViewer(player.lastView) - } - - _buttleManager.nodeManager.creationNode("_buttleData.graph", object.pluginType, 0, 0) + _buttleData.setActiveGraphId("graphEditor") + _buttleManager.nodeManager.creationNode("graphEditor", object.pluginType, 0, 0) } } @@ -449,18 +429,8 @@ ApplicationWindow { text: object.pluginLabel onTriggered: { - _buttleData.currentGraphIsGraph() - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - - // If before the viewer was showing an image from the brower, we change the currentView - if (_buttleData.currentViewerIndex > 9) { - _buttleData.currentViewerIndex = player.lastView - if (player.lastNodeWrapper != undefined) - _buttleData.currentViewerNodeWrapper = player.lastNodeWrapper - player.changeViewer(player.lastView) - } - - _buttleManager.nodeManager.creationNode("_buttleData.graph", object.pluginType, 0, 0) + _buttleData.setActiveGraphId("graphEditor") + _buttleManager.nodeManager.creationNode("graphEditor", object.pluginType, 0, 0) } } @@ -483,18 +453,8 @@ ApplicationWindow { text: object.pluginLabel onTriggered: { - _buttleData.currentGraphIsGraph() - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - - // If before the viewer was showing an image from the brower, we change the currentView - if (_buttleData.currentViewerIndex > 9) { - _buttleData.currentViewerIndex = player.lastView - if (player.lastNodeWrapper != undefined) - _buttleData.currentViewerNodeWrapper = player.lastNodeWrapper - player.changeViewer(player.lastView) - } - - _buttleManager.nodeManager.creationNode("_buttleData.graph", object.pluginType, 0, 0) + _buttleData.setActiveGraphId("graphEditor") + _buttleManager.nodeManager.creationNode("graphEditor", object.pluginType, 0, 0) } } @@ -517,18 +477,8 @@ ApplicationWindow { text: object.pluginLabel onTriggered: { - _buttleData.currentGraphIsGraph() - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - - // If before the viewer was showing an image from the brower, we change the currentView - if (_buttleData.currentViewerIndex > 9) { - _buttleData.currentViewerIndex = player.lastView - if (player.lastNodeWrapper != undefined) - _buttleData.currentViewerNodeWrapper = player.lastNodeWrapper - player.changeViewer(player.lastView) - } - - _buttleManager.nodeManager.creationNode("_buttleData.graph", object.pluginType, 0, 0) + _buttleData.setActiveGraphId("graphEditor") + _buttleManager.nodeManager.creationNode("graphEditor", object.pluginType, 0, 0) } } diff --git a/buttleofx/core/graph/graph.py b/buttleofx/core/graph/graph.py index 6e5bbc35..8fdbd99c 100644 --- a/buttleofx/core/graph/graph.py +++ b/buttleofx/core/graph/graph.py @@ -97,7 +97,7 @@ def createConnection(self, clipOut, clipIn): def createNode(self, nodeType, x=20, y=20): """ - Adds a node from the node list when a node is created. + Add a node from the node list when a node is created. """ cmdCreateNode = CmdCreateNode(self, nodeType, x, y) cmdManager = globalCommandManager @@ -105,14 +105,14 @@ def createNode(self, nodeType, x=20, y=20): def createReaderNode(self, url, x, y): """ - Creates a reader node when an image has been dropped in the graph. + Create a reader node from an image url to specific coordinates. """ (_, extension) = os.path.splitext(url) try: nodeType = tuttle.getBestReader(extension) except Exception: logging.debug("Unknown format. Can't create the reader node for extension '%s'.", extension) - return + return None # We create the node. # We can't use a group of commands because we need the tuttle node to set the value, and this tuttle node is @@ -120,8 +120,7 @@ def createReaderNode(self, url, x, y): # creates a new node and set its value with the correct url. # See the definition of the class CmdCreateReaderNode. cmdCreateReaderNode = CmdCreateReaderNode(self, nodeType, x, y, url) - cmdManager = globalCommandManager - return cmdManager.push(cmdCreateReaderNode) + return globalCommandManager.push(cmdCreateReaderNode) def deleteConnection(self, connection): """ @@ -129,8 +128,7 @@ def deleteConnection(self, connection): Pushes a command in the CommandManager. """ cmdDeleteConnection = CmdDeleteConnection(self, connection) - cmdManager = globalCommandManager - cmdManager.push(cmdDeleteConnection) + globalCommandManager.push(cmdDeleteConnection) def deleteNodeConnections(self, nodeName): """ @@ -147,8 +145,7 @@ def deleteNodes(self, nodes): Pushes a command in the CommandManager. """ cmdDeleteNodes = CmdDeleteNodes(self, nodes) - cmdManager = globalCommandManager - cmdManager.push(cmdDeleteNodes) + globalCommandManager.push(cmdDeleteNodes) # ## Others ## # @@ -174,44 +171,59 @@ def nodesConnected(self, clipOut, clipIn): return True return False - def nodeMoved(self, nodeName, newX, newY): + def moveSelectedNodes(self, offsetX, offsetY): """ - This function pushes a cmdMoved in the globalCommandManager. + Move all selected nodes with an offset. """ from buttleofx.data import globalButtleData - node = globalButtleData.getCurrentGraph().getNode(nodeName) - if not node: - logging.debug("no nodes nodeMoved -- graph : %s", globalButtleData.getCurrentGraph()) - - # What is the value of the movement (compared to the old position)? - oldX, oldY = node.getOldCoord() - xMovement = newX - oldX - yMovement = newY - oldY - - print(oldX, oldY) - print(newX, newY) # If the node didn't really move, nothing is done - if (xMovement, xMovement) == (0, 0): + if (offsetX, offsetY) == (0, 0): return commands = [] - # We create a GroupUndoableCommands of CmdSetCoord for each selected node for selectedNodeWrapper in globalButtleData.getCurrentSelectedNodeWrappers(): - # We get the needed informations for this node + # We get the needed information for this node selectedNode = selectedNodeWrapper.getNode() selectedNodeName = selectedNode.getName() oldX, oldY = selectedNode.getOldCoord() # We set the new coordinates of the node (each selected node is doing the same movement) - cmdMoved = CmdSetCoord(self, selectedNodeName, (oldX + xMovement, oldY + yMovement)) + cmdMoved = CmdSetCoord(self, selectedNodeName, (oldX + offsetX, oldY + offsetY)) commands.append(cmdMoved) # Then we push the group of commands globalCommandManager.push(GroupUndoableCommands(commands, "Move nodes")) + def moveNode(self, nodeName, newX, newY): + """ + Move all selected nodes to get nodeName at a specific position. + """ + from buttleofx.data import globalButtleData + node = globalButtleData.getActiveGraph().getNode(nodeName) + if not node: + logging.debug("no nodes moveNode -- graph : %s", globalButtleData.getActiveGraph()) + return + + # What is the value of the movement (compared to the old position)? + oldX, oldY = node.getOldCoord() + + logging.debug("Move node from (%s, %s) to (%s, %s)", oldX, oldY, newX, newY) + + self.moveSelectedNodes(newX - oldX, newY - oldY) + + def hardClear(self): + """ + Clear all nodes and connections. + Also remove all the nodes from the TuttleGraph. + :warning: that can break the undo stack. + """ + self._graphTuttle.clear() + self._nodes = [] + self._connections = [] + def object_to_dict(self): """ Convert the graph to a dictionary of his representation. @@ -258,7 +270,7 @@ def dict_to_object(self, graphData): def __str__(self): """ Displays on terminal some data. - Usefull to debug the class. + Useful to debug the class. """ str_list = [] diff --git a/buttleofx/data/buttleData.py b/buttleofx/data/buttleData.py index ed902020..aefd7180 100644 --- a/buttleofx/data/buttleData.py +++ b/buttleofx/data/buttleData.py @@ -74,7 +74,7 @@ class ButtleData(QtCore.QObject): _currentViewerIndex = 1 _currentViewerFrame = 0 _currentViewerNodeName = None - _mapViewerIndextoNodeName = {} + _mapViewerIndexToNodeName = {} _mapNodeNameToComputedImage = {} # Boolean used in viewerManager @@ -86,27 +86,32 @@ class ButtleData(QtCore.QObject): _urlOfFileToSave = "" - def init(self, view, filePath=''): - self._graph = Graph() - self._graphWrapper = GraphWrapper(self._graph, view) + def init(self, parent, buttlePath): + """ + There is only an empty default constructor and this initialization function should be called manually. - self._graphBrowser = Graph() - self._graphBrowserWrapper = GraphWrapper(self._graphBrowser, view) + :param parent: Parent Qt object + :param buttlePath: Path to the buttle python source code. + :return: + """ + self._graphWrapper = GraphWrapper(Graph(), parent) + self._graphBrowserWrapper = GraphWrapper(Graph(), parent) self._listOfShortcut = QObjectListModel(self) self._mapGraph = { - "graph": self._graph, - "graphBrowser": self._graphBrowser, + "graphEditor": self._graphWrapper, + "graphBrowser": self._graphBrowserWrapper, } - self._currentGraph = self._graph # By default, the current graph is the graph of the graphEditor - self._currentGraphWrapper = self._graphWrapper # By default, the current graph is the graph of the graphEditor - self._buttlePath = filePath + # By default, the active graph is the graphEditor + self._activeGraphId = "graphEditor" + + self._buttlePath = buttlePath # 9 views for the viewer, the 10th for the browser, the 11th temporary for index in range(1, 12): - self._mapViewerIndextoNodeName[str(index)] = None + self._mapViewerIndexToNodeName[str(index)] = None return self @@ -128,15 +133,13 @@ def getFileName(self, path): @QtCore.pyqtSlot(int, result=int) def getFrameByViewerIndex(self, index): """ - Returns the frame of the node contained in the viewer at the corresponding index. + Returns the frame (time) of the node contained in the viewer at the corresponding index. """ - # We get info about this node. It's a tuple (fodeName, frame), so we have to return - # the second element nodeViewerInfos[1]. - nodeViewerInfos = self._mapViewerIndextoNodeName[str(index)] + nodeViewerInfos = self._mapViewerIndexToNodeName[str(index)] if nodeViewerInfos is None: return 0 - else: - return nodeViewerInfos[1] + # It's a tuple (nodeName, frame), so we have to return the second element nodeViewerInfos[1]. + return nodeViewerInfos[1] @QtCore.pyqtSlot(result=QtCore.QObject) def getlistOfContext(self): @@ -232,13 +235,12 @@ def getNodeWrapperByViewerIndex(self, index): """ Returns the nodeWrapper of the node contained in the viewer at the corresponding index. """ - # We get info about this node. It's a tuple (fodeName, frame), so we have to return + # We get info about this node. It's a tuple (nodeName, frame), so we have to return # the first element nodeViewerInfos[0]. - nodeViewerInfos = self._mapViewerIndextoNodeName[str(index)] + nodeViewerInfos = self._mapViewerIndexToNodeName[str(index)] if nodeViewerInfos is None: return None - else: - return self._currentGraphWrapper.getNodeWrapper(nodeViewerInfos[0]) + return self._currentGraphWrapper.getNodeWrapper(nodeViewerInfos[0]) @QtCore.pyqtSlot(result=QtCore.QObject) def getParentNodes(self): @@ -378,55 +380,57 @@ def getSinglePluginSuggestion(self, pluginSearched): @QtCore.pyqtSlot(result=QtCore.QObject) def getSortedNodesWrapper(self): """ - Returns the total of sorted param nodeWrapper for the parametersEditor. + Returns all nodeWrapper for the ParametersEditor. """ listOfNodes = QObjectListModel(self) - if len(self._graphWrapper.getNodeWrappers()) != 0: - for nodes in self._graphWrapper.getNodeWrappers(): - if nodes.pluginContext == "OfxImageEffectContextReader": - listOfNodes.append(nodes) - firstNode = nodes - else: - firstNode = self._graphWrapper.getNodeWrappers()[0] - - toVisit = set() - visited = set() - toVisit.add(firstNode) - while len(toVisit) != 0: - currentNodeWrapper = toVisit.pop() - - # If the node has not already been visited - if currentNodeWrapper not in visited: - # If the node has inputs - currentNodeOutputClip = currentNodeWrapper.getOutputClip() + if not self._graphWrapper.getNodeWrappers(): + return None - # If the input is connected to a parent - if self.getGraphWrapper().getConnectedClipWrapper_Output(currentNodeOutputClip) is not None: - parentNodeWrapper = self.getGraphWrapper().getNodeWrapper( - self.getGraphWrapper().getConnectedClipWrapper_Output(currentNodeOutputClip).getNodeName()) - toVisit.add(parentNodeWrapper) + for nodes in self._graphWrapper.getNodeWrappers(): + if nodes.pluginContext == "OfxImageEffectContextReader": + listOfNodes.append(nodes) + firstNode = nodes + else: + firstNode = self._graphWrapper.getNodeWrappers()[0] + + toVisit = set() + visited = set() + toVisit.add(firstNode) + while len(toVisit) != 0: + currentNodeWrapper = toVisit.pop() - visited.add(currentNodeWrapper) + # If the node has not already been visited + if currentNodeWrapper not in visited: + # If the node has inputs + currentNodeOutputClip = currentNodeWrapper.getOutputClip() - listOfParents = QObjectListModel(self) - listOfParents.append(firstNode) - visited.remove(firstNode) - while len(visited) != 0: - listOfParents.append(visited.pop()) + # If the input is connected to a parent + if self.getGraphWrapper().getConnectedClipWrapper_Output(currentNodeOutputClip) is not None: + parentNodeWrapper = self.getGraphWrapper().getNodeWrapper( + self.getGraphWrapper().getConnectedClipWrapper_Output(currentNodeOutputClip).getNodeName()) + toVisit.add(parentNodeWrapper) - for _ in range(len(listOfParents)): - if len(listOfNodes) > 0: - currentNode = listOfNodes[len(listOfNodes) - 1] - currentNodeOutputClip = currentNode.getOutputClip() + visited.add(currentNodeWrapper) - # If the input is connected to a parent - if self.getGraphWrapper().getConnectedClipWrapper_Output(currentNodeOutputClip) is not None: - parentNode = self.getGraphWrapper().getNodeWrapper( - self.getGraphWrapper().getConnectedClipWrapper_Output(currentNodeOutputClip).getNodeName()) - listOfNodes.append(parentNode) - return listOfNodes - else: - return None + listOfParents = QObjectListModel(self) + listOfParents.append(firstNode) + visited.remove(firstNode) + while len(visited) != 0: + listOfParents.append(visited.pop()) + + # TODO faca + for _ in range(len(listOfParents)): + if len(listOfNodes) > 0: + currentNode = listOfNodes[len(listOfNodes) - 1] + currentNodeOutputClip = currentNode.getOutputClip() + + # If the input is connected to a parent + if self.getGraphWrapper().getConnectedClipWrapper_Output(currentNodeOutputClip) is not None: + parentNode = self.getGraphWrapper().getNodeWrapper( + self.getGraphWrapper().getConnectedClipWrapper_Output(currentNodeOutputClip).getNodeName()) + listOfNodes.append(parentNode) + + return listOfNodes # ## Others ## # @@ -461,14 +465,16 @@ def addShortcut(self, key1, key2, name, doc, context): def appendToCurrentSelectedNodeWrappers(self, nodeWrapper): self.appendToCurrentSelectedNodeNames(nodeWrapper.getName()) - @QtCore.pyqtSlot(QtCore.QObject, int) - def assignNodeToViewerIndex(self, nodeWrapper, frame): + @QtCore.pyqtSlot(str, QtCore.QObject) + def setActiveNode(self, graphId, nodeWrapper): """ - Assigns a node to the _mapViewerIndextoNodeName at the current viewerIndex. + Assigns a node to the _mapViewerIndexToNodeName at the current viewerIndex. It adds a tuple (nodeName, frame). """ + logging.debug("ButtleData.setActiveNode: %s", nodeWrapper.getName()) + self.setActiveGraphId(graphId) if nodeWrapper: - self._mapViewerIndextoNodeName.update({str(self._currentViewerIndex): (nodeWrapper.getName(), frame)}) + self._mapViewerIndexToNodeName.update({str(self._currentViewerIndex): nodeWrapper.getName()}) @QtCore.pyqtSlot() def clearCurrentConnectionId(self): @@ -476,23 +482,38 @@ def clearCurrentConnectionId(self): self.currentConnectionWrapperChanged.emit() @QtCore.pyqtSlot() - def clearCurrentSelectedNodeNames(self): - self._currentSelectedNodeNames[:] = [] - self.currentSelectedNodesChanged.emit() + def getActiveGraphId(self): + return self._activeGraphId + + @QtCore.pyqtSlot() + def getActiveGraphWrapper(self): + """ + :return: the active graph ID (which is the graph displayed in the Viewer). + """ + return self._mapGraph[self.getActiveGraphId()] @QtCore.pyqtSlot() - def currentGraphIsGraphBrowser(self): + def getActiveGraph(self): """ - Set the _currentGraph to graphBrowser // work in QML + :return: the active graph ID (which is the graph displayed in the Viewer). """ - self._currentGraph = self._graphBrowser + return self.getActiveGraphWrapper().getGraph() @QtCore.pyqtSlot() - def currentGraphIsGraph(self): + def clearCurrentSelectedNodeNames(self): + self._currentSelectedNodeNames[:] = [] + self.currentSelectedNodesChanged.emit() + + @QtCore.pyqtSlot(str) + def setActiveGraphId(self, graphId): """ - Set the _currentGraph to graph // work in QML + Set the active graph (from QML). """ - self._currentGraph = self._graph + logging.debug("ButtleData.setActiveGraphId: %s", graphId) + if graphId not in self._mapGraph: + raise KeyError('Unknown graphId: %s' % graphId) + self._activeGraphId = graphId + self.activeGraphIdChanged.emit() @QtCore.pyqtSlot(str, result=bool) def isAPlugin(self, pluginId): @@ -507,7 +528,7 @@ def lastNode(self): sizeOfGraph = self._currentGraphWrapper._nodeWrappers.size() - if (sizeOfGraph >= 1): + if sizeOfGraph >= 1: # Nodes to connect lastNode = self._currentGraphWrapper._nodeWrappers[sizeOfGraph-1] else: @@ -546,19 +567,19 @@ def loadData(self, url='buttleofx/backup/data.bofx'): # Viewer : other views for index, view in decoded["viewer"]["other_views"].items(): - nodeName, frame = view["nodeName"], view["frame"] - self._mapViewerIndextoNodeName.update({index: (nodeName, frame)}) + nodeName = view["nodeName"] + self._mapViewerIndexToNodeName.update({index: nodeName}) # Viewer : currentViewerNodeName for index, current_view in decoded["viewer"]["current_view"].items(): - nodeName, frame = current_view["nodeName"], current_view["frame"] - self._mapViewerIndextoNodeName.update({index: (nodeName, frame)}) - # The next commands need to be fixed: We need to click on the viewers number to see the image in the viewer - # self.setCurrentViewerIndex(int(index)) - # self.setCurrentViewerNodeName(current_view) - # self.setCurrentViewerNodeWrapper = self.getNodeWrapperByViewerIndex(int(index)) - # self.setCurrentViewerFrame(frame) - # ButtleEvent().emitViewerChangedSignal() + nodeName = current_view["nodeName"] + self._mapViewerIndexToNodeName.update({index: nodeName}) + # The next commands need to be fixed: We need to click on the viewers number to see the image in the viewer + # self.setCurrentViewerIndex(int(index)) + # self.setCurrentViewerNodeName(current_view) + # self.setCurrentViewerNodeWrapper = self.getNodeWrapperByViewerIndex(int(index)) + # self.setCurrentViewerFrame(frame) + # ButtleEvent().emitViewerChangedSignal() self.urlOfFileToSave = filepath @@ -629,16 +650,19 @@ def nodeIsSelected(self, nodeWrapper): return False @QtCore.pyqtSlot(str, result=QtCore.QObject) - def nodeReaderWrapperForBrowser(self, url): - self._graphBrowser._nodes = [] # Clear the graph - self._graphBrowser._graphTuttle = tuttle.Graph() # Clear the graphTuttle - self._currentGraph = self._graphBrowser - self._currentGraphWrapper = self._graphBrowserWrapper + def setActiveBrowserFile(self, url): + # Clear the graph + self._graphBrowserWrapper.getGraph().hardClear() + + readerNode = self._graphBrowserWrapper.getGraph().createReaderNode(url, 0, 0) + readerNodeWrapper = NodeWrapper(readerNode, self._graphBrowserWrapper._parent) - readerNode = self._graphBrowser.createReaderNode(url, 0, 0) # Create a reader node (like for file drag & drop) + logging.warning("image viewer, node ('%s'): (%s) to (%s)", url, str(readerNodeWrapper.getFirstFrame()), str(readerNodeWrapper.getLastFrame())) + self.setCurrentViewerFrame(readerNodeWrapper.getFirstFrame()) - readerNodeWrapper = NodeWrapper(readerNode, self._graphBrowserWrapper._view) # Wrapper of the reader file + self.setActiveGraphId("graphBrowser") + self.setCurrentViewerNodeWrapper(readerNodeWrapper) return readerNodeWrapper @QtCore.pyqtSlot(QtCore.QUrl) @@ -690,17 +714,15 @@ def saveData(self, url): dictJson["paramEditor"] = self.getCurrentParamNodeName() # Viewer : currentViewerNodeName - for num_view, view in self._mapViewerIndextoNodeName.items(): - if view is not None: - (nodeName, frame) = view + for num_view, view in self._mapViewerIndexToNodeName.items(): + if view: + nodeName = view if self.getCurrentViewerNodeName() == nodeName: dictJson["viewer"]["current_view"][str(num_view)] = {} dictJson["viewer"]["current_view"][str(num_view)]["nodeName"] = nodeName - dictJson["viewer"]["current_view"][str(num_view)]["frame"] = frame else: dictJson["viewer"]["other_views"][str(num_view)] = {} dictJson["viewer"]["other_views"][str(num_view)]["nodeName"] = nodeName - dictJson["viewer"]["other_views"][str(num_view)]["frame"] = frame # Write dictJson in a file f.write(str(json.dumps(dictJson, sort_keys=True, indent=2, ensure_ascii=False))) @@ -763,12 +785,6 @@ def getCurrentCopiedNodesInfo(self): """ return self._currentCopiedNodesInfo - def getCurrentGraph(self): - return self._currentGraph - - def getCurrentGraphWrapper(self): - return self._currentGraphWrapper - def getCurrentParamNodeName(self): """ Returns the name of the current param node. @@ -779,7 +795,7 @@ def getCurrentParamNodeWrapper(self): """ Returns the current param nodeWrapper. """ - return self._currentGraphWrapper.getNodeWrapper(self.getCurrentParamNodeName()) + return self.getActiveGraphWrapper().getNodeWrapper(self.getCurrentParamNodeName()) def getCurrentSelectedNodeNames(self): """ @@ -808,17 +824,11 @@ def getCurrentViewerIndex(self): """ return self._currentViewerIndex - # def getCurrentViewerNodeWrapper(self): - # """ - # Returns the current viewer nodeWrapper. - # """ - # return self._graphWrapper.getNodeWrapper(self.getCurrentViewerNodeName()) - def getCurrentViewerNodeWrapper(self): """ Returns the current viewer nodeWrapper. """ - return self._currentGraphWrapper.getNodeWrapper(self.getCurrentViewerNodeName()) + return self.getActiveGraphWrapper().getNodeWrapper(self.getCurrentViewerNodeName()) def getEditedNodesWrapper(self): """ @@ -897,13 +907,6 @@ def setCurrentCopiedConnectionsInfo(self, connectionsInfo): def setCurrentCopiedNodesInfo(self, nodesInfo): self._currentCopiedNodesInfo = nodesInfo - def setCurrentGraph(self, currentGraph): - """ - Set the _currentGraph // doesn't work in QML - """ - self._currentGraph = currentGraph - self.currentGraphChanged.emit() - def setCurrentGraphWrapper(self, currentGraphWrapper): """ Set the _currentGraphWrapper @@ -949,34 +952,14 @@ def setCurrentViewerNodeName(self, nodeName): self._currentViewerNodeName = nodeName self.currentViewerNodeChanged.emit() - # def setCurrentViewerNodeWrapper(self, nodeWrapper): - # """ - # Changes the current viewer node and emits the change. - # """ - # if nodeWrapper is None: - # self._currentViewerNodeName = None - # elif self._currentViewerNodeName == nodeWrapper.getName(): - # return - # else: - # self._currentViewerNodeName = nodeWrapper.getName() - # # emit signal - # self.currentViewerNodeChanged.emit() - def setCurrentViewerNodeWrapper(self, nodeWrapper): """ Changes the current viewer node and emits the change. """ if nodeWrapper is None: self._currentViewerNodeName = None - elif self._currentViewerNodeName == nodeWrapper.getName(): - return else: self._currentViewerNodeName = nodeWrapper.getName() - # Emit signal - # logging.debug("setCurrentViewerId globalButtleData.getCurrentGraphWrapper(): %s",self.getCurrentGraphWrapper()) - # logging.debug("setCurrentViewerId nodeWrapper.getName(): %s", nodeWrapper.getName()) - - # logging.debug("setCurrentViewerId self._graphBrowser._graphTuttle: ", self._graphBrowser._graphTuttle) self.currentViewerNodeChanged.emit() @@ -1001,7 +984,7 @@ def setVideoIsPlaying(self, valueBool): def appendNodeWrapper(self, nodeWrapper): if nodeWrapper.getName() in self._currentSelectedNodeNames: - self._currentSelectedNodeNames.remove(nodeWrapper.getName()) + self._currentSelectedNodeNames.remove(nodeWrapper.getName()) self._currentSelectedNodeNames.append(nodeWrapper.getName()) self.currentSelectedNodesChanged.emit() @@ -1061,6 +1044,11 @@ def graphCanBeSaved(self): currentViewerNodeName = QtCore.pyqtProperty(QtCore.QObject, getCurrentViewerNodeName, constant=True) + activeGraphIdChanged = QtCore.pyqtSignal() + activeGraphId = QtCore.pyqtProperty(str, getActiveGraphId, + setActiveGraphId, + notify=activeGraphIdChanged) + currentSelectedNodesChanged = QtCore.pyqtSignal() currentSelectedNodeWrappers = QtCore.pyqtProperty("QVariant", getCurrentSelectedNodeWrappers, setCurrentSelectedNodeWrappers, @@ -1078,14 +1066,6 @@ def graphCanBeSaved(self): # Total of the nodes editedNodesWrapper = QtCore.pyqtProperty(QtCore.QObject, getEditedNodesWrapper, constant=True) - currentGraphWrapperChanged = QtCore.pyqtSignal() - currentGraphWrapper = QtCore.pyqtProperty(QtCore.QObject, getCurrentGraphWrapper, - setCurrentGraphWrapper, notify=currentGraphWrapperChanged) - - currentGraphChanged = QtCore.pyqtSignal() - currentGraph = QtCore.pyqtProperty(QtCore.QObject, getCurrentGraph, setCurrentGraph, - notify=currentGraphChanged) - # Paste possibility pastePossibilityChanged = QtCore.pyqtSignal() canPaste = QtCore.pyqtProperty(bool, canPaste, notify=pastePossibilityChanged) @@ -1110,4 +1090,4 @@ def _decode_dict(_dict): for key in _dict: if isinstance(_dict[key], str): _dict[key] = str(_dict[key]) - return _dict \ No newline at end of file + return _dict diff --git a/buttleofx/gui/browser_v2/qml/FileWindow.qml b/buttleofx/gui/browser_v2/qml/FileWindow.qml index bb1c5008..3b0707c3 100644 --- a/buttleofx/gui/browser_v2/qml/FileWindow.qml +++ b/buttleofx/gui/browser_v2/qml/FileWindow.qml @@ -11,39 +11,15 @@ Rectangle { // defaults slots function onItemClickedSlot(pathImg){ - // handleGraphViewerClick - // We come to the temporary viewer - player.changeViewer(11) - - // We save the last node wrapper of the last view - var nodeWrapper - player.lastNodeWrapper = _buttleData.getNodeWrapperByViewerIndex(player.lastView) - nodeWrapper = _buttleData.nodeReaderWrapperForBrowser(pathImg) - _buttleData.currentGraphIsGraphBrowser() - _buttleData.currentGraphWrapper = _buttleData.graphBrowserWrapper - _buttleData.currentViewerNodeWrapper = nodeWrapper - _buttleData.currentViewerFrame = 0 - - // We assign the node to the viewer, at the frame 0 - _buttleData.assignNodeToViewerIndex(nodeWrapper, 10) - _buttleData.currentViewerIndex = 10 // We assign to the viewer the 10th view + console.debug("handleGraphViewerClick") + _buttleData.setActiveBrowserFile(pathImg) _buttleEvent.emitViewerChangedSignal() } function onItemDoubleClickedSlot(absolutePath){ - // handleGraphViewerDoubleClick - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - _buttleData.currentGraphIsGraph() - - // If before the viewer was showing an image from the browser, we change the currentView - if (_buttleData.currentViewerIndex > 9){ - _buttleData.currentViewerIndex = player.lastView - - if (player.lastNodeWrapper != undefined) - _buttleData.currentViewerNodeWrapper = player.lastNodeWrapper - player.changeViewer(player.lastView) - } - _buttleManager.nodeManager.dropFile(absolutePath, 10, 10) + console.debug("handleGraphViewerDoubleClick") + _buttleData.setActiveGraphId("graphEditor") + _buttleManager.nodeManager.dropFile(absolutePath, 10, 10) } signal itemClicked(string absolutePath, string pathImg, bool isFolder, bool isSupported) diff --git a/buttleofx/gui/graph/graphWrapper.py b/buttleofx/gui/graph/graphWrapper.py index cf6864b8..1a6ff5f4 100644 --- a/buttleofx/gui/graph/graphWrapper.py +++ b/buttleofx/gui/graph/graphWrapper.py @@ -12,7 +12,7 @@ class GraphWrapper(QtCore.QObject): """ Class GraphWrapper defined by: - - _view : to have the view object + - _parent : the parent Qt object - _nodeWrappers : list of node wrappers (the python objects we use to communicate with the QML) - _connectionWrappers : list of connections wrappers (the python objects we use to communicate with the QML) - _zMax : to manage the depth of the graph (in QML) @@ -21,12 +21,10 @@ class GraphWrapper(QtCore.QObject): This class is a view (= a map) of a graph. """ - _resize = False + def __init__(self, graph, parent): + QtCore.QObject.__init__(self, parent) - def __init__(self, graph, view): - QtCore.QObject.__init__(self, view) - - self._view = view + self._parent = parent self._nodeWrappers = QObjectListModel(self) self._connectionWrappers = QObjectListModel(self) @@ -234,17 +232,17 @@ def createNodeWrapper(self, nodeName): # we search the right node in the node list node = self._graph.getNode(nodeName) if node: - nodeWrapper = NodeWrapper(node, self._view) + nodeWrapper = NodeWrapper(node, self._parent) self._nodeWrappers.append(nodeWrapper) def createConnectionWrapper(self, connection): """ Creates a connection wrapper and add it to the connectionWrappers list. """ - conWrapper = ConnectionWrapper(connection, self._view) + conWrapper = ConnectionWrapper(connection, self._parent) self._connectionWrappers.append(conWrapper) - def getGraphMapped(self): + def getGraph(self): """ Returns the graph (the node list and the connection list), mapped by this graphWrapper. """ @@ -277,13 +275,6 @@ def getZMax(self): """ return self._zMax - def resize(self): - return self._resize - - def setResize(self, value): - self._resize = value - self.currentSizeChanged.emit() - def setTmpMoveNodeX(self, value): self.tmpMoveNode[0] = value @@ -340,7 +331,7 @@ def __str__(self): str_list.append(con.__str__()) str_list.append("\n") - str_list.append((self.getGraphMapped()).__str__()) + str_list.append((self.getGraph()).__str__()) return ''.join(str_list) @@ -356,6 +347,3 @@ def __str__(self): bboxChanged = QtCore.pyqtSignal() bbox = QtCore.pyqtProperty(QtGui.QVector4D, getBBox, notify=bboxChanged) - - currentSizeChanged = QtCore.pyqtSignal() - resize = QtCore.pyqtProperty(bool, resize, notify=currentSizeChanged) diff --git a/buttleofx/gui/graph/node/nodeWrapper.py b/buttleofx/gui/graph/node/nodeWrapper.py index afc4dbce..918eeab3 100644 --- a/buttleofx/gui/graph/node/nodeWrapper.py +++ b/buttleofx/gui/graph/node/nodeWrapper.py @@ -16,16 +16,15 @@ class NodeWrapper(QtCore.QObject): - _fpsError, _frameError : potential errors that we need to displayed. """ - def __init__(self, node, view): + def __init__(self, node, parent): # logging.debug("NodeWrapper constructor") - QtCore.QObject.__init__(self, view) + QtCore.QObject.__init__(self, parent) self._node = node - self._view = view self._isHighlighted = False # paramWrappers - self._paramWrappers = ParamEditorWrapper(self._view, self._node.getParams()) + self._paramWrappers = ParamEditorWrapper(parent, self._node.getParams()) # Potential errors self._fpsError = "" @@ -36,7 +35,7 @@ def __init__(self, node, view): self._node.nodePositionChanged.connect(self.emitNodePositionChanged) self._node.nodeContentChanged.connect(self.emitNodeContentChanged) - self._clipWrappers = [ClipWrapper(clip, self.getName(), self._view) for clip in self._node.getClips()] + self._clipWrappers = [ClipWrapper(clip, self.getName(), parent) for clip in self._node.getClips()] self._srcClips = QObjectListModel(self) logging.info("Gui : NodeWrapper created") @@ -136,7 +135,7 @@ def getFPS(self): # Import which needs to be changed in the future from buttleofx.data import globalButtleData - graph = globalButtleData.getCurrentGraph().getGraphTuttle() + graph = globalButtleData.getActiveGraph().getGraphTuttle() node = self._node.getTuttleNode().asImageEffectNode() try: self.setFpsError("") @@ -157,31 +156,52 @@ def setFpsError(self, nodeName): self._fpsError = nodeName def getNbFrames(self): + """ + Returns the number of frames of this node. + """ + timeDomain = self.getTimeDomain() # getTimeDomain() returns the first and last frames + nbFrames = timeDomain.max - timeDomain.min + + # Not very elegant, but allows us to avoid a problem if an image returns a lot of frames + if nbFrames > 100000000 or nbFrames < 0: + nbFrames = 1 + # logging.debug("nbFrames: %d", nbFrames) + return nbFrames + + def getTimeDomain(self): """ Returns the number of frames of this node. """ # Import which needs to be changed in the future from buttleofx.data import globalButtleData - - graph = globalButtleData.getCurrentGraph().getGraphTuttle() + from pyTuttle import tuttle + + graph = globalButtleData.getActiveGraph().getGraphTuttle() node = self._node.getTuttleNode().asImageEffectNode() try: + processOptions = tuttle.ComputeOptions(0) + processGraph = tuttle.ProcessGraph(processOptions, graph, [node.getName()], tuttle.core().getMemoryCache()) + processGraph.setup() + return node.getTimeDomain() + self.setFrameError("") graph.setup() except Exception as e: - logging.debug("can't get nbFrames of the node %s", self._node.getName()) + logging.debug("Can't get the time domain of the node '%s'.", self._node.getName()) + logging.debug(str(e)) self.setFrameError(str(e)) - return 0 + r = tuttle.OfxRangeD() + r.min = 0 + r.max = 0 + return r - timeDomain = node.getTimeDomain() # getTimeDomain() returns the first and last frames - nbFrames = timeDomain.max - timeDomain.min - # Not very elegant, but allows us to avoid a problem if an image returns a lot of frames - if nbFrames > 100000000 or nbFrames < 0: - nbFrames = 1 - # logging.debug("nbFrames: %d", nbFrames) - return nbFrames + def getFirstFrame(self): + return self.getTimeDomain().min + + def getLastFrame(self): + return self.getTimeDomain().max def getFrameError(self): return self._frameError @@ -261,6 +281,8 @@ def __del__(self): # Video fps = QtCore.pyqtProperty(float, getFPS, constant=True) nbFrames = QtCore.pyqtProperty(int, getNbFrames, constant=True) + firstFrame = QtCore.pyqtProperty(int, getFirstFrame, constant=True) + lastFrame = QtCore.pyqtProperty(int, getLastFrame, constant=True) # For a clean display of connections srcClips = QtCore.pyqtProperty(QtCore.QObject, getSrcClips, constant=True) diff --git a/buttleofx/gui/graph/qml/Graph.qml b/buttleofx/gui/graph/qml/Graph.qml index dbce5d7d..ff92e85a 100644 --- a/buttleofx/gui/graph/qml/Graph.qml +++ b/buttleofx/gui/graph/qml/Graph.qml @@ -58,9 +58,7 @@ Rectangle { // We assign the mosquito only if there is only one node selected if (selectedNodes.count == 1) { var node = selectedNodes.get(0) - _buttleData.currentViewerNodeWrapper = node - _buttleData.currentViewerFrame = 0 - _buttleData.assignNodeToViewerIndex(node, 0) + _buttleData.setActiveNode("graphEditor", node) _buttleEvent.emitViewerChangedSignal() } } @@ -150,15 +148,7 @@ Rectangle { return } - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - _buttleData.currentGraphIsGraph() - // If before the viewer was showing an image from the brower, we change the currentView - if (_buttleData.currentViewerIndex > 9) { - _buttleData.currentViewerIndex = player.lastView - if (player.lastNodeWrapper != undefined) - _buttleData.currentViewerNodeWrapper = player.lastNodeWrapper - player.changeViewer(player.lastView) - } + _buttleData.setActiveGraphId("graphEditor") for (var urlIndex in drop.urls) { // TODO: screenToScene to take scale into account @@ -178,16 +168,7 @@ Rectangle { keys: "internFileDrag" onDropped: { - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - _buttleData.currentGraphIsGraph() - // If before the viewer was showing an image from the brower, we change the currentView - if (_buttleData.currentViewerIndex > 9) { - _buttleData.currentViewerIndex = player.lastView - if (player.lastNodeWrapper != undefined) - _buttleData.currentViewerNodeWrapper = player.lastNodeWrapper - player.changeViewer(player.lastView) - } - + _buttleData.setActiveGraphId("graphEditor") for (var urlIndex in drag.source.selectedFiles) { _buttleManager.nodeManager.dropFile(drag.source.selectedFiles[urlIndex], drag.x - m.graphRoot.originX + 10*urlIndex, drag.y - m.graphRoot.originY + 10*urlIndex) diff --git a/buttleofx/gui/graph/qml/GraphEditor.qml b/buttleofx/gui/graph/qml/GraphEditor.qml index 6080efa3..30c698ee 100644 --- a/buttleofx/gui/graph/qml/GraphEditor.qml +++ b/buttleofx/gui/graph/qml/GraphEditor.qml @@ -92,17 +92,8 @@ Item { onClickCreationNode: { // console.log("Node created clicking from Tools") - _buttleData.currentGraphIsGraph() - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - // If before the viewer was showing an image from the brower, we change the currentView - if (_buttleData.currentViewerIndex > 9) { - _buttleData.currentViewerIndex = player.lastView - if (player.lastNodeWrapper != undefined) - _buttleData.currentViewerNodeWrapper = player.lastNodeWrapper - player.changeViewer(player.lastView) - } - - _buttleManager.nodeManager.creationNode("_buttleData.graph", nodeType, -graph.originX + 20, -graph.originY + 20) + _buttleData.setActiveGraphId("graphEditor") + _buttleManager.nodeManager.creationNode("graphEditor", nodeType, -graph.originX + 20, -graph.originY + 20) } } @@ -124,17 +115,8 @@ Item { onClickCreationNode: { // console.log("Node created clicking from Graph") - _buttleData.currentGraphIsGraph() - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - // If before the viewer was showing an image from the brower, we change the currentView - if (_buttleData.currentViewerIndex > 9) { - _buttleData.currentViewerIndex = player.lastView - if (player.lastNodeWrapper != undefined) - _buttleData.currentViewerNodeWrapper = player.lastNodeWrapper - player.changeViewer(player.lastView) - } - - _buttleManager.nodeManager.creationNode("_buttleData.graph", nodeType, -graph.originX + graph.mouseX, -graph.originY + graph.mouseY) + _buttleData.setActiveGraphId("graphEditor") + _buttleManager.nodeManager.creationNode("graphEditor", nodeType, -graph.originX + graph.mouseX, -graph.originY + graph.mouseY) } MouseArea { diff --git a/buttleofx/gui/graph/qml/Node.qml b/buttleofx/gui/graph/qml/Node.qml index 41847a39..8a84a0e6 100644 --- a/buttleofx/gui/graph/qml/Node.qml +++ b/buttleofx/gui/graph/qml/Node.qml @@ -79,8 +79,7 @@ Rectangle { } // Param buttle - // TODO showNodeInfo() - _buttleData.currentParamNodeWrapper = m.nodeWrapper + _buttleData.setActiveNode("graphEditor", m.nodeWrapper) } // Take the focus @@ -99,18 +98,12 @@ Rectangle { if (mouse.button == Qt.LeftButton) { - _buttleManager.nodeManager.nodeMoved(m.nodeWrapper.name, qml_nodeRoot.x / graph.zoomCoeff, + _buttleManager.nodeManager.moveNode(m.nodeWrapper.name, qml_nodeRoot.x / graph.zoomCoeff, qml_nodeRoot.y / graph.zoomCoeff) } else if (mouse.button == Qt.MidButton) { // Middle button: assign the node to the viewer - _buttleData.currentGraphIsGraph() - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - _buttleData.currentViewerNodeWrapper = m.nodeWrapper - _buttleData.currentViewerFrame = 0 - // We assign the node to the viewer, at the frame 0 - _buttleData.assignNodeToViewerIndex(m.nodeWrapper, 0) + _buttleData.setActiveNode("graphEditor", m.nodeWrapper) _buttleEvent.emitViewerChangedSignal() - player.lastNodeWrapper = _buttleData.currentViewerNodeWrapper } } @@ -126,14 +119,8 @@ Rectangle { keys: "mosquitoMouseArea" onDropped: { - _buttleData.currentGraphIsGraph() - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - _buttleData.currentViewerNodeWrapper = m.nodeWrapper - _buttleData.currentViewerFrame = 0 - // We assign the node to the viewer, at the frame 0 - _buttleData.assignNodeToViewerIndex(m.nodeWrapper, 0) + _buttleData.setActiveNode("graphEditor", m.nodeWrapper) _buttleEvent.emitViewerChangedSignal() - player.lastNodeWrapper = _buttleData.currentViewerNodeWrapper } } diff --git a/buttleofx/gui/paramEditor/qml/ParametersEditor.qml b/buttleofx/gui/paramEditor/qml/ParametersEditor.qml index 64c959ec..54f2629d 100644 --- a/buttleofx/gui/paramEditor/qml/ParametersEditor.qml +++ b/buttleofx/gui/paramEditor/qml/ParametersEditor.qml @@ -255,11 +255,8 @@ Item { tuttleParamContent.visible = true } } else if (mouse.button == Qt.MidButton) { - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - _buttleData.currentViewerNodeWrapper = paramNode.currentParamNode - _buttleData.currentViewerFrame = 0 // We assign the node to the viewer, at the frame 0 - _buttleData.assignNodeToViewerIndex(paramNode.currentParamNode, 0) + _buttleData.setActiveNode("graphEditor", paramNode.currentParamNode) _buttleEvent.emitViewerChangedSignal() } } @@ -270,11 +267,7 @@ Item { keys: "mosquitoMouseArea" onDropped: { - _buttleData.currentGraphWrapper = _buttleData.graphWrapper - _buttleData.currentViewerNodeWrapper = paramNode.currentParamNode - _buttleData.currentViewerFrame = 0 - // We assign the node to the viewer, at the frame 0 - _buttleData.assignNodeToViewerIndex(paramNode.currentParamNode, 0) + _buttleData.setActiveNode("graphEditor", paramNode.currentParamNode) _buttleEvent.emitViewerChangedSignal() } } @@ -581,9 +574,9 @@ Item { _buttleData.currentGraphWrapper = _buttleData.graphWrapper if (previousNode == undefined) - _buttleManager.nodeManager.creationNode("_buttleData.graph", object, 0, 0) + _buttleManager.nodeManager.creationNode("graphEditor", object, 0, 0) else - _buttleManager.nodeManager.creationNode("_buttleData.graph", object, previousNode.xCoord+140, previousNode.yCoord) + _buttleManager.nodeManager.creationNode("graphEditor", object, previousNode.xCoord+140, previousNode.yCoord) // If there is only one node, we don't connect it if (previousNode != undefined){ diff --git a/buttleofx/gui/plugin/qml/PluginBrowser.qml b/buttleofx/gui/plugin/qml/PluginBrowser.qml index 17f8585b..e6be4326 100644 --- a/buttleofx/gui/plugin/qml/PluginBrowser.qml +++ b/buttleofx/gui/plugin/qml/PluginBrowser.qml @@ -197,9 +197,9 @@ ApplicationWindow { _buttleData.currentGraphWrapper = _buttleData.graphWrapper if (previousNode == undefined) { - _buttleManager.nodeManager.creationNode("_buttleData.graph", currentObject.pluginType, 0, 0) + _buttleManager.nodeManager.creationNode("graphEditor", currentObject.pluginType, 0, 0) } else { - _buttleManager.nodeManager.creationNode("_buttleData.graph", currentObject.pluginType, + _buttleManager.nodeManager.creationNode("graphEditor", currentObject.pluginType, previousNode.xCoord+140, previousNode.yCoord) // If there is no input clip, no auto-connection @@ -210,13 +210,13 @@ ApplicationWindow { } else { if (_buttleData.currentSelectedNodeWrappers.count == 1) { var selectedNode = _buttleData.currentSelectedNodeWrappers.get(0) - _buttleManager.nodeManager.creationNode("_buttleData.graph", currentObject.pluginType, + _buttleManager.nodeManager.creationNode("graphEditor", currentObject.pluginType, selectedNode.xCoord+140, selectedNode.yCoord) var createdNode = _buttleData.lastNode() if (createdNode.nbInput != 0) _buttleManager.connectionManager.connectWrappers(selectedNode.outputClip, createdNode.srcClips.get(0)) } else { - _buttleManager.nodeManager.creationNode("_buttleData.graph", currentObject.pluginType, 0, 0) + _buttleManager.nodeManager.creationNode("graphEditor", currentObject.pluginType, 0, 0) } } diff --git a/buttleofx/gui/viewer/qml/Player.qml b/buttleofx/gui/viewer/qml/Player.qml index 45e58422..3c0fcb17 100644 --- a/buttleofx/gui/viewer/qml/Player.qml +++ b/buttleofx/gui/viewer/qml/Player.qml @@ -12,9 +12,6 @@ Item { // Remark: in python if there are ten frames, they are numbered from 0 to 9 so we need some time to add 1 for display property variant node - property real nodeFps: node ? node.fps : 25 - property int nodeNbFrames: node ? node.nbFrames : 1 - property real nodeDurationSeconds: node ? node.nbFrames/node.fps : 0 property bool isPlaying: false property int lastView: 1 // The last view where the user was @@ -22,48 +19,44 @@ Item { TimerPlayer { //Class Timer defined in python - // property associated: frame, acces with timer.frame - id: timer - fps: nodeFps - nbFrames: nodeNbFrames + // property associated: frame, access with timer.frame + id: timerPlayer + fps: node ? node.fps : 25 + nbFrames: node ? node.nbFrames : 1 } - property variant timer: timer + property variant timer: timerPlayer - // Displays an integer with 2 digits + // Displays an integer with a padding of 2 digits function with2digits(n) { return n > 9 ? "" + n: "0" + n; } + function frameToTimecode(frames, fps) { + return frames + var seconds = frames / fps + + var _hours = Math.floor(seconds / 3600) + var _minutes = Math.floor((seconds - _hours*3600) / 60) + var _seconds = Math.floor(seconds - _hours*3600 - _minutes*60) + var _frames = frames - Math.floor((_hours*3600 - _minutes*60 - _seconds))*fps + return with2digits(_hours) + ":" + with2digits(_minutes) + ":" + with2digits(_seconds) + "." + with2digits(_frames) + } + // Returns the string displayed under the viewer. It's the current time. function getTimePosition() { - var totalHours = Math.floor(nodeDurationSeconds / 3600) - var totalMinutes = Math.floor((nodeDurationSeconds - totalHours*3600) / 60) - var totalSeconds = Math.floor(nodeDurationSeconds - totalHours*3600 - totalMinutes*60) - - var durationElapsedSeconds = timer ? Math.floor((timer.frame + 1) / timer.fps) : 0 - var elapsedHours = Math.floor(durationElapsedSeconds / 3600) - var elapsedMinutes = Math.floor((durationElapsedSeconds - elapsedHours*3600) / 60) - var elapsedSeconds = Math.floor(durationElapsedSeconds - elapsedHours*3600 - elapsedMinutes*60) + if(node == undefined) { + return frameToTimecode(timer.frame, timer.fps) + } - return with2digits(elapsedHours) + ":" + with2digits(elapsedMinutes) + ":" + with2digits(elapsedSeconds) + " / " + with2digits(totalHours) + ":" + with2digits(totalMinutes) + ":" + with2digits(totalSeconds) + // First / Current / Last Time + return frameToTimecode(node.firstFrame, timer.fps) + " / " + frameToTimecode(timer.frame, timer.fps) + " / " + frameToTimecode(node.lastFrame, timer.fps) } - // Changes the viewer: displays the vew n°indexViewer. - // It updates in ButtleData all informations of the current viewer: the nodeWrapper, the viewerIndex, the frame, and it sets the right frame on the timeline. + // Change viewer index (for the GraphEditor) function changeViewer(indexViewer) { - // First we save the frame for the current node, to be able to retrieve the frame later - if (_buttleData.currentViewerNodeWrapper != null) - _buttleData.assignNodeToViewerIndex(_buttleData.currentViewerNodeWrapper, timer.frame) - - // Then we change the viewer - _buttleData.currentViewerIndex = indexViewer - _buttleData.currentViewerNodeWrapper = _buttleData.getNodeWrapperByViewerIndex(indexViewer) - - // And we change the frame of the viewer (if there isn't a node in this view, returns 0) - var frame = _buttleData.getFrameByViewerIndex(indexViewer) - _buttleData.currentViewerFrame = frame - timer.frame = frame + // _buttleData.currentViewerIndex = indexViewer + // _buttleData.currentViewerNodeWrapper = _buttleData.getNodeWrapperByViewerIndex(indexViewer) _buttleEvent.emitViewerChangedSignal() } @@ -73,7 +66,9 @@ Item { } onNodeChanged: { - // console.log("Node Changed: ", node) + if(node) { + timerPlayer.frame = node.firstFrame + } } Tab { @@ -222,7 +217,7 @@ Item { TimelineTools { id: timelineTools timer: timer - nbFrames: player.nodeNbFrames + nbFrames: timerPlayer.nbFrames } } @@ -385,15 +380,15 @@ Item { // -10 because of margins //cursorTimeline.x = mouse.x - 10 - cursorTimeline.width/2 - //timer.frame = (cursorTimeline.x + cursorTimeline.width/2) * nodeNbFrames /barTimeline.width; + //timer.frame = (cursorTimeline.x + cursorTimeline.width/2) * timerPlayer.nbFrames /barTimeline.width; - timer.frame = (mouse.x - 10) * nodeNbFrames /barTimeline.width; + timer.frame = (mouse.x - 10) * timerPlayer.nbFrames /barTimeline.width; //timer.pause() } } /* blocks the cursor even if window isn't resize... onWidthChanged: { - cursorTimeline.x = timer.frame * (barTimeline.width - cursorTimeline.width/2) / nodeNbFrames; + cursorTimeline.x = timer.frame * (barTimeline.width - cursorTimeline.width/2) / timerPlayer.nbFrames; } */ } @@ -404,7 +399,7 @@ Item { anchors.verticalCenter: parent.verticalCenter property int frame: timer ? timer.frame : 0 - x: barTimeline.x + (frame * barTimeline.width / nodeNbFrames) - cursorTimeline.width/2 + x: barTimeline.x + (frame * barTimeline.width / timerPlayer.nbFrames) - cursorTimeline.width/2 height: 10 width: 5 radius: 1 @@ -424,7 +419,7 @@ Item { timer.launchProcessGraph() // used this to use the processGraph (should be faster) } onPositionChanged: { - timer.frame = (cursorTimeline.x + cursorTimeline.width/2) * nodeNbFrames / barTimeline.width + timer.frame = (cursorTimeline.x + cursorTimeline.width/2) * timerPlayer.nbFrames / barTimeline.width } onReleased: { timer.pause() // to close the processGraph launch with onPressed diff --git a/buttleofx/gui/viewer/timerPlayer.py b/buttleofx/gui/viewer/timerPlayer.py index 6ca35e85..8b556dac 100644 --- a/buttleofx/gui/viewer/timerPlayer.py +++ b/buttleofx/gui/viewer/timerPlayer.py @@ -12,10 +12,9 @@ class TimerPlayer(QtQuick.QQuickItem): def __init__(self, parent=None): QtQuick.QQuickItem.__init__(self, parent) self._timer = QtCore.QTimer() - self._timer.timeout.connect(self.nextFrame) # Initialize the timer + self._timer.timeout.connect(self.playNextFrame) # Initialize the timer self._fps = 25 - self._speed = 1000/self._fps # Delay between frames in milliseconds - self._frame = 0 + self._frame = 11 self._fps = 25 self._nbFrames = 1 self._processGraph = None @@ -32,11 +31,12 @@ def launchProcessGraph(self): # Get the name of the currentNode of the viewer node = globalButtleData.getCurrentViewerNodeName() # Initialization of the process graph - graph = globalButtleData.getCurrentGraph().getGraphTuttle() + graph = globalButtleData.getActiveGraph().getGraphTuttle() # timeRange between the frames of beginning and end (first frame, last frame, step) - timeRange = tuttle.TimeRange(self._frame, self._nbFrames, 1) - self._processOptions = tuttle.ComputeOptions(self._frame, self._nbFrames, 1) + # TODO: faca + timeRange = tuttle.TimeRange(self.getFrame(), self.getFrame() + self.getNbFrames(), 1) + self._processOptions = tuttle.ComputeOptions(self.getFrame(), self.getNbFrames(), 1) processGraph = tuttle.ProcessGraph(self._processOptions, graph, [node], tuttle.core().getMemoryCache()) processGraph.setup() @@ -66,11 +66,11 @@ def play(self): # Get the name of the currentNode of the viewer node = globalButtleData.getCurrentViewerNodeName() # Initialization of the process graph - graph = globalButtleData.getCurrentGraph().getGraphTuttle() + graph = globalButtleData.getActiveGraph().getGraphTuttle() # timeRange between the frames of beginning and end (first frame, last frame, step) - timeRange = tuttle.TimeRange(self._frame, self._nbFrames, 1) - self._processOptions = tuttle.ComputeOptions(self._frame, self._nbFrames, 1) + timeRange = tuttle.TimeRange(self.getFrame(), self.getNbFrames(), 1) + self._processOptions = tuttle.ComputeOptions(self.getFrame(), self.getNbFrames(), 1) processGraph = tuttle.ProcessGraph(self._processOptions, graph, [node], tuttle.core().getMemoryCache()) processGraph.setup() @@ -80,16 +80,11 @@ def play(self): globalButtleData.setProcessGraph(processGraph) globalButtleData.setVideoIsPlaying(True) - self._speed = 1000 / self._fps - self._timer.start(self._speed) + self._timer.start(self.getOneFrameDuration()) @QtCore.pyqtSlot() def previousFrame(self): - if self._frame > 0: - self._frame = self._frame - 1 - else: - return - self.framePlayerChanged.emit() + self.setFrame(self.getFrame() - 1) @QtCore.pyqtSlot() def stop(self): @@ -104,50 +99,58 @@ def stop(self): globalButtleData.setProcessGraph(None) # Return to the beginning of the video - self._frame = 0 - self.framePlayerChanged.emit() + self.setFrame(0) # ######################################## Methods private to this class ######################################## # # ## Getters ## # + @QtCore.pyqtSlot(result=int) def getFrame(self): return self._frame + @QtCore.pyqtSlot(result=float) + def getOneFrameDuration(self): + """ + :return: Duration between frames in milliseconds + """ + return 1000.0 / self._fps + + @QtCore.pyqtSlot(result=float) def getFPS(self): return self._fps + @QtCore.pyqtSlot(result=int) def getNbFrames(self): - return self.nbFrames + return self._nbFrames # ## Setters ## # + @QtCore.pyqtSlot(float) def setFrame(self, frame): - if int(frame) >= self._nbFrames: - logging.debug('setFrame, ignore: frame outside bounds') - self.pause() - return - - if int(frame) == self._nbFrames - 1: - self.pause() - - self._frame = int(frame) + self._frame = frame self.framePlayerChanged.emit() + @QtCore.pyqtSlot(float) def setFPS(self, fps): self._fps = fps self.fpsVideoChanged.emit() + @QtCore.pyqtSlot(int) def setNbFrames(self, nbFrames): self._nbFrames = nbFrames self.nbFramesChanged.emit() + # DO NOT declare this function as pyqtSlot, else the Qt connection doesn't work and breaks everything. + # @QtCore.pyqtSlot() + def playNextFrame(self): + # TODO: faca + # If time outside timeDomain self.pause() + self.nextFrame() + + @QtCore.pyqtSlot() def nextFrame(self): - if self._frame < self._nbFrames - 1: - self._frame = self._frame + 1 - else: - return - self.framePlayerChanged.emit() + self.setFrame(self.getFrame() + 1) # ############################################# Data exposed to QML ############################################# # @@ -155,6 +158,6 @@ def nextFrame(self): fpsVideoChanged = QtCore.pyqtSignal() nbFramesChanged = QtCore.pyqtSignal() - frame = QtCore.pyqtProperty(float, getFrame, setFrame, notify=framePlayerChanged) + frame = QtCore.pyqtProperty(int, getFrame, setFrame, notify=framePlayerChanged) fps = QtCore.pyqtProperty(float, getFPS, setFPS, notify=fpsVideoChanged) nbFrames = QtCore.pyqtProperty(int, getNbFrames, setNbFrames, notify=nbFramesChanged) diff --git a/buttleofx/manager/nodeManager.py b/buttleofx/manager/nodeManager.py index 0f5d0a77..d23529ec 100644 --- a/buttleofx/manager/nodeManager.py +++ b/buttleofx/manager/nodeManager.py @@ -20,16 +20,15 @@ def creationNode(self, graph, nodeType, x=20, y=20): """ Creates a node. """ - # globalButtleData.getGraph().createNode(nodeType, x, y) + # globalButtleData.getActiveGraph().createNode(nodeType, x, y) # graph.createNode(nodeType, x, y) - if graph == "_buttleData.graph": - globalButtleData.getGraph().createNode(nodeType, x, y) - elif graph == "_buttleData.graphBrowser": + if graph == "graphEditor": + globalButtleData.getActiveGraph().createNode(nodeType, x, y) + elif graph == "graphBrowser": globalButtleData.getGraphBrowser().createNode(nodeType, x, y) - # By default creation is on the graph in grapheditor else: - globalButtleData.getGraph().createNode(nodeType, x, y) + raise KeyError("Graph type '%s' is unknown" % graph) # Update undo/redo display self.undoRedoChanged() @@ -37,24 +36,26 @@ def creationNode(self, graph, nodeType, x=20, y=20): @QtCore.pyqtSlot() def copyNode(self): """ - Copies the current node(s). + Copy selected node(s). """ # Clear the info saved in currentCopiedNodesInfo globalButtleData.clearCurrentCopiedNodesInfo() # Save new data in currentCopiedNodesInfo for each selected node - if globalButtleData.getCurrentSelectedNodeWrappers() != []: - for node in globalButtleData.getCurrentSelectedNodeWrappers(): - copyNode = {} - copyNode.update({"nodeType": node.getNode().getType()}) - copyNode.update({"nameUser": node.getNode().getNameUser()}) - copyNode.update({"color": node.getNode().getColor()}) - copyNode.update({"params": node.getNode().getTuttleNode().getParamSet()}) - copyNode.update({"mode": "_copy"}) - copyNode.update({"x": node.getNode().getCoord()[0]}) - copyNode.update({"y": node.getNode().getCoord()[1]}) - globalButtleData.getCurrentCopiedNodesInfo()[node.getName()] = copyNode - # Emit the change for the toolbar - globalButtleData.pastePossibilityChanged.emit() + if not globalButtleData.getCurrentSelectedNodeWrappers(): + return + + for node in globalButtleData.getCurrentSelectedNodeWrappers(): + copyNode = {} + copyNode.update({"nodeType": node.getNode().getType()}) + copyNode.update({"nameUser": node.getNode().getNameUser()}) + copyNode.update({"color": node.getNode().getColor()}) + copyNode.update({"params": node.getNode().getTuttleNode().getParamSet()}) + copyNode.update({"mode": "_copy"}) + copyNode.update({"x": node.getNode().getCoord()[0]}) + copyNode.update({"y": node.getNode().getCoord()[1]}) + globalButtleData.getCurrentCopiedNodesInfo()[node.getName()] = copyNode + # Emit the change for the toolbar + globalButtleData.pastePossibilityChanged.emit() @QtCore.pyqtSlot() def cutNode(self): @@ -94,10 +95,10 @@ def destructionNodes(self): # If the viewer displays a node affected by the destruction # need something from Tuttle # if at least one node in the graph - if len(globalButtleData.getGraphWrapper().getNodeWrappers()) > 0 and len(globalButtleData.getGraph().getNodes()) > 0: + if len(globalButtleData.getGraphWrapper().getNodeWrappers()) > 0 and len(globalButtleData.getActiveGraph().getNodes()) > 0: # If a node is selected if globalButtleData.getCurrentSelectedNodeNames() != []: - globalButtleData.getGraph().deleteNodes([nodeWrapper.getNode() for nodeWrapper in + globalButtleData.getActiveGraph().deleteNodes([nodeWrapper.getNode() for nodeWrapper in globalButtleData.getCurrentSelectedNodeWrappers()]) globalButtleData.clearCurrentSelectedNodeNames() @@ -122,7 +123,7 @@ def dropFile(self, url, x, y): if extension == 'bofx': globalButtleData.loadData(url) # Also need to verify the json format else: - globalButtleData.getGraph().createReaderNode(url, x, y) + globalButtleData.getActiveGraph().createReaderNode(url, x, y) # Update undo/redo display self.undoRedoChanged() @@ -137,8 +138,8 @@ def duplicationNode(self): # Create a node giving the current selected node's type, x and y nodeType = node.getNode().getType() coord = node.getNode().getCoord() - globalButtleData.getGraph().createNode(nodeType, coord[0], coord[1]) - newNode = globalButtleData.getGraph().getNodes()[-1] + globalButtleData.getActiveGraph().createNode(nodeType, coord[0], coord[1]) + newNode = globalButtleData.getActiveGraph().getNodes()[-1] # Get the current selected node's properties nameUser = node.getNameUser() + "_duplicate" @@ -155,13 +156,12 @@ def duplicationNode(self): self.undoRedoChanged() @QtCore.pyqtSlot(str, int, int) - def nodeMoved(self, nodeName, x, y): + def moveNode(self, nodeName, x, y): """ - This function pushes a cmdMoved in the CommandManager. + Move a node to an absolute position. """ - globalButtleData.getGraph().nodeMoved(nodeName, x, y) - globalButtleData.getGraph().nodesChanged() - globalButtleData.getGraphWrapper().setResize(True) + globalButtleData.getActiveGraph().moveNode(nodeName, x, y) + globalButtleData.getActiveGraph().nodesChanged() # Update undo/redo display self.undoRedoChanged() @@ -201,17 +201,17 @@ def pasteNode(self): i = 0 for node in globalButtleData.getCurrentCopiedNodesInfo(): - globalButtleData.getGraph().createNode(globalButtleData.getCurrentCopiedNodesInfo()[node]["nodeType"], + globalButtleData.getActiveGraph().createNode(globalButtleData.getCurrentCopiedNodesInfo()[node]["nodeType"], globalButtleData.getCurrentCopiedNodesInfo()[node]["x"] + 20, globalButtleData.getCurrentCopiedNodesInfo()[node]["y"] + 20) - newNode = globalButtleData.getGraph().getNodes()[-1] + newNode = globalButtleData.getActiveGraph().getNodes()[-1] globalButtleData.appendToCurrentSelectedNodeNames(newNode._name) newNode.setColor(globalButtleData.getCurrentCopiedNodesInfo()[node]["color"]) newNode.setNameUser(globalButtleData.getCurrentCopiedNodesInfo()[node]["nameUser"] + globalButtleData.getCurrentCopiedNodesInfo()[node]["mode"]) newNode.getTuttleNode().getParamSet().copyParamsValues(globalButtleData.getCurrentCopiedNodesInfo()[node] ["params"]) - globalButtleData.getGraph().nodesChanged() + globalButtleData.getActiveGraph().nodesChanged() globalButtleData.getCurrentCopiedNodesInfo()[node].update({node: newNode._name}) i = i + 1 diff --git a/buttleofx/manager/viewerManager.py b/buttleofx/manager/viewerManager.py index 1607b360..c7e26594 100644 --- a/buttleofx/manager/viewerManager.py +++ b/buttleofx/manager/viewerManager.py @@ -73,7 +73,7 @@ def computeNode(self, node, frame): """ Computes the node (displayed in the viewer) at the frame indicated. """ - graphTuttle = globalButtleData.getCurrentGraph().getGraphTuttle() + graphTuttle = globalButtleData.getActiveGraph().getGraphTuttle() # Get the output where we save the result self._tuttleImageCache = tuttle.MemoryCache() @@ -124,7 +124,7 @@ def retrieveImage(self, frame, frameChanged): # Get the global node hash ID if node is not None: hashMap = tuttle.NodeHashContainer() - globalButtleData.getCurrentGraph().getGraphTuttle().computeGlobalHashAtTime(hashMap, frame, [node]) + globalButtleData.getActiveGraph().getGraphTuttle().computeGlobalHashAtTime(hashMap, frame, [node]) node_hashCode = hashMap.getHash(node, frame) # Get the buttle latest images map mapNodeToImage = globalButtleData.getMapNodeNameToComputedImage()