Example #1
0
    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))
Example #2
0
    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))
Example #3
0
    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
Example #4
0
    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))
Example #5
0
    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)))