def moveNode(self, node, position): """ Move 'node' to the given 'position'. Args: node (Node): the node to move position (QPoint): the target position """ if isinstance(position, QPoint): position = Position(position.x(), position.y()) self.push(commands.MoveNodeCommand(self._graph, node, position))
def addNewNode(self, nodeType, position=None, **kwargs): """ [Undoable] Create a new Node of type 'nodeType' and returns it. Args: nodeType (str): the type of the Node to create. position (QPoint): (optional) the initial position of the node **kwargs: optional node attributes values Returns: Node: the created node """ if isinstance(position, QPoint): position = Position(position.x(), position.y()) return self.push(commands.AddNodeCommand(self._graph, nodeType, position=position, **kwargs))
def duplicateNode(self, srcNode, duplicateFollowingNodes=False): """ Duplicate a node an optionally all the following nodes to graph leaves. Args: srcNode (Node): node to start the duplication from duplicateFollowingNodes (bool): whether to duplicate all the following nodes to graph leaves Returns: [Nodes]: the list of duplicated nodes """ title = "Duplicate Nodes from {}" if duplicateFollowingNodes else "Duplicate {}" # enable updates between duplication and layout to get correct depths during layout with self.groupedGraphModification(title.format(srcNode.name), disableUpdates=False): # disable graph updates during duplication with self.groupedGraphModification("Node duplication", disableUpdates=True): duplicates = self.push( commands.DuplicateNodeCommand(self._graph, srcNode, duplicateFollowingNodes)) # move nodes below the bounding box formed by the duplicated node(s) bbox = self._layout.boundingBox(duplicates) for n in duplicates: self.moveNode( n, Position(n.x, bbox[3] + self.layout.gridSpacing + n.y)) return duplicates
def autoLayout(self, fromNode=None, toNode=None, startX=0, startY=0): """ Perform auto-layout from 'fromNode' to 'toNode', starting from (startX, startY) position. Args: fromNode (BaseNode): where to start the auto layout from toNode (BaseNode): up to where to perform the layout startX (int): start position x coordinate startY (int): start position y coordinate """ fromIndex = self.graph.nodes.indexOf(fromNode) if fromNode else 0 toIndex = self.graph.nodes.indexOf( toNode) if toNode else self.graph.nodes.count - 1 def getDepth(n): return getattr(n, self._depthAttribute[self._depthMode]) maxDepth = max([getDepth(n) for n in self.graph.nodes.values()]) grid = [[] for _ in range(maxDepth + 1)] # retrieve reference depth from start node zeroDepth = getDepth( self.graph.nodes.at(fromIndex)) if fromIndex > 0 else 0 for i in range(fromIndex, toIndex + 1): n = self.graph.nodes.at(i) grid[getDepth(n) - zeroDepth].append(n) with self.graph.groupedGraphModification("Graph Auto-Layout"): for x, line in enumerate(grid): for y, node in enumerate(line): px = startX + x * (self._nodeWidth + self._gridSpacing) py = startY + y * (self._nodeHeight + self._gridSpacing) self.graph.moveNode(node, Position(px, py))
def handleFilesDrop(self, drop, cameraInit): """ Handle drop events aiming to add images to the Reconstruction. Fetching urls from dropEvent is generally expensive in QML/JS (bug ?). This method allows to reduce process time by doing it on Python side. """ filesByType = self.getFilesByTypeFromDrop(drop) if filesByType.images: self.importImagesAsync(filesByType.images, cameraInit) if filesByType.videos: boundingBox = self.layout.boundingBox() keyframeNode = self.addNewNode( "KeyframeSelection", position=Position(boundingBox[0], boundingBox[1] + boundingBox[3])) keyframeNode.mediaPaths.value = filesByType.videos if len(filesByType.videos) == 1: newVideoNodeMessage = "New node '{}' added for the input video.".format( keyframeNode.getLabel()) else: newVideoNodeMessage = "New node '{}' added for a rig of {} synchronized cameras.".format( keyframeNode.getLabel(), len(filesByType.videos)) self.info.emit( Message( "Video Input", newVideoNodeMessage, "Warning: You need to manually compute the KeyframeSelection node \n" "and then reimport the created images into Meshroom for the reconstruction.\n\n" "If you know the Camera Make/Model, it is highly recommended to declare them in the Node." )) if filesByType.panoramaInfo: if len(filesByType.panoramaInfo) > 1: self.error.emit( Message( "Multiple XML files in input", "Ignore the xml Panorama files:\n\n'{}'.".format( ',\n'.join(filesByType.panoramaInfo)), "", )) else: panoramaInitNodes = self.graph.nodesByType('PanoramaInit') for panoramaInfoFile in filesByType.panoramaInfo: for panoramaInitNode in panoramaInitNodes: panoramaInitNode.attribute( 'config').value = panoramaInfoFile if panoramaInitNodes: self.info.emit( Message( "Panorama XML", "XML file declared on PanoramaInit node", "XML file '{}' set on node '{}'".format( ','.join(filesByType.panoramaInfo), ','.join([ n.getLabel() for n in panoramaInitNodes ])), )) else: self.error.emit( Message( "No PanoramaInit Node", "No PanoramaInit Node to set the Panorama file:\n'{}'." .format(','.join(filesByType.panoramaInfo)), "", )) if not filesByType.images and not filesByType.videos and not filesByType.panoramaInfo: if filesByType.other: extensions = set( [os.path.splitext(url)[1] for url in filesByType.other]) self.error.emit( Message( "No Recognized Input File", "No recognized input file in the {} dropped files". format(len(filesByType.other)), "Unknown file extensions: " + ', '.join(extensions)))