diff --git a/meshroom/ui/components/filepath.py b/meshroom/ui/components/filepath.py index ed450609..cf1308f7 100644 --- a/meshroom/ui/components/filepath.py +++ b/meshroom/ui/components/filepath.py @@ -4,6 +4,7 @@ from PySide2.QtCore import QUrl, QFileInfo from PySide2.QtCore import QObject, Slot import os +import glob class FilepathHelper(QObject): @@ -103,19 +104,44 @@ class FilepathHelper(QObject): def resolve(self, path, vp): # Resolve dynamic path that depends on viewpoint - vpPath = vp.childAttribute("path").value - replacements = { - "": str(vp.childAttribute("viewId").value), - "": str(vp.childAttribute("intrinsicId").value), - "": str(vp.childAttribute("poseId").value), - "": vpPath, - "": FilepathHelper.basename(FilepathHelper, vpPath), - "": FilepathHelper.removeExtension(FilepathHelper, FilepathHelper.basename(FilepathHelper, vpPath)), - "": FilepathHelper.extension(FilepathHelper, vpPath), - } + replacements = {} + if vp == None: + replacements = FilepathHelper.getFilenamesFromFolder(FilepathHelper, FilepathHelper.dirname(FilepathHelper, path), FilepathHelper.extension(FilepathHelper, path)) + resolved = [path for i in range(len(replacements))] + for key in replacements: + for i in range(len(resolved)): + resolved[i] = resolved[i].replace("", replacements[i]) + return resolved + else: + + vpPath = vp.childAttribute("path").value + filename = FilepathHelper.basename(FilepathHelper, vpPath) + replacements = { + "": str(vp.childAttribute("viewId").value), + "": str(vp.childAttribute("intrinsicId").value), + "": str(vp.childAttribute("poseId").value), + "": vpPath, + "": filename, + "": FilepathHelper.removeExtension(FilepathHelper, filename), + "": FilepathHelper.extension(FilepathHelper, filename), + } resolved = path for key in replacements: resolved = resolved.replace(key, replacements[key]) - return resolved \ No newline at end of file + return resolved + + @Slot(str, result="QVariantList") + @Slot(str, str, result="QVariantList") + def getFilenamesFromFolder(self, folderPath: str, extension: str = None): + """ + Get all filenames from a folder with a specific extension. + + :param folderPath: Path to the folder. + :param extension: Extension of the files to get. + :return: List of filenames. + """ + if extension is None: + extension = ".*" + return [self.removeExtension(FilepathHelper, self.basename(FilepathHelper, f)) for f in glob.glob(os.path.join(folderPath, f"*{extension}")) if os.path.isfile(f)] diff --git a/meshroom/ui/qml/Viewer/SequencePlayer.qml b/meshroom/ui/qml/Viewer/SequencePlayer.qml index 9d216340..e9475ec9 100644 --- a/meshroom/ui/qml/Viewer/SequencePlayer.qml +++ b/meshroom/ui/qml/Viewer/SequencePlayer.qml @@ -27,6 +27,7 @@ FloatingPane { readonly property alias syncFeaturesSelected: m.syncFeaturesSelected property bool loading: fetchButton.checked || m.playing property alias settings_SequencePlayer: settings_SequencePlayer + property alias frameId: m.frame Settings { id: settings_SequencePlayer @@ -94,7 +95,7 @@ FloatingPane { id: timer repeat: true - running: m.playing + running: m.playing && root.visible interval: 1000 / m.fps onTriggered: { diff --git a/meshroom/ui/qml/Viewer/Viewer2D.qml b/meshroom/ui/qml/Viewer/Viewer2D.qml index 768a1c34..9f743cd1 100644 --- a/meshroom/ui/qml/Viewer/Viewer2D.qml +++ b/meshroom/ui/qml/Viewer/Viewer2D.qml @@ -30,6 +30,8 @@ FocusScope { property bool enableSequencePlayer: enableSequencePlayerAction.checked readonly property alias sync3DSelected: sequencePlayer.sync3DSelected + property var sequence: [] + property int currentFrame: sequencePlayer.frameId QtObject { id: m @@ -181,7 +183,7 @@ FocusScope { } // node must have at least one output attribute with the image semantic - if (!node.hasImageOutput) { + if (!node.hasImageOutput && !node.hasSequenceOutput) { return false } @@ -223,7 +225,6 @@ FocusScope { return undefined } - function getImageFile() { // Entry point for getting the image file URL @@ -237,6 +238,11 @@ FocusScope { return Filepath.stringToUrl(path) } + if (_reconstruction && displayedNode && displayedNode.hasSequenceOutput) { + var path = sequence[currentFrame] + return Filepath.stringToUrl(path) + } + if (_reconstruction) { let vp = getViewpoint(_reconstruction.pickedViewId) let attr = getAttributeByName(displayedNode, outputAttribute.name) @@ -253,17 +259,24 @@ FocusScope { // ordered by path let objs = [] - for (let i = 0; i < _reconstruction.viewpoints.count; i++) { - objs.push(_reconstruction.viewpoints.at(i)) - } - objs.sort((a, b) => { return a.childAttribute("path").value < b.childAttribute("path").value ? -1 : 1; }) - let seq = []; - for (let i = 0; i < objs.length; i++) { - seq.push(Filepath.resolve(path_template, objs[i])) - } + if (displayedNode && displayedNode.hasSequenceOutput) { + objs = Filepath.resolve(path_template, null) + //order by path + objs.sort() + return objs + } else { + for (let i = 0; i < _reconstruction.viewpoints.count; i++) { + objs.push(_reconstruction.viewpoints.at(i)) + } + objs.sort((a, b) => { return a.childAttribute("path").value < b.childAttribute("path").value ? -1 : 1; }) + let seq = []; + for (let i = 0; i < objs.length; i++) { + seq.push(Filepath.resolve(path_template, objs[i])) + } - return seq + return seq + } } function getSequence() { @@ -300,12 +313,23 @@ FocusScope { if (attr.isOutput && attr.desc.semantic === "image" && attr.enabled) { names.push(attr.name) } + + if (attr.isOutput && attr.desc.semantic === "sequence" && attr.enabled) { + names.push(attr.name) + } } } - if (!displayedNode || displayedNode.isComputable) names.push("gallery") - outputAttribute.names = names - root.source = getImageFile() + if (!displayedNode || displayedNode.isComputable) names.push("gallery") + + outputAttribute.names = names + if (displayedNode && !displayedNode.hasSequenceOutput) { + + root.source = getImageFile() + } else { + root.sequence = getSequence() + enableSequencePlayerAction.checked = true + } } Connections { @@ -457,11 +481,11 @@ FocusScope { 'sfmRequired': Qt.binding(function() { return displayLensDistortionViewer.checked ? true : false }), 'surface.msfmData': Qt.binding(function() { return (msfmDataLoader.status === Loader.Ready && msfmDataLoader.item != null && msfmDataLoader.item.status === 2) ? msfmDataLoader.item : null }), 'canBeHovered': false, - 'idView': Qt.binding(function() { return (_reconstruction ? _reconstruction.selectedViewId : -1) }), + 'idView': Qt.binding(function() { return ((root.displayedNode && !root.displayedNode.hasSequenceOutput && _reconstruction) ? _reconstruction.selectedViewId : -1) }), 'cropFisheye': false, - 'sequence': Qt.binding(function() { return ((root.enableSequencePlayer && _reconstruction && _reconstruction.viewpoints.count > 0) ? getSequence() : []) }), + 'sequence': Qt.binding(function() { return ((root.enableSequencePlayer && (_reconstruction || (root.displayedNode && root.displayedNode.hasSequenceOutput))) ? getSequence() : []) }), 'targetSize': Qt.binding(function() { return floatImageViewerLoader.targetSize }), - 'useSequence': Qt.binding(function() { return root.enableSequencePlayer && !useExternal && _reconstruction }), + 'useSequence': Qt.binding(function() { return (root.enableSequencePlayer && !useExternal && (_reconstruction || (root.displayedNode && root.displayedNode.hasSequenceOutput))) }), 'fetchingSequence': Qt.binding(function() { return sequencePlayer.loading }), 'memoryLimit': Qt.binding(function() { return sequencePlayer.settings_SequencePlayer.maxCacheMemory }), }) @@ -1434,7 +1458,7 @@ FocusScope { id: sequencePlayer anchors.margins: 0 Layout.fillWidth: true - sortedViewIds: (root.enableSequencePlayer && _reconstruction && _reconstruction.viewpoints.count > 0) ? buildOrderedSequence("") : [] + sortedViewIds: { return (root.enableSequencePlayer && (root.displayedNode && root.displayedNode.hasSequenceOutput)) ? root.sequence : (_reconstruction && _reconstruction.viewpoints.count > 0) ? buildOrderedSequence("") : [] } viewer: floatImageViewerLoader.status === Loader.Ready ? floatImageViewerLoader.item : null visible: root.enableSequencePlayer enabled: root.enableSequencePlayer