[ui] drag&drop: common behavior for graph editor and image gallery

In Image Gallery :
- drop 1 .mg open the scene
- drop images either create new camera or augment the reconstruction

In Graph Editor :
- drop 1 .mg open the scene
- drop images create new camera at position of mouse
This commit is contained in:
Aurore LAFAURIE 2024-03-20 17:05:28 +01:00
parent bb9661a141
commit 67fbf1b00f
5 changed files with 88 additions and 19 deletions

View file

@ -29,6 +29,9 @@ Item {
signal computeRequest(var node) signal computeRequest(var node)
signal submitRequest(var node) signal submitRequest(var node)
// Files have been dropped
signal filesDropped(var drop, var mousePosition)
// trigger initial fit() after initialization // trigger initial fit() after initialization
// (ensure GraphEditor has its final size) // (ensure GraphEditor has its final size)
Component.onCompleted: firstFitTimer.start() Component.onCompleted: firstFitTimer.start()
@ -705,6 +708,20 @@ Item {
Item { Item {
id: boxSelectDraggable id: boxSelectDraggable
} }
DropArea {
id: dropArea
anchors.fill: parent
keys: ["text/uri-list"]
onDropped: {
// retrieve mouse position and convert coordinate system
// from pixel values to graph reference system
var mousePosition = mapToItem(draggable, drag.x, drag.y)
// send the list of files,
// to create the corresponding nodes or open another scene
filesDropped(drop, mousePosition)
}
}
} }
// Toolbar // Toolbar

View file

@ -28,6 +28,9 @@ Panel {
property int defaultCellSize: 160 property int defaultCellSize: 160
property bool readOnly: false property bool readOnly: false
property bool isMeshroomScene : false
property int nbFilesDropped: 0
signal removeImageRequest(var attribute) signal removeImageRequest(var attribute)
signal allViewpointsCleared() signal allViewpointsCleared()
signal filesDropped(var drop, var augmentSfm) signal filesDropped(var drop, var augmentSfm)
@ -438,7 +441,16 @@ Panel {
anchors.fill: parent anchors.fill: parent
enabled: !m.readOnly && !intrinsicsFilterButton.checked enabled: !m.readOnly && !intrinsicsFilterButton.checked
keys: ["text/uri-list"] keys: ["text/uri-list"]
// TODO: onEntered: call specific method to filter files based on extension onEntered: {
isMeshroomScene = false
nbFilesDropped = drag.urls.length
if (nbFilesDropped == 1){
var url = drag.urls[0]
if (url.endsWith(".mg")){
isMeshroomScene = true
}
}
}
onDropped: { onDropped: {
var augmentSfm = augmentArea.hovered var augmentSfm = augmentArea.hovered
root.filesDropped(drop, augmentSfm) root.filesDropped(drop, augmentSfm)
@ -463,7 +475,17 @@ Panel {
Layout.fillHeight: true Layout.fillHeight: true
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
text: "Add Images" text: {
if (isMeshroomScene) {
if(nbFilesDropped == 1) {
return "Load Project"
} else {
return "Only one project"
}
}else if (!isMeshroomScene){
return "Add Images"
}
}
font.bold: true font.bold: true
background: Rectangle { background: Rectangle {
color: parent.hovered ? parent.palette.highlight : parent.palette.window color: parent.hovered ? parent.palette.highlight : parent.palette.window
@ -483,7 +505,15 @@ Panel {
text: "Augment Reconstruction" text: "Augment Reconstruction"
font.bold: true font.bold: true
wrapMode: Text.WrapAtWordBoundaryOrAnywhere wrapMode: Text.WrapAtWordBoundaryOrAnywhere
visible: m.viewpoints ? m.viewpoints.count > 0 : false visible: {
if(isMeshroomScene)
return false
if(m.viewpoints){
return m.viewpoints.count > 0
}else{
return false
}
}
background: Rectangle { background: Rectangle {
color: parent.hovered ? palette.highlight : palette.window color: parent.hovered ? palette.highlight : palette.window
opacity: 0.8 opacity: 0.8

View file

@ -80,7 +80,9 @@ Item {
cameraInitIndex: reconstruction ? reconstruction.cameraInitIndex : -1 cameraInitIndex: reconstruction ? reconstruction.cameraInitIndex : -1
onRemoveImageRequest: reconstruction.removeAttribute(attribute) onRemoveImageRequest: reconstruction.removeAttribute(attribute)
onAllViewpointsCleared: { reconstruction.removeAllImages(); reconstruction.selectedViewId = "-1" } onAllViewpointsCleared: { reconstruction.removeAllImages(); reconstruction.selectedViewId = "-1" }
onFilesDropped: reconstruction.handleFilesDrop(drop, augmentSfm ? null : cameraInit) onFilesDropped: {
reconstruction.handleFilesUrl(drop.urls, augmentSfm ? null : cameraInit)
}
} }
LiveSfmView { LiveSfmView {
visible: settings_UILayout.showLiveReconstruction visible: settings_UILayout.showLiveReconstruction

View file

@ -1186,6 +1186,9 @@ ApplicationWindow {
_reconstruction.forceNodesStatusUpdate(); _reconstruction.forceNodesStatusUpdate();
computeManager.submit(node) computeManager.submit(node)
} }
onFilesDropped: {
_reconstruction.handleFilesUrl(drop.urls, null, mousePosition)
}
} }
TaskManager { TaskManager {

View file

@ -6,7 +6,7 @@ from collections.abc import Iterable
from multiprocessing.pool import ThreadPool from multiprocessing.pool import ThreadPool
from threading import Thread from threading import Thread
from PySide2.QtCore import QObject, Slot, Property, Signal, QUrl, QSizeF from PySide2.QtCore import QObject, Slot, Property, Signal, QUrl, QSizeF, QPoint
from PySide2.QtGui import QMatrix4x4, QMatrix3x3, QQuaternion, QVector3D, QVector2D from PySide2.QtGui import QMatrix4x4, QMatrix3x3, QQuaternion, QVector3D, QVector2D
import meshroom.core import meshroom.core
@ -716,14 +716,24 @@ class Reconstruction(UIGraph):
""" Get all view Ids involved in the reconstruction. """ """ Get all view Ids involved in the reconstruction. """
return [vp.viewId.value for node in self._cameraInits for vp in node.viewpoints.value] return [vp.viewId.value for node in self._cameraInits for vp in node.viewpoints.value]
@Slot(QObject, Node) @Slot('QList<QUrl>')
def handleFilesDrop(self, drop, cameraInit): @Slot('QList<QUrl>', Node)
@Slot('QList<QUrl>', Node, 'QPoint')
def handleFilesUrl(self, urls, cameraInit=None, position=None):
""" Handle drop events aiming to add images to the Reconstruction. """ Handle drop events aiming to add images to the Reconstruction.
Fetching urls from dropEvent is generally expensive in QML/JS (bug ?).
This method allows to reduce process time by doing it on Python side. This method allows to reduce process time by doing it on Python side.
""" """
filesByType = self.getFilesByTypeFromDrop(drop) filesByType = self.getFilesByTypeFromDrop(urls)
if filesByType.images: if filesByType.images:
if cameraInit is None:
boundingBox = self.layout.boundingBox()
if not position:
p = Position(boundingBox[0], boundingBox[1] + boundingBox[3])
elif isinstance(position, QPoint):
p = Position(position.x(), position.y())
else:
p = position
cameraInit = self.addNewNode("CameraInit", position=p)
self._workerThreads.apply_async(func=self.importImagesSync, args=(filesByType.images, cameraInit,)) self._workerThreads.apply_async(func=self.importImagesSync, args=(filesByType.images, cameraInit,))
if filesByType.videos: if filesByType.videos:
boundingBox = self.layout.boundingBox() boundingBox = self.layout.boundingBox()
@ -773,26 +783,33 @@ class Reconstruction(UIGraph):
if not filesByType.images and not filesByType.videos and not filesByType.panoramaInfo: if not filesByType.images and not filesByType.videos and not filesByType.panoramaInfo:
if filesByType.other: if filesByType.other:
extensions = set([os.path.splitext(url)[1] for url in filesByType.other]) singleMgFile = False
self.error.emit( if len(filesByType.other) == 1:
Message( url = filesByType.other[0]
"No Recognized Input File", ext = os.path.splitext(url)[1]
"No recognized input file in the {} dropped files".format(len(filesByType.other)), if ext == '.mg':
"Unknown file extensions: " + ', '.join(extensions) self.loadUrl(url)
singleMgFile = True
if not singleMgFile:
extensions = set([os.path.splitext(url)[1] for url in filesByType.other])
self.error.emit(
Message(
"No Recognized Input File",
"No recognized input file in the {} dropped files".format(len(filesByType.other)),
"Unknown file extensions: " + ', '.join(extensions)
)
) )
)
@staticmethod @staticmethod
def getFilesByTypeFromDrop(drop): def getFilesByTypeFromDrop(urls):
""" """
Args: Args:
drop: urls: list of filepaths
Returns: Returns:
<images, otherFiles> List of recognized images and list of other files <images, otherFiles> List of recognized images and list of other files
""" """
urls = drop.property("urls")
# Build the list of images paths # Build the list of images paths
filesByType = multiview.FilesByType() filesByType = multiview.FilesByType()
for url in urls: for url in urls: