mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-06-01 18:31:58 +02:00
Merge pull request #2449 from alicevision/dev/accessCurrentFrame
Provide access to the path of the currently displayed frame
This commit is contained in:
commit
ccf76ab3b5
6 changed files with 72 additions and 47 deletions
|
@ -1,3 +1,5 @@
|
|||
# [Viewer] Clean-up: Harmonize syntax for the Viewer2D
|
||||
9af65092b9e881c828430f54a73fb4522bc1e370
|
||||
# [nodes] Harmonize the use of trailing commas across all the nodes
|
||||
61a8dcd4e2878f80b2f320f2b1c3c9b41e999b82
|
||||
# [nodes] Clean-up: Harmonize nodes' descriptions
|
||||
|
|
|
@ -226,8 +226,6 @@ class Graph(BaseObject):
|
|||
self._fileDateVersion = 0
|
||||
self.header = {}
|
||||
|
||||
self._selectedViewpoint = None
|
||||
|
||||
def clear(self):
|
||||
self.header.clear()
|
||||
self._compatibilityNodes.clear()
|
||||
|
@ -1566,12 +1564,6 @@ class Graph(BaseObject):
|
|||
self.updateStatusFromCache(force=True)
|
||||
self.cacheDirChanged.emit()
|
||||
|
||||
@property
|
||||
def selectedViewpoint(self):
|
||||
""" Return the attribute describing the viewpoint that is
|
||||
currently set as the 'selected viewpoint'. """
|
||||
return self._selectedViewpoint
|
||||
|
||||
@property
|
||||
def fileDateVersion(self):
|
||||
return self._fileDateVersion
|
||||
|
|
|
@ -6,6 +6,8 @@ from meshroom.common import Backend
|
|||
meshroom.setupEnvironment(backend=Backend.PYSIDE)
|
||||
|
||||
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
||||
from meshroom.ui.app import MeshroomApp
|
||||
app = MeshroomApp(sys.argv)
|
||||
app.exec_()
|
||||
import meshroom.ui
|
||||
import meshroom.ui.app
|
||||
|
||||
meshroom.ui.uiInstance = meshroom.ui.app.MeshroomApp(sys.argv)
|
||||
meshroom.ui.uiInstance.exec_()
|
||||
|
|
|
@ -4,13 +4,14 @@ import re
|
|||
import argparse
|
||||
|
||||
from PySide2 import QtCore
|
||||
from PySide2.QtCore import Qt, QUrl, Slot, QJsonValue, Property, Signal, qInstallMessageHandler, QtMsgType, QSettings
|
||||
from PySide2.QtCore import Qt, QUrl, QJsonValue, qInstallMessageHandler, QtMsgType, QSettings
|
||||
from PySide2.QtGui import QIcon
|
||||
from PySide2.QtWidgets import QApplication
|
||||
|
||||
import meshroom
|
||||
from meshroom.core import nodesDesc
|
||||
from meshroom.core.taskManager import TaskManager
|
||||
from meshroom.common import Property, Variant, Signal, Slot
|
||||
|
||||
from meshroom.ui import components
|
||||
from meshroom.ui.components.clipboard import ClipboardHelper
|
||||
|
@ -143,9 +144,9 @@ class MeshroomApp(QApplication):
|
|||
# instantiate Reconstruction object
|
||||
self._undoStack = commands.UndoStack(self)
|
||||
self._taskManager = TaskManager(self)
|
||||
r = Reconstruction(undoStack=self._undoStack, taskManager=self._taskManager, defaultPipeline=args.pipeline, parent=self)
|
||||
r.setSubmitLabel(args.submitLabel)
|
||||
self.engine.rootContext().setContextProperty("_reconstruction", r)
|
||||
self._activeProject = Reconstruction(undoStack=self._undoStack, taskManager=self._taskManager, defaultPipeline=args.pipeline, parent=self)
|
||||
self._activeProject.setSubmitLabel(args.submitLabel)
|
||||
self.engine.rootContext().setContextProperty("_reconstruction", self._activeProject)
|
||||
|
||||
# those helpers should be available from QML Utils module as singletons, but:
|
||||
# - qmlRegisterUncreatableType is not yet available in PySide2
|
||||
|
@ -162,7 +163,7 @@ class MeshroomApp(QApplication):
|
|||
self.engine.rootContext().setContextProperty("MeshroomApp", self)
|
||||
|
||||
# request any potential computation to stop on exit
|
||||
self.aboutToQuit.connect(r.stopChildThreads)
|
||||
self.aboutToQuit.connect(self._activeProject.stopChildThreads)
|
||||
|
||||
if args.project and not os.path.isfile(args.project):
|
||||
raise RuntimeError(
|
||||
|
@ -171,17 +172,17 @@ class MeshroomApp(QApplication):
|
|||
|
||||
if args.project:
|
||||
args.project = os.path.abspath(args.project)
|
||||
r.load(args.project)
|
||||
self._activeProject.load(args.project)
|
||||
self.addRecentProjectFile(args.project)
|
||||
else:
|
||||
r.new()
|
||||
self._activeProject.new()
|
||||
|
||||
# import is a python keyword, so we have to access the attribute by a string
|
||||
if getattr(args, "import", None):
|
||||
r.importImagesFromFolder(getattr(args, "import"), recursive=False)
|
||||
self._activeProject.importImagesFromFolder(getattr(args, "import"), recursive=False)
|
||||
|
||||
if args.importRecursive:
|
||||
r.importImagesFromFolder(args.importRecursive, recursive=True)
|
||||
self._activeProject.importImagesFromFolder(args.importRecursive, recursive=True)
|
||||
|
||||
if args.save:
|
||||
if os.path.isfile(args.save):
|
||||
|
@ -195,7 +196,7 @@ class MeshroomApp(QApplication):
|
|||
"Meshroom Command Line Error: Cannot save the new Meshroom project file (.mg) as the parent of the folder does not exists.\n"
|
||||
"Invalid value: '{}'".format(args.save))
|
||||
os.mkdir(projectFolder)
|
||||
r.saveAs(args.save)
|
||||
self._activeProject.saveAs(args.save)
|
||||
self.addRecentProjectFile(args.save)
|
||||
|
||||
self.engine.load(os.path.normpath(url))
|
||||
|
@ -459,7 +460,8 @@ class MeshroomApp(QApplication):
|
|||
def _default8bitViewerEnabled(self):
|
||||
return bool(os.environ.get("MESHROOM_USE_8BIT_VIEWER", False))
|
||||
|
||||
|
||||
activeProjectChanged = Signal()
|
||||
activeProject = Property(Variant, lambda self: self._activeProject, notify=activeProjectChanged)
|
||||
changelogModel = Property("QVariantList", _changelogModel, constant=True)
|
||||
licensesModel = Property("QVariantList", _licensesModel, constant=True)
|
||||
pipelineTemplateFilesChanged = Signal()
|
||||
|
|
|
@ -137,8 +137,8 @@ FocusScope {
|
|||
if (Math.min(imgContainer.width, imgContainer.image.height) * imgContainer.scale * zoomFactor < 10)
|
||||
return
|
||||
var point = mapToItem(imgContainer, wheel.x, wheel.y)
|
||||
imgContainer.x += (1-zoomFactor) * point.x * imgContainer.scale
|
||||
imgContainer.y += (1-zoomFactor) * point.y * imgContainer.scale
|
||||
imgContainer.x += (1 - zoomFactor) * point.x * imgContainer.scale
|
||||
imgContainer.y += (1 - zoomFactor) * point.y * imgContainer.scale
|
||||
imgContainer.scale *= zoomFactor
|
||||
}
|
||||
}
|
||||
|
@ -224,12 +224,15 @@ FocusScope {
|
|||
// Entry point for getting the image file from the gallery
|
||||
let vp = getViewpoint(_reconstruction.pickedViewId)
|
||||
let path = vp ? vp.childAttribute("path").value : ""
|
||||
_reconstruction.currentViewPath = path
|
||||
return Filepath.stringToUrl(path)
|
||||
}
|
||||
|
||||
if (_reconstruction && displayedNode && displayedNode.hasSequenceOutput && displayedAttr && (displayedAttr.desc.semantic === "imageList" || displayedAttr.desc.semantic === "sequence")) {
|
||||
if (_reconstruction && displayedNode && displayedNode.hasSequenceOutput && displayedAttr &&
|
||||
(displayedAttr.desc.semantic === "imageList" || displayedAttr.desc.semantic === "sequence")) {
|
||||
// Entry point for getting the image file from a sequence defined by an output attribute
|
||||
var path = sequence[currentFrame-frameRange.min]
|
||||
var path = sequence[currentFrame - frameRange.min]
|
||||
_reconstruction.currentViewPath = path
|
||||
return Filepath.stringToUrl(path)
|
||||
}
|
||||
|
||||
|
@ -238,20 +241,22 @@ FocusScope {
|
|||
let vp = getViewpoint(_reconstruction.pickedViewId)
|
||||
let path = displayedAttr ? displayedAttr.value : ""
|
||||
let resolved = vp ? Filepath.resolve(path, vp) : path
|
||||
_reconstruction.currentViewPath = resolved
|
||||
return Filepath.stringToUrl(resolved)
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
function buildOrderedSequence(path_template) {
|
||||
function buildOrderedSequence(pathTemplate) {
|
||||
// Resolve the path template on the sequence of viewpoints
|
||||
// ordered by path
|
||||
|
||||
let objs = []
|
||||
|
||||
if (displayedNode && displayedNode.hasSequenceOutput && displayedAttr && (displayedAttr.desc.semantic === "imageList" || displayedAttr.desc.semantic === "sequence")) {
|
||||
let sequence = Filepath.resolveSequence(path_template)
|
||||
if (displayedNode && displayedNode.hasSequenceOutput && displayedAttr &&
|
||||
(displayedAttr.desc.semantic === "imageList" || displayedAttr.desc.semantic === "sequence")) {
|
||||
let sequence = Filepath.resolveSequence(pathTemplate)
|
||||
let ids = sequence[0]
|
||||
let resolved = sequence[1]
|
||||
|
||||
|
@ -260,7 +265,7 @@ FocusScope {
|
|||
// concat in one array all sequences in resolved
|
||||
resolved = [].concat.apply([], resolved)
|
||||
frameRange.min = 0
|
||||
frameRange.max = resolved.length-1
|
||||
frameRange.max = resolved.length - 1
|
||||
currentFrame = 0
|
||||
}
|
||||
|
||||
|
@ -270,7 +275,7 @@ FocusScope {
|
|||
resolved = resolved[0]
|
||||
ids = ids[0]
|
||||
frameRange.min = ids[0]
|
||||
frameRange.max = ids[ids.length-1]
|
||||
frameRange.max = ids[ids.length - 1]
|
||||
currentFrame = frameRange.min
|
||||
}
|
||||
|
||||
|
@ -282,11 +287,11 @@ FocusScope {
|
|||
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]))
|
||||
seq.push(Filepath.resolve(pathTemplate, objs[i]))
|
||||
}
|
||||
|
||||
frameRange.min = 0
|
||||
frameRange.max = seq.length-1
|
||||
frameRange.max = seq.length - 1
|
||||
currentFrame = 0
|
||||
|
||||
return seq
|
||||
|
@ -322,13 +327,15 @@ FocusScope {
|
|||
// store attr name for output attributes that represent images
|
||||
for (var i = 0; i < displayedNode.attributes.count; i++) {
|
||||
var attr = displayedNode.attributes.at(i)
|
||||
if (attr.isOutput && (attr.desc.semantic === "image" || attr.desc.semantic === "sequence" || attr.desc.semantic === "imageList") && attr.enabled) {
|
||||
if (attr.isOutput && (attr.desc.semantic === "image" || attr.desc.semantic === "sequence" ||
|
||||
attr.desc.semantic === "imageList") && attr.enabled) {
|
||||
names.push(attr.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!displayedNode || displayedNode.isComputable) names.push("gallery")
|
||||
if (!displayedNode || displayedNode.isComputable)
|
||||
names.push("gallery")
|
||||
|
||||
outputAttribute.names = names
|
||||
}
|
||||
|
@ -399,7 +406,8 @@ FocusScope {
|
|||
if (floatImageViewerLoader.item.containsMouse === false) {
|
||||
return null
|
||||
}
|
||||
var pix = floatImageViewerLoader.item.pixelValueAt(Math.floor(floatImageViewerLoader.item.mouseX), Math.floor(floatImageViewerLoader.item.mouseY))
|
||||
var pix = floatImageViewerLoader.item.pixelValueAt(Math.floor(floatImageViewerLoader.item.mouseX),
|
||||
Math.floor(floatImageViewerLoader.item.mouseY))
|
||||
return pix
|
||||
}
|
||||
}
|
||||
|
@ -444,7 +452,8 @@ FocusScope {
|
|||
// qtAliceVision Image Viewer
|
||||
ExifOrientedViewer {
|
||||
id: floatImageViewerLoader
|
||||
active: root.aliceVisionPluginAvailable && (root.useFloatImageViewer || root.useLensDistortionViewer) && !panoramaViewerLoader.active
|
||||
active: root.aliceVisionPluginAvailable &&
|
||||
(root.useFloatImageViewer || root.useLensDistortionViewer) && !panoramaViewerLoader.active
|
||||
visible: (floatImageViewerLoader.status === Loader.Ready) && active
|
||||
anchors.centerIn: parent
|
||||
orientationTag: imgContainer.orientationTag
|
||||
|
@ -467,7 +476,8 @@ FocusScope {
|
|||
* opened, for example, and the images have a different size), then another auto-fit needs to be
|
||||
* performed */
|
||||
if ((!fittedOnce && imgContainer.image && imgContainer.image.height > 0) ||
|
||||
(fittedOnce && ((width > 1 && previousWidth != width) || (height > 1 && previousHeight != height)))) {
|
||||
(fittedOnce && ((width > 1 && previousWidth != width) ||
|
||||
(height > 1 && previousHeight != height)))) {
|
||||
fit()
|
||||
fittedOnce = true
|
||||
previousWidth = width
|
||||
|
@ -515,7 +525,8 @@ FocusScope {
|
|||
// qtAliceVision Panorama Viewer
|
||||
Loader {
|
||||
id: panoramaViewerLoader
|
||||
active: root.aliceVisionPluginAvailable && root.usePanoramaViewer && _reconstruction.activeNodes.get('sfm').node
|
||||
active: root.aliceVisionPluginAvailable && root.usePanoramaViewer &&
|
||||
_reconstruction.activeNodes.get('sfm').node
|
||||
visible: (panoramaViewerLoader.status === Loader.Ready) && active
|
||||
anchors.centerIn: parent
|
||||
|
||||
|
@ -689,7 +700,7 @@ FocusScope {
|
|||
|
||||
onJsonFolderChanged: {
|
||||
json = null
|
||||
if(activeNode.attribute("autoDetect").value) {
|
||||
if (activeNode.attribute("autoDetect").value) {
|
||||
// auto detection enabled
|
||||
var jsonPath = activeNode.attribute("output").value + "/detection.json"
|
||||
Request.get(Filepath.stringToUrl(jsonPath), function(xhr) {
|
||||
|
@ -711,12 +722,12 @@ FocusScope {
|
|||
onNodeCircleRadiusChanged : { updateGizmo() }
|
||||
|
||||
function updateGizmo() {
|
||||
if(activeNode.attribute("autoDetect").value) {
|
||||
if (activeNode.attribute("autoDetect").value) {
|
||||
// update gizmo from auto detection json file
|
||||
if(json) {
|
||||
if (json) {
|
||||
// json file found
|
||||
var data = json[currentViewId]
|
||||
if(data && data[0]) {
|
||||
if (data && data[0]) {
|
||||
// current view id found
|
||||
circleX = data[0].x
|
||||
circleY= data[0].y
|
||||
|
@ -738,11 +749,13 @@ FocusScope {
|
|||
}
|
||||
|
||||
onMoved: {
|
||||
_reconstruction.setAttribute(activeNode.attribute("sphereCenter"), JSON.stringify([xoffset, yoffset]))
|
||||
_reconstruction.setAttribute(activeNode.attribute("sphereCenter"),
|
||||
JSON.stringify([xoffset, yoffset]))
|
||||
}
|
||||
|
||||
onIncrementRadius: {
|
||||
_reconstruction.setAttribute(activeNode.attribute("sphereRadius"), activeNode.attribute("sphereRadius").value + radiusOffset)
|
||||
_reconstruction.setAttribute(activeNode.attribute("sphereRadius"),
|
||||
activeNode.attribute("sphereRadius").value + radiusOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1087,7 +1100,8 @@ FocusScope {
|
|||
left: parent.left
|
||||
margins: 2
|
||||
}
|
||||
active: root.aliceVisionPluginAvailable && displayFeatures.checked && featuresViewerLoader.status === Loader.Ready
|
||||
active: root.aliceVisionPluginAvailable && displayFeatures.checked &&
|
||||
featuresViewerLoader.status === Loader.Ready
|
||||
|
||||
sourceComponent: FeaturesInfoOverlay {
|
||||
pluginStatus: featuresViewerLoader.status
|
||||
|
|
|
@ -464,6 +464,8 @@ class Reconstruction(UIGraph):
|
|||
self._pickedViewId = None
|
||||
self._liveSfmManager = LiveSfmManager(self)
|
||||
|
||||
self._currentViewPath = ""
|
||||
|
||||
self._workerThreads = ThreadPool(processes=1)
|
||||
|
||||
# react to internal graph changes to update those variables
|
||||
|
@ -1158,7 +1160,6 @@ class Reconstruction(UIGraph):
|
|||
# Reconstruction has ownership of Viewpoint object - destroy it when not needed anymore
|
||||
self._selectedViewpoint.deleteLater()
|
||||
self._selectedViewpoint = ViewpointWrapper(viewpointAttribute, self) if viewpointAttribute else None
|
||||
self._graph._selectedViewpoint = self._selectedViewpoint.attribute if viewpointAttribute else None
|
||||
self.selectedViewpointChanged.emit()
|
||||
|
||||
def setPickedViewId(self, viewId):
|
||||
|
@ -1217,6 +1218,12 @@ class Reconstruction(UIGraph):
|
|||
|
||||
return R, T
|
||||
|
||||
def setCurrentViewPath(self, path):
|
||||
if self._currentViewPath == path:
|
||||
return
|
||||
self._currentViewPath = path
|
||||
self.currentViewPathChanged.emit()
|
||||
|
||||
selectedViewIdChanged = Signal()
|
||||
selectedViewId = Property(str, lambda self: self._selectedViewId, setSelectedViewId, notify=selectedViewIdChanged)
|
||||
selectedViewpointChanged = Signal()
|
||||
|
@ -1234,6 +1241,12 @@ class Reconstruction(UIGraph):
|
|||
|
||||
nbCameras = Property(int, reconstructedCamerasCount, notify=sfmReportChanged)
|
||||
|
||||
# Provides the path of the image that is currently displayed
|
||||
# This is an alternative to "selectedViewpoint.attribute.path.value" for images that are displayed
|
||||
# but not part of the list of viewpoints of a CameraInit node (i.e. "sequence" node outputs)
|
||||
currentViewPathChanged = Signal()
|
||||
currentViewPath = Property(str, lambda self: self._currentViewPath, setCurrentViewPath, notify=currentViewPathChanged)
|
||||
|
||||
# Signals to propagate high-level messages
|
||||
error = Signal(Message)
|
||||
warning = Signal(Message)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue