diff --git a/meshroom/ui/app.py b/meshroom/ui/app.py index 3f9df1fa0e..1ab9a06815 100644 --- a/meshroom/ui/app.py +++ b/meshroom/ui/app.py @@ -198,6 +198,9 @@ def _pipelineTemplateFiles(self): templates.append(variant) return templates + def _pipelineTemplateNames(self): + return [p["name"] for p in self.pipelineTemplateFiles] + @Slot() def reloadTemplateList(self): for f in meshroom.core.pipelineTemplatesFolders: @@ -352,5 +355,6 @@ def _default8bitViewerEnabled(self): pipelineTemplateFilesChanged = Signal() recentProjectFilesChanged = Signal() pipelineTemplateFiles = Property("QVariantList", _pipelineTemplateFiles, notify=pipelineTemplateFilesChanged) + pipelineTemplateNames = Property("QVariantList", _pipelineTemplateNames, notify=pipelineTemplateFilesChanged) recentProjectFiles = Property("QVariantList", _recentProjectFiles, notify=recentProjectFilesChanged) default8bitViewerEnabled = Property(bool, _default8bitViewerEnabled, constant=True) diff --git a/meshroom/ui/commands.py b/meshroom/ui/commands.py index a5db90ae72..bbfb30631c 100755 --- a/meshroom/ui/commands.py +++ b/meshroom/ui/commands.py @@ -221,10 +221,11 @@ class ImportProjectCommand(GraphCommand): """ Handle the import of a project into a Graph. """ - def __init__(self, graph, filepath=None, yOffset=0, parent=None): + def __init__(self, graph, filepath=None, position=None, yOffset=0, parent=None): super(ImportProjectCommand, self).__init__(graph, parent) self.filepath = filepath self.importedNames = [] + self.position = position self.yOffset = yOffset def redoImpl(self): @@ -239,9 +240,12 @@ def redoImpl(self): for node in importedNodes: self.importedNames.append(node.name) - self.graph.node(node.name).position = Position(node.x, node.y + lowestY + self.yOffset) + if self.position is not None: + self.graph.node(node.name).position = Position(node.x + self.position.x, node.y + self.position.y) + else: + self.graph.node(node.name).position = Position(node.x, node.y + lowestY + self.yOffset) - return status + return importedNodes def undoImpl(self): for nodeName in self.importedNames: diff --git a/meshroom/ui/graph.py b/meshroom/ui/graph.py index 0abf173b45..7ace78d095 100644 --- a/meshroom/ui/graph.py +++ b/meshroom/ui/graph.py @@ -353,8 +353,9 @@ def loadGraph(self, filepath, setupProjectFile=True): self.setGraph(g) return status - @Slot(QUrl, result=bool) - def importProject(self, filepath): + @Slot(QUrl, result="QVariantList") + @Slot(QUrl, QPoint, result="QVariantList") + def importProject(self, filepath, position=None): if isinstance(filepath, (QUrl)): # depending how the QUrl has been initialized, # toLocalFile() may return the local path or an empty string @@ -363,8 +364,10 @@ def importProject(self, filepath): localFile = filepath.toString() else: localFile = filepath + if isinstance(position, QPoint): + position = Position(position.x(), position.y()) yOffset = self.layout.gridSpacing + self.layout.nodeHeight - return self.push(commands.ImportProjectCommand(self._graph, localFile, yOffset=yOffset)) + return self.push(commands.ImportProjectCommand(self._graph, localFile, position=position, yOffset=yOffset)) @Slot(QUrl) def saveAs(self, url): diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index 36ccc4a41d..3262c62258 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -214,18 +214,37 @@ Item { Menu { id: newNodeMenu property point spawnPosition + property variant menuKeys: Object.keys(root.nodeTypesModel).concat(Object.values(MeshroomApp.pipelineTemplateNames)) function createNode(nodeType) { - // add node via the proper command in uigraph - var node = uigraph.addNewNode(nodeType, spawnPosition) - selectNode(node) + uigraph.clearNodeSelection() // Ensures that only the created node / imported pipeline will be selected + + // "nodeType" might be a pipeline (artificially added in the "Pipelines" category) instead of a node + // If it is not a pipeline to import, then it must be a node + if (!importPipeline(nodeType)) { + // Add node via the proper command in uigraph + var node = uigraph.addNewNode(nodeType, spawnPosition) + selectNode(node) + } close() } + function importPipeline(pipeline) + { + if (MeshroomApp.pipelineTemplateNames.includes(pipeline)) { + var url = MeshroomApp.pipelineTemplateFiles[MeshroomApp.pipelineTemplateNames.indexOf(pipeline)]["path"] + var nodes = uigraph.importProject(Filepath.stringToUrl(url), spawnPosition) + uigraph.selectedNode = nodes[0] + uigraph.selectNodes(nodes) + return true + } + return false + } + function parseCategories() { - // organize nodes based on their category + // Organize nodes based on their category // {"category1": ["node1", "node2"], "category2": ["node3", "node4"]} let categories = {}; for (const [name, data] of Object.entries(root.nodeTypesModel)) { @@ -235,11 +254,15 @@ Item { } categories[category].push(name) } + + // Add a "Pipelines" category, filled with the list of templates to create pipelines from the menu + categories["Pipelines"] = MeshroomApp.pipelineTemplateNames; + return categories } onVisibleChanged: { - if(visible) { + if (visible) { // when menu is shown, // clear and give focus to the TextField filter searchBar.clear() @@ -306,7 +329,7 @@ Item { Repeater { id: nodeMenuRepeater - model: searchBar.text != "" ? Object.keys(root.nodeTypesModel) : undefined + model: searchBar.text != "" ? Object.values(newNodeMenu.menuKeys) : undefined // create Menu items from available items delegate: menuItemDelegateComponent