def createMenu(parentMenu, parentName, view): for menuItem in ButtleDataSingleton().get( ).getQObjectPluginsIdentifiersByParentPath(parentName): """ TODO : Need to be documented. """ # menuItem is a tuple : see getPluginsIdentifiersAsDictionary() is data/tuttleTools.py pluginParent, pluginId = menuItem # if the item is not a plugin, pluginId = "" isAPlugin = ButtleDataSingleton().get().isAPlugin(pluginId) is True noPluginFound = pluginId is False if noPluginFound: action = QAction(pluginParent, parentMenu) action.setData(None) parentMenu.addAction(action) # If it is a plugin, we add it to the menu elif isAPlugin: action = QAction(pluginParent, parentMenu) action.setData(pluginId) parentMenu.addAction(action) # Else we create a new menu else: submenu = QMenu(view) submenu.setTitle(pluginParent) parentMenu.addMenu(submenu) createMenu(submenu, parentName + pluginParent + "/", view)
def duplicationNode(self): """ Duplicates the current node(s). """ buttleData = ButtleDataSingleton().get() if buttleData.getCurrentSelectedNodeWrappers() != []: for node in buttleData.getCurrentSelectedNodeWrappers(): # Create a node giving the current selected node's type, x and y nodeType = node.getNode().getType() coord = node.getNode().getCoord() buttleData.getGraph().createNode(nodeType, coord[0], coord[1]) newNode = buttleData.getGraph().getNodes()[-1] # Get the current selected node's properties nameUser = node.getNameUser() + "_duplicate" oldCoord = node.getNode().getOldCoord() color = node.getNode().getColor() # Use the current selected node's properties to set the duplicated node's properties newNode.setNameUser(nameUser) newNode.setOldCoord(oldCoord[0], oldCoord[1]) newNode.setColor(color) newNode.getTuttleNode().getParamSet().copyParamsValues( node.getNode().getTuttleNode().getParamSet()) # update undo/redo display self.undoRedoChanged()
def getNbFrames(self): """ Returns the number of frames of this node. """ #import which needs to be changed in the future from buttleofx.data import ButtleDataSingleton buttleData = ButtleDataSingleton().get() graph = buttleData.getGraph().getGraphTuttle() node = self._node.getTuttleNode().asImageEffectNode() try: self.setFrameError("") graph.setup() except Exception as e: logging.debug("can't get nbFrames of the node" + self._node.getName()) self.setFrameError(str(e)) return 0 raise timeDomain = node.getTimeDomain( ) #getTimeDomain() returns first frame and last one nbFrames = timeDomain.max - timeDomain.min #not very elegant but allow to avoid a problem because an image returns a number of frames very high if nbFrames > 100000000 or nbFrames < 0: nbFrames = 1 #print "nbFrames: ", nbFrames return nbFrames
def retrieveImage(self, frame, frameChanged): """ Computes the node at the frame indicated if the frame has changed (if the time has changed). """ buttleData = ButtleDataSingleton().get() #Get the name of the currentNode of the viewer node = buttleData.getCurrentViewerNodeName() #Get the gloabl hashCode of the node if node is not None: hashMap = tuttle.NodeHashContainer() buttleData.getGraph().getGraphTuttle().computeGlobalHashAtTime( hashMap, frame) node_hashCode = hashMap.getHash(node, frame) #Get the map mapNodeToImage = buttleData.getMapNodeNameToComputedImage() try: self.setNodeError("") for key in mapNodeToImage.keys(): #If the image is already calculated if node_hashCode == key and frameChanged is False: #print "**************************Image already calculated**********************" return mapNodeToImage.get(node_hashCode) #If it is not #print "**************************Image is not already calculated**********************" return self.computeNode(node, frame) except Exception as e: logging.debug("Can't display node : " + node) self.setNodeError(str(e)) raise
def deleteSelection(self): buttleData = ButtleDataSingleton().get() if (buttleData.currentConnectionWrapper): self._connectionManager.disconnect( buttleData.currentConnectionWrapper) else: self._nodeManager.destructionNodes()
def nodeMoved(self, nodeName, newX, newY): """ This function pushes a cmdMoved in the CommandManager. """ from buttleofx.data import ButtleDataSingleton buttleData = ButtleDataSingleton().get() node = buttleData.getGraph().getNode(nodeName) # What is the value of the movement (compared to the old position) ? oldX, oldY = node.getOldCoord() xMovement = newX - oldX yMovement = newY - oldY # if the node did'nt really move, nothing is done if (xMovement, xMovement) == (0, 0): return commands = [] # we create a GroupUndoableCommands of CmdSetCoord for each selected node for selectedNodeWrapper in buttleData.getCurrentSelectedNodeWrappers(): # we get the needed informations 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)) commands.append(cmdMoved) # then we push the group of commands CommandManager().push(GroupUndoableCommands(commands))
def disconnect(self, connectionWrapper): """ Removes a connection between 2 clips. """ buttleData = ButtleDataSingleton().get() buttleData.getGraph().deleteConnection( connectionWrapper.getConnection())
def graphHadChanged(self): """ Indicates to ButtleData that a command just had been done. This function will update the property graphCanBeSaved of ButtleData and will change the display of the "save graph" icon. """ from buttleofx.data import ButtleDataSingleton ButtleDataSingleton().get().setGraphCanBeSaved(self.savedGraphIndex != self.index)
def canConnect(self, clip1, clip2): """ Returns True if the connection between the nodes is possible, else False. A connection is possible if the clip isn't already taken, and if the clips are from 2 different nodes, not already connected. """ buttleData = ButtleDataSingleton().get() graph = buttleData.getGraph() # if the clips are from the same node : False if (clip1.getNodeName() == clip2.getNodeName()): return False # if the clips are 2 inputs or 2 outputs : False if (clip1.getClipName() == "Output" and clip2.getClipName() == "Output") or (clip1.getClipName() != "Output" and clip2.getClipName() != "Output"): return False # if the input clip is already taken : False if (clip1.getClipName() != "Output" and graph.contains(clip1)) or (clip2.getClipName() != "Output" and graph.contains(clip2)): return False # if the nodes containing the clips are already connected : False if (graph.nodesConnected(clip2, clip1)): return False return True
def canConnectTmpNodes(self, dataTmpClip, clip, clipIndex): """ Returns True if the connection between the nodes is possible, else False. This function is called by Clip.qml on the event onDragEnter, to display in real time if the nodes can be connected (during a creation of a connection). It simulates a connection and calls the function self.canConnect(clip1, clip2). """ buttleData = ButtleDataSingleton().get() # we split the data of the tmpClip (from mimeData) to find needed informations about this clip. infosTmpClip = dataTmpClip.split("/") if infosTmpClip[0] != "clip" or len(infosTmpClip) != 4: return False else: tmpClipNodeName, tmpClipName, tmpClipIndex = infosTmpClip[ 1], infosTmpClip[2], int(infosTmpClip[3]) # we find the position of this tmpClip to be able to create a IdClip object. positionTmpClip = buttleData.getGraphWrapper().getPositionClip( tmpClipNodeName, tmpClipName, tmpClipIndex) tmpClip = IdClip(tmpClipNodeName, tmpClipName, clipIndex, positionTmpClip) if tmpClip: # idem, for the "dropped" clip = newClip positionNewClip = buttleData.getGraphWrapper().getPositionClip( clip.getNodeName(), clip.getClipName(), clipIndex) newClip = IdClip(clip.getNodeName(), clip.getClipName(), clipIndex, positionNewClip) # finally we return if the clips can be connected return self.canConnect(tmpClip, newClip) else: return False
def creationNode(self, nodeType, x=20, y=20): """ Creates a node. """ buttleData = ButtleDataSingleton().get() buttleData.getGraph().createNode(nodeType, x, y) # update undo/redo display self.undoRedoChanged()
def nodeMoved(self, nodeName, x, y): """ This function pushes a cmdMoved in the CommandManager. """ buttleData = ButtleDataSingleton().get() buttleData.getGraph().nodeMoved(nodeName, x, y) # update undo/redo display self.undoRedoChanged()
def pause(self): logging.debug("--------------pause-------------") self._timer.stop() buttleData = ButtleDataSingleton().get() if buttleData.getVideoIsPlaying(): buttleData.setVideoIsPlaying(False) # close processGraph and delete it buttleData.getProcessGraph().endSequence() buttleData.setProcessGraph(None) self.framePlayerChanged.emit()
def connectionDropEvent(self, dataTmpClip, clip, clipIndex): """ Creates or deletes a connection between 2 clips ('tmpClip', the "dragged" clip, and 'newClip', the "dropped" clip) Arguments : - dataTmpClip : the string from mimeData, identifying the tmpClip (the "dragged" clip). - clip : the ClipWrapper of the "dropped" clip. - clipIndex : the index of the "dropped" clip. """ buttleData = ButtleDataSingleton().get() # we split the data of the tmpClip (from mimeData) to find needed informations about this clip. infosTmpClip = dataTmpClip.split("/") if infosTmpClip[0] != "clip" or len(infosTmpClip) != 4: return # use exception ! else: tmpClipNodeName, tmpClipName, tmpClipIndex = infosTmpClip[ 1], infosTmpClip[2], int(infosTmpClip[3]) # we find the position of this tmpClip to be able to create a IdClip object. positionTmpClip = buttleData.getGraphWrapper().getPositionClip( tmpClipNodeName, tmpClipName, tmpClipIndex) tmpClip = IdClip(tmpClipNodeName, tmpClipName, clipIndex, positionTmpClip) if tmpClip: # idem, for the "dropped" clip = newClip positionNewClip = buttleData.getGraphWrapper().getPositionClip( clip.getNodeName(), clip.getClipName(), clipIndex) newClip = IdClip(clip.getNodeName(), clip.getClipName(), clipIndex, positionNewClip) # a connection must be created from the ouput clip to the input clip (the order of the arguments is important !) if tmpClip.getClipName() == "Output": clipOut, clipIn = tmpClip, newClip else: clipOut, clipIn = newClip, tmpClip # if the clips can be connected, we connect them if self.canConnect(clipOut, clipIn): self.connect(clipOut, clipIn) return # else if they can't be connected, we check if they are already connected, and disconnect them if it is the case. else: connection = buttleData.getGraph().getConnectionByClips( clipOut, clipIn) if connection: self.disconnect( buttleData.getGraphWrapper().getConnectionWrapper( connection.getId())) return # update undo/redo display self.undoRedoChanged()
def stop(self): logging.debug("--------------stop-------------") self._timer.stop() buttleData = ButtleDataSingleton().get() # if a video is reading, we need to close the processGraph if buttleData.getVideoIsPlaying(): buttleData.setVideoIsPlaying(False) # close processGraph and delete it buttleData.getProcessGraph().endSequence() buttleData.setProcessGraph(None) # return to the beginning of the video self._frame = 0 self.framePlayerChanged.emit()
def emitNodeContentChanged(self): """ If necessary, call emitOneParamChangedSignal, to warn buttleEvent that a param just changed (to update the viewer) Also emit nodeContentChanged signal, to warn the node wrapper that a param just changed (for property si secret of other params for example !) """ from buttleofx.data import ButtleDataSingleton buttleData = ButtleDataSingleton().get() if (self._name == buttleData.getCurrentViewerNodeName()): # to buttleEvent buttleEvent = ButtleEventSingleton().get() buttleEvent.emitOneParamChangedSignal() # to the node wrapper self.nodeContentChanged()
def redo(self): """ Calls the cmdManager to redo the last command. """ cmdManager = CommandManager() cmdManager.redo() # emit undo/redo display self.emitUndoRedoChanged() # if we need to update params or viewer buttleData = ButtleDataSingleton().get() buttleData.currentParamNodeChanged.emit() buttleData.currentViewerNodeChanged.emit()
def dropFile(self, url, x, y): """ Drops a file on the graph. - Image or video : creates a reader node. - Json : load a graph (if the format allows it) """ buttleData = ButtleDataSingleton().get() extension = url.split(".")[-1].lower() if extension == 'bofx': buttleData.loadData(url) # also need to verify the json format else: buttleData.getGraph().createReaderNode(url, x, y) # update undo/redo display self.undoRedoChanged()
def computeNode(self, node, frame): """ Computes the node (displayed in the viewer) at the frame indicated. """ buttleData = ButtleDataSingleton().get() graphTuttle = buttleData.getGraph().getGraphTuttle() #Get the output where we save the result self._tuttleImageCache = tuttle.MemoryCache() if buttleData.getVideoIsPlaying(): # if a video is playing processGraph = buttleData.getProcessGraph() processGraph.setupAtTime(frame) processGraph.processAtTime(self._tuttleImageCache, frame) else: # if it's an image only processOptions = tuttle.ComputeOptions(int(frame)) processGraph = tuttle.ProcessGraph(processOptions, graphTuttle, [node]) processGraph.setup() timeRange = tuttle.TimeRange(frame, frame, 1) # buttleData.getTimeRange() processGraph.beginSequence(timeRange) processGraph.setupAtTime(frame) processGraph.processAtTime(self._tuttleImageCache, frame) processGraph.endSequence() self._computedImage = self._tuttleImageCache.get(0) #Add the computedImage to the map hashMap = tuttle.NodeHashContainer() graphTuttle.computeGlobalHashAtTime(hashMap, frame) hasCode = hashMap.getHash(node, frame) #Max 15 computedImages saved in memory if hasCode not in buttleData._mapNodeNameToComputedImage.keys( ) and len(buttleData._mapNodeNameToComputedImage) < 15: buttleData._mapNodeNameToComputedImage.update( {hasCode: self._computedImage}) elif hasCode not in buttleData._mapNodeNameToComputedImage.keys( ) and len(buttleData._mapNodeNameToComputedImage) >= 15: #Delete a computed image from the memory (random) buttleData._mapNodeNameToComputedImage.popitem() buttleData._mapNodeNameToComputedImage.update( {hasCode: self._computedImage}) return self._computedImage
def launchProcessGraph(self): buttleData = ButtleDataSingleton().get() #Get the name of the currentNode of the viewer node = buttleData.getCurrentViewerNodeName() # initialization of the process graph graph = buttleData.getGraph().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) processGraph = tuttle.ProcessGraph(self._processOptions, graph, [node]) processGraph.setup() processGraph.beginSequence(timeRange) # communicate processGraph to buttleData buttleData.setProcessGraph(processGraph) buttleData.setVideoIsPlaying(True)
def nodeIsMoving(self, nodeName, newX, newY): """ This function updates the position of the selected nodes and the connections, when one or several nodes are moving. """ buttleData = ButtleDataSingleton().get() node = buttleData.getGraph().getNode(nodeName) # What is the value of the movement (compared to the old position) ? oldX, oldY = node.getCoord() xMovement = newX - oldX yMovement = newY - oldY # for each selected node, we update the position considering the value of the movement for selectedNodeWrapper in buttleData.getCurrentSelectedNodeWrappers(): selectedNode = selectedNodeWrapper.getNode() currentX, currentY = selectedNode.getCoord() selectedNode.setCoord(currentX + xMovement, currentY + yMovement) # we update also the position of all the connections buttleData.getGraph().connectionsCoordChanged(selectedNode)
def getFPS(self): """ Returns the FPS of this node. """ #import which needs to be changed in the future from buttleofx.data import ButtleDataSingleton buttleData = ButtleDataSingleton().get() graph = buttleData.getGraph().getGraphTuttle() node = self._node.getTuttleNode().asImageEffectNode() try: self.setFpsError("") graph.setup() except Exception as e: logging.debug("can't get fps of the node" + self._node.getName()) self.setFpsError(str(e)) return 1 raise framerate = node.getOutputFrameRate() #print "framerate: ", framerate return framerate
def mosquitoDragEvent(self): """ Function called when the viewer's mosquito is dragged. The function sends the mimeData and launches a drag event. """ widget = QtGui.QWidget() drag = QtGui.QDrag(widget) mimeData = QtCore.QMimeData() # set data (here it's just a text) mimeData.setText("mosquito_of_the_dead") drag.setMimeData(mimeData) # sets the image of the mosquito in the pixmap filePath = ButtleDataSingleton().get().getButtlePath() imgPath = filePath + "/gui/img/mosquito/mosquito.png" drag.setPixmap(QtGui.QPixmap(imgPath)) # starts the drag drag.exec_(QtCore.Qt.MoveAction)
def copyNode(self): """ Copies the current node(s). """ buttleData = ButtleDataSingleton().get() # Clear the info saved in currentCopiedNodesInfo buttleData.clearCurrentCopiedNodesInfo() # Save new data in currentCopiedNodesInfo for each selected node if buttleData.getCurrentSelectedNodeWrappers() != []: for node in buttleData.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"}) buttleData.getCurrentCopiedNodesInfo()[ node.getName()] = copyNode # Emit the change for the toolbar buttleData.pastePossibilityChanged.emit()
def pasteNode(self): """ Pasts the current node(s). """ buttleData = ButtleDataSingleton().get() # If nodes have been copied previously if buttleData.getCurrentCopiedNodesInfo(): # Create a copy for each node copied for node in buttleData.getCurrentCopiedNodesInfo(): buttleData.getGraph().createNode( buttleData.getCurrentCopiedNodesInfo()[node]["nodeType"], 20, 20) newNode = buttleData.getGraph().getNodes()[-1] newNode.setColor( buttleData.getCurrentCopiedNodesInfo()[node]["color"]) newNode.setNameUser( buttleData.getCurrentCopiedNodesInfo()[node]["nameUser"] + buttleData.getCurrentCopiedNodesInfo()[node]["mode"]) newNode.getTuttleNode().getParamSet().copyParamsValues( buttleData.getCurrentCopiedNodesInfo()[node]["params"]) # update undo/redo display self.undoRedoChanged()
def destructionNodes(self): """ Deletes the current node(s). """ buttleData = ButtleDataSingleton().get() # if the params of the current node deleted are display if buttleData.getCurrentParamNodeName( ) in buttleData.getCurrentSelectedNodeNames(): buttleData.setCurrentParamNodeName(None) # if the viewer of the current node deleted is display if buttleData.getCurrentViewerNodeName( ) in buttleData.getCurrentSelectedNodeNames(): buttleData.setCurrentViewerNodeName(None) # if the viewer display a node affected by the destruction # need something from Tuttle # if at least one node in the graph if len(buttleData.getGraphWrapper().getNodeWrappers()) > 0 and len( buttleData.getGraph().getNodes()) > 0: # if a node is selected if buttleData.getCurrentSelectedNodeNames() != []: buttleData.getGraph().deleteNodes([ nodeWrapper.getNode() for nodeWrapper in buttleData.getCurrentSelectedNodeWrappers() ]) buttleData.clearCurrentSelectedNodeNames() # emit signals buttleData.currentParamNodeChanged.emit() buttleData.currentViewerNodeChanged.emit() buttleData.currentSelectedNodesChanged.emit() # update undo/redo display self.undoRedoChanged()
def cutNode(self): """ Cuts the current node(s). """ # Call the copyNode function to save the data of the selected nodes self.copyNode() buttleData = ButtleDataSingleton().get() # If we are sure that at least one node is selected if buttleData.getCurrentSelectedNodeWrappers() != []: for node in buttleData.getCurrentSelectedNodeWrappers(): # We precise that we want to cut the node and not only copy it buttleData.getCurrentCopiedNodesInfo()[node.getName()].update( {"mode": ""}) # And we delete it self.destructionNodes() # And update the view if necessary if buttleData.getCurrentViewerNodeName( ) in buttleData.getCurrentSelectedNodeNames(): buttleData.setCurrentViewerNodeName(None) if buttleData.getCurrentParamNodeName( ) in buttleData.getCurrentSelectedNodeNames(): buttleData.setCurrentParamNodeName(None) # Emit the change for the toolbar buttleData.pastePossibilityChanged.emit()
def clearMapOfImageAlreadyCalculated(self): buttleData = ButtleDataSingleton().get() buttleData._mapNodeNameToComputedImage.clear()
def connect(self, clipOut, clipIn): """ Adds a connection between 2 clips. """ buttleData = ButtleDataSingleton().get() buttleData.getGraph().createConnection(clipOut, clipIn)
def main(argv): #preload Tuttle tuttle.core().preload() # give to QML acces to TimerPlayer defined in buttleofx/gui/viewer QtDeclarative.qmlRegisterType(TimerPlayer, "TimerPlayer", 1, 0, "TimerPlayer") # add new QML type QtDeclarative.qmlRegisterType(Finder, "FolderListViewItem", 1, 0, "FolderListView") if tuttleofx_installed: QtDeclarative.qmlRegisterType(GLViewport_tuttleofx, "Viewport", 1, 0, "GLViewport") else: QtDeclarative.qmlRegisterType(GLViewport_pil, "Viewport", 1, 0, "GLViewport") # init undo_redo contexts cmdManager = CommandManager() cmdManager.setActive() cmdManager.clean() # create QApplication app = ButtleApp(argv) # create the declarative view view = QtDeclarative.QDeclarativeView() view.setViewport(QtOpenGL.QGLWidget()) view.setViewportUpdateMode(QtDeclarative.QDeclarativeView.FullViewportUpdate) # data buttleData = ButtleDataSingleton().get().init(view, currentFilePath) # manager buttleManager = ButtleManagerSingleton().get().init() # event buttleEvent = ButtleEventSingleton().get() # Menus fileMenu = MenuWrapper("file", 0, view, app) editMenu = MenuWrapper("edit", 0, view, app) addMenu = MenuWrapper("buttle/", 1, view, app) # expose data to QML rc = view.rootContext() rc.setContextProperty("_buttleApp", app) rc.setContextProperty("_buttleData", buttleData) rc.setContextProperty("_buttleManager", buttleManager) rc.setContextProperty("_buttleEvent", buttleEvent) rc.setContextProperty("_fileMenu", fileMenu) rc.setContextProperty("_editMenu", editMenu) rc.setContextProperty("_addMenu", addMenu) # set the view view.setSource(os.path.join(currentFilePath, "MainWindow.qml")) view.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView) view.setWindowTitle("ButtleOFX") view.setWindowIcon(QtGui.QIcon("blackMosquito.png")) view.setWindowIconText("ButtleOFX") view.setVisible(True) # Declare we are using instant coding tool on this view qic = QmlInstantCoding(view, verbose=True) # Add any source file (.qml and .js by default) in current working directory qic.addFilesFromDirectory(os.getcwd(), recursive=True) #add._menu.popup(view.mapToGlobal(QtCore.QPoint(0, 0))) view.show() app.exec_()