From 04ab813cef80a81851481ab0be250dc3b38d5eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Fri, 25 Nov 2022 18:27:57 +0100 Subject: [PATCH 1/6] [ui] GraphEditor: Add a "Pipelines" category in the node menu Artificially add a "Pipelines" category in the node menu. The category contains the list of available pipeline templates. Selecting a pipeline instead of a regular node type "creates" the pipeline in the GraphEditor (using the "import project" functionality). The imported pipeline is by default placed under the current graph. This commit does not include enabling the search for pipelines from the "Pipelines" category in the search bar. --- meshroom/ui/qml/GraphEditor/GraphEditor.qml | 41 ++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index 36ccc4a41d..6f4b6b689e 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -217,15 +217,38 @@ Item { function createNode(nodeType) { - // add node via the proper command in uigraph - var node = uigraph.addNewNode(nodeType, spawnPosition) - selectNode(node) + // "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) + { + var pipelineNames = [] + var pipelinePaths = [] + for (const [_, data] of Object.entries(MeshroomApp.pipelineTemplateFiles)) { + let name = data["name"] + let path = data["path"] + pipelineNames.push(name) + pipelinePaths.push(path) + } + + if (pipelineNames.includes(pipeline)) { + var url = Filepath.stringToUrl(pipelinePaths[pipelineNames.indexOf(pipeline)]) + uigraph.importProject(url) + 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 +258,19 @@ Item { } categories[category].push(name) } + + // Add list of templates to create pipelines + categories["Pipelines"] = []; + for (const [_, data] of Object.entries(MeshroomApp.pipelineTemplateFiles)) { + let pipeline = data["name"]; + categories["Pipelines"].push(pipeline) + } + return categories } onVisibleChanged: { - if(visible) { + if (visible) { // when menu is shown, // clear and give focus to the TextField filter searchBar.clear() From c2570ca0fb7858f968c55e3dcd32a76b10658c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Fri, 2 Dec 2022 18:13:17 +0100 Subject: [PATCH 2/6] [ui] GraphEditor: Import pipeline on the mouse's position with the node menu If a pipeline is imported with the node menu instead of the "Import Project" action from the File menu, the top-left "corner" of the graph should be placed on the mouse's position. The position of pipelines imported with the "Import Project" menu remains unchanged: they are automatically placed below the lowest node in the current graph. --- meshroom/ui/commands.py | 8 ++++++-- meshroom/ui/graph.py | 7 +++++-- meshroom/ui/qml/GraphEditor/GraphEditor.qml | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/meshroom/ui/commands.py b/meshroom/ui/commands.py index a5db90ae72..208826139e 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,7 +240,10 @@ 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 diff --git a/meshroom/ui/graph.py b/meshroom/ui/graph.py index 0abf173b45..e9769bd4c3 100644 --- a/meshroom/ui/graph.py +++ b/meshroom/ui/graph.py @@ -354,7 +354,8 @@ def loadGraph(self, filepath, setupProjectFile=True): return status @Slot(QUrl, result=bool) - def importProject(self, filepath): + @Slot(QUrl, QPoint, result=bool) + 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 6f4b6b689e..28265a8635 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -240,7 +240,7 @@ Item { if (pipelineNames.includes(pipeline)) { var url = Filepath.stringToUrl(pipelinePaths[pipelineNames.indexOf(pipeline)]) - uigraph.importProject(url) + uigraph.importProject(url, spawnPosition) return true } return false From 2e5f471b7b9e00da1676536d0a0028f5cd60d20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Tue, 29 Nov 2022 18:08:15 +0100 Subject: [PATCH 3/6] [ui] Add a "pipelineTemplateNames" property This property simplifies and optimizes the addition of templates in the node menu and their import into the graph, as it prevents us from looping over the entire "pipelineTemplateFiles" dictionary to retrieve the information we need. --- meshroom/ui/app.py | 4 ++++ meshroom/ui/qml/GraphEditor/GraphEditor.qml | 23 +++++---------------- 2 files changed, 9 insertions(+), 18 deletions(-) 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/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index 28265a8635..d2c48a57fd 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -229,18 +229,9 @@ Item { function importPipeline(pipeline) { - var pipelineNames = [] - var pipelinePaths = [] - for (const [_, data] of Object.entries(MeshroomApp.pipelineTemplateFiles)) { - let name = data["name"] - let path = data["path"] - pipelineNames.push(name) - pipelinePaths.push(path) - } - - if (pipelineNames.includes(pipeline)) { - var url = Filepath.stringToUrl(pipelinePaths[pipelineNames.indexOf(pipeline)]) - uigraph.importProject(url, spawnPosition) + if (MeshroomApp.pipelineTemplateNames.includes(pipeline)) { + var url = MeshroomApp.pipelineTemplateFiles[MeshroomApp.pipelineTemplateNames.indexOf(pipeline)]["path"] + uigraph.importProject(Filepath.stringToUrl(url), spawnPosition) return true } return false @@ -259,12 +250,8 @@ Item { categories[category].push(name) } - // Add list of templates to create pipelines - categories["Pipelines"] = []; - for (const [_, data] of Object.entries(MeshroomApp.pipelineTemplateFiles)) { - let pipeline = data["name"]; - categories["Pipelines"].push(pipeline) - } + // Add a "Pipelines" category, filled with the list of templates to create pipelines from the menu + categories["Pipelines"] = MeshroomApp.pipelineTemplateNames; return categories } From aedf0ef4c1715a8271479a047db95015df116488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Tue, 29 Nov 2022 18:18:05 +0100 Subject: [PATCH 4/6] [ui] GraphEditor: Add the pipelines in the node menu's search bar Pipelines can be searched in the list of available node types / pipelines like regular node types. --- meshroom/ui/qml/GraphEditor/GraphEditor.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index d2c48a57fd..3fe3faa699 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -214,6 +214,7 @@ Item { Menu { id: newNodeMenu property point spawnPosition + property variant menuKeys: Object.keys(root.nodeTypesModel).concat(Object.values(MeshroomApp.pipelineTemplateNames)) function createNode(nodeType) { @@ -324,7 +325,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 From 9d88ad91d51fa9668d89531f29f3472ff9aa9746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Tue, 29 Nov 2022 18:43:56 +0100 Subject: [PATCH 5/6] [ui] GraphEditor: clear node selection before adding new nodes from the menu This commit is a bugfix. When adding new nodes with the node menu, the selection was never explicitly cleared and nodes were added to the selection rather than being exclusively selected. This behaviour used to be hidden when the node menu could only appear with a right click, as the click would automatically empty the selection. However, with the node menu being now openable with the TAB key, there might not be a click to empty the selection. If new nodes are added by opening the menu with the TAB key and using the arrow keys to select the node to create, an infinite number of them can be created and added to the selection. --- meshroom/ui/qml/GraphEditor/GraphEditor.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index 3fe3faa699..0d801606d1 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -218,6 +218,8 @@ Item { function createNode(nodeType) { + 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)) { From 6a36da61cf04590d51f301d8e9c34d321efdc6e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Fri, 2 Dec 2022 18:19:56 +0100 Subject: [PATCH 6/6] [ui] GraphEditor: select pipelines imported through the node menu Add all the nodes from a pipeline imported through the node menu to the node selection. This involves changing the return value of ImportProjectCommand from a boolean to a list of imported nodes. --- meshroom/ui/commands.py | 2 +- meshroom/ui/graph.py | 4 ++-- meshroom/ui/qml/GraphEditor/GraphEditor.qml | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/meshroom/ui/commands.py b/meshroom/ui/commands.py index 208826139e..bbfb30631c 100755 --- a/meshroom/ui/commands.py +++ b/meshroom/ui/commands.py @@ -245,7 +245,7 @@ def redoImpl(self): 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 e9769bd4c3..7ace78d095 100644 --- a/meshroom/ui/graph.py +++ b/meshroom/ui/graph.py @@ -353,8 +353,8 @@ def loadGraph(self, filepath, setupProjectFile=True): self.setGraph(g) return status - @Slot(QUrl, result=bool) - @Slot(QUrl, QPoint, result=bool) + @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, diff --git a/meshroom/ui/qml/GraphEditor/GraphEditor.qml b/meshroom/ui/qml/GraphEditor/GraphEditor.qml index 0d801606d1..3262c62258 100755 --- a/meshroom/ui/qml/GraphEditor/GraphEditor.qml +++ b/meshroom/ui/qml/GraphEditor/GraphEditor.qml @@ -234,7 +234,9 @@ Item { { if (MeshroomApp.pipelineTemplateNames.includes(pipeline)) { var url = MeshroomApp.pipelineTemplateFiles[MeshroomApp.pipelineTemplateNames.indexOf(pipeline)]["path"] - uigraph.importProject(Filepath.stringToUrl(url), spawnPosition) + var nodes = uigraph.importProject(Filepath.stringToUrl(url), spawnPosition) + uigraph.selectedNode = nodes[0] + uigraph.selectNodes(nodes) return true } return false