diff --git a/meshroom/core/graph.py b/meshroom/core/graph.py index 1b176e8a..9f730087 100644 --- a/meshroom/core/graph.py +++ b/meshroom/core/graph.py @@ -280,6 +280,8 @@ class Graph(BaseObject): # Create graph edges by resolving attributes expressions self._applyExpr() + return True + @property def updateEnabled(self): return self._updateEnabled diff --git a/meshroom/ui/app.py b/meshroom/ui/app.py index fba3425a..5abe029c 100644 --- a/meshroom/ui/app.py +++ b/meshroom/ui/app.py @@ -8,6 +8,8 @@ from PySide2.QtWidgets import QApplication import meshroom from meshroom.core import nodesDesc +from meshroom.core import pyCompatibility + from meshroom.ui import components from meshroom.ui.components.clipboard import ClipboardHelper from meshroom.ui.components.filepath import FilepathHelper @@ -183,8 +185,19 @@ class MeshroomApp(QApplication): return projects @Slot(str) + @Slot(QUrl) def addRecentProjectFile(self, projectFile): - projectFile = QUrl(projectFile).path() + if not isinstance(projectFile, (QUrl, pyCompatibility.basestring)): + raise TypeError("Unexpected data type: {}".format(projectFile.__class__)) + if isinstance(projectFile, QUrl): + projectFileNorm = projectFile.toLocalFile() + if not projectFileNorm: + projectFileNorm = projectFile.toString() + else: + projectFileNorm = QUrl(projectFile).toLocalFile() + if not projectFileNorm: + projectFileNorm = QUrl.fromLocalFile(projectFile).toLocalFile() + projects = self._recentProjectFiles() # remove duplicates while preserving order @@ -192,10 +205,10 @@ class MeshroomApp(QApplication): uniqueProjects = OrderedDict.fromkeys(projects) projects = list(uniqueProjects) # remove previous usage of the value - if projectFile in uniqueProjects: - projects.remove(projectFile) + if projectFileNorm in uniqueProjects: + projects.remove(projectFileNorm) # add the new value in the first place - projects.insert(0, projectFile) + projects.insert(0, projectFileNorm) # keep only the 10 first elements projects = projects[0:20] @@ -211,6 +224,43 @@ class MeshroomApp(QApplication): self.recentProjectFilesChanged.emit() + @Slot(str) + @Slot(QUrl) + def removeRecentProjectFile(self, projectFile): + if not isinstance(projectFile, (QUrl, pyCompatibility.basestring)): + raise TypeError("Unexpected data type: {}".format(projectFile.__class__)) + if isinstance(projectFile, QUrl): + projectFileNorm = projectFile.toLocalFile() + if not projectFileNorm: + projectFileNorm = projectFile.toString() + else: + projectFileNorm = QUrl(projectFile).toLocalFile() + if not projectFileNorm: + projectFileNorm = QUrl.fromLocalFile(projectFile).toLocalFile() + + projects = self._recentProjectFiles() + + # remove duplicates while preserving order + from collections import OrderedDict + uniqueProjects = OrderedDict.fromkeys(projects) + projects = list(uniqueProjects) + # remove previous usage of the value + if projectFileNorm not in uniqueProjects: + return + + projects.remove(projectFileNorm) + + settings = QSettings() + settings.beginGroup("RecentFiles") + size = settings.beginWriteArray("Projects") + for i, p in enumerate(projects): + settings.setArrayIndex(i) + settings.setValue("filepath", p) + settings.endArray() + settings.sync() + + self.recentProjectFilesChanged.emit() + @Slot(str, result=str) def markdownToHtml(self, md): """ diff --git a/meshroom/ui/graph.py b/meshroom/ui/graph.py index 566c122c..f6fb501d 100644 --- a/meshroom/ui/graph.py +++ b/meshroom/ui/graph.py @@ -309,16 +309,14 @@ class UIGraph(QObject): self.stopExecution() self._chunksMonitor.stop() - def load(self, filepath, setupProjectFile=True): + @Slot(str, result=bool) + def loadGraph(self, filepath, setupProjectFile=True): g = Graph('') - g.load(filepath, setupProjectFile) + status = g.load(filepath, setupProjectFile) if not os.path.exists(g.cacheDir): os.mkdir(g.cacheDir) self.setGraph(g) - - @Slot(QUrl) - def loadUrl(self, url): - self.load(url.toLocalFile()) + return status @Slot(QUrl) def saveAs(self, url): diff --git a/meshroom/ui/qml/Viewer/FeaturesInfoOverlay.qml b/meshroom/ui/qml/Viewer/FeaturesInfoOverlay.qml index 380dd21e..909b61be 100644 --- a/meshroom/ui/qml/Viewer/FeaturesInfoOverlay.qml +++ b/meshroom/ui/qml/Viewer/FeaturesInfoOverlay.qml @@ -75,7 +75,7 @@ FloatingPane { property var viewer: root.featuresViewer.itemAt(index) spacing: 4 - // Visibility toogle + // Visibility toggle MaterialToolButton { text: featureType.viewer.visible ? MaterialIcons.visibility : MaterialIcons.visibility_off onClicked: featureType.viewer.visible = !featureType.viewer.visible diff --git a/meshroom/ui/qml/main.qml b/meshroom/ui/qml/main.qml index 75b8cbeb..b8e49028 100755 --- a/meshroom/ui/qml/main.qml +++ b/meshroom/ui/qml/main.qml @@ -207,8 +207,10 @@ ApplicationWindow { title: "Open File" nameFilters: ["Meshroom Graphs (*.mg)"] onAccepted: { - _reconstruction.loadUrl(file.toString()) - MeshroomApp.addRecentProjectFile(file.toString()) + if(_reconstruction.loadUrl(file)) + { + MeshroomApp.addRecentProjectFile(file.toString()) + } } } @@ -353,8 +355,14 @@ ApplicationWindow { MenuItem { onTriggered: ensureSaved(function() { openRecentMenu.dismiss(); - _reconstruction.load(modelData); - MeshroomApp.addRecentProjectFile(modelData); + if(_reconstruction.loadUrl(modelData)) + { + MeshroomApp.addRecentProjectFile(modelData); + } + else + { + MeshroomApp.removeRecentProjectFile(modelData); + } }) text: fileTextMetrics.elidedText diff --git a/meshroom/ui/reconstruction.py b/meshroom/ui/reconstruction.py index 0dcf0bd1..f8fe909a 100755 --- a/meshroom/ui/reconstruction.py +++ b/meshroom/ui/reconstruction.py @@ -430,10 +430,10 @@ class Reconstruction(UIGraph): # use the user-provided default photogrammetry project file self.load(p, setupProjectFile=False) - @Slot(str) + @Slot(str, result=bool) def load(self, filepath, setupProjectFile=True): try: - super(Reconstruction, self).load(filepath, setupProjectFile) + status = super(Reconstruction, self).loadGraph(filepath, setupProjectFile) # warn about pre-release projects being automatically upgraded if Version(self._graph.fileReleaseVersion).major == "0": self.warning.emit(Message( @@ -442,17 +442,41 @@ class Reconstruction(UIGraph): "Data might have been lost in the process.", "Open it with the corresponding version of Meshroom to recover your data." )) + return status + except FileNotFoundError as e: + self.error.emit( + Message( + "No Such File", + "Error While Loading '{}': No Such File.".format(os.path.basename(filepath)), + "" + ) + ) + logging.error("Error while loading '{}': No Such File.".format(os.path.basename(filepath))) + return False except Exception as e: import traceback trace = traceback.format_exc() self.error.emit( Message( - "Error while loading {}".format(os.path.basename(filepath)), - "An unexpected error has occurred", + "Error While Loading Project File", + "An unexpected error has occurred while loading file: '{}'".format(os.path.basename(filepath)), trace ) ) logging.error(trace) + return False + + @Slot(QUrl, result=bool) + def loadUrl(self, url): + if isinstance(url, (QUrl)): + # depending how the QUrl has been initialized, + # toLocalFile() may return the local path or an empty string + localFile = url.toLocalFile() + if not localFile: + localFile = url.toString() + else: + localFile = url + return self.load(localFile) def onGraphChanged(self): """ React to the change of the internal graph. """