mirror of
https://github.com/alicevision/Meshroom.git
synced 2025-04-29 18:27:23 +02:00
[ui] handle multiple CameraInit nodes in a Reconstruction
* [ui] ImageGallery: add control to navigate image groups * [graph] expose Node.attribute as a Slot
This commit is contained in:
parent
1f0ed1f2c9
commit
273cfd9f0e
5 changed files with 220 additions and 122 deletions
|
@ -1248,6 +1248,7 @@ class Graph(BaseObject):
|
||||||
def node(self, nodeName):
|
def node(self, nodeName):
|
||||||
return self._nodes.get(nodeName)
|
return self._nodes.get(nodeName)
|
||||||
|
|
||||||
|
@Slot(str, result=Attribute)
|
||||||
def attribute(self, fullName):
|
def attribute(self, fullName):
|
||||||
# type: (str) -> Attribute
|
# type: (str) -> Attribute
|
||||||
"""
|
"""
|
||||||
|
|
91
meshroom/ui/qml/ImageDelegate.qml
Normal file
91
meshroom/ui/qml/ImageDelegate.qml
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
import QtQuick 2.9
|
||||||
|
import QtQuick.Controls 2.3
|
||||||
|
import QtQuick.Layouts 1.3
|
||||||
|
import "filepath.js" as Filepath
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ImageDelegate for a Viewpoint object.
|
||||||
|
*/
|
||||||
|
Item {
|
||||||
|
id: imageDelegate
|
||||||
|
|
||||||
|
property variant viewpoint
|
||||||
|
property bool isCurrentItem: false
|
||||||
|
property alias source: _viewpoint.source
|
||||||
|
property alias metadata: _viewpoint.metadata
|
||||||
|
|
||||||
|
signal pressed(var mouse)
|
||||||
|
|
||||||
|
// retrieve viewpoints inner data
|
||||||
|
QtObject {
|
||||||
|
id: _viewpoint
|
||||||
|
property string source: viewpoint ? viewpoint.get("path").value : ''
|
||||||
|
property string metadataStr: viewpoint ? viewpoint.get("metadata").value : ''
|
||||||
|
property var metadata: metadataStr ? JSON.parse(viewpoint.get("metadata").value) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: imageMA
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 6
|
||||||
|
hoverEnabled: true
|
||||||
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
|
onPressed: {
|
||||||
|
if (mouse.button == Qt.RightButton)
|
||||||
|
imageMenu.popup()
|
||||||
|
imageDelegate.pressed(mouse)
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: imageMenu
|
||||||
|
MenuItem {
|
||||||
|
text: "Show Containing Folder"
|
||||||
|
onClicked: {
|
||||||
|
Qt.openUrlExternally(Filepath.dirname(imageDelegate.source))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Remove"
|
||||||
|
enabled: !root.readOnly
|
||||||
|
onClicked: removeImageRequest(viewpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
// Image thumbnail and background
|
||||||
|
Rectangle {
|
||||||
|
id: imageBackground
|
||||||
|
color: Qt.darker(palette.base, 1.15)
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.fillWidth: true
|
||||||
|
border.color: isCurrentItem ? palette.highlight : Qt.darker(palette.highlight)
|
||||||
|
border.width: imageMA.containsMouse || imageDelegate.isCurrentItem ? 2 : 0
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 4
|
||||||
|
source: imageDelegate.source
|
||||||
|
sourceSize: Qt.size(100, 100)
|
||||||
|
asynchronous: true
|
||||||
|
autoTransform: true
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Image basename
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
padding: 2
|
||||||
|
font.pointSize: 8
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
text: Filepath.basename(imageDelegate.source)
|
||||||
|
background: Rectangle {
|
||||||
|
color: imageDelegate.isCurrentItem ? palette.highlight : "transparent"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,17 +2,18 @@ import QtQuick 2.7
|
||||||
import QtQuick.Controls 2.3
|
import QtQuick.Controls 2.3
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.3
|
||||||
import MaterialIcons 2.2
|
import MaterialIcons 2.2
|
||||||
import "filepath.js" as Filepath
|
|
||||||
import QtQml.Models 2.2
|
import QtQml.Models 2.2
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ImageGallery displays as a grid of Images a model containing Viewpoints objects.
|
* ImageGallery displays as a grid of Images a model containing Viewpoints objects.
|
||||||
|
* It manages a model of multiple CameraInit nodes as individual groups.
|
||||||
*/
|
*/
|
||||||
Panel {
|
Panel {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property alias model: grid.model
|
property variant cameraInits
|
||||||
|
property variant cameraInit
|
||||||
readonly property string currentItemSource: grid.currentItem ? grid.currentItem.source : ""
|
readonly property string currentItemSource: grid.currentItem ? grid.currentItem.source : ""
|
||||||
readonly property var currentItemMetadata: grid.currentItem ? grid.currentItem.metadata : undefined
|
readonly property var currentItemMetadata: grid.currentItem ? grid.currentItem.metadata : undefined
|
||||||
signal removeImageRequest(var attribute)
|
signal removeImageRequest(var attribute)
|
||||||
|
@ -21,9 +22,13 @@ Panel {
|
||||||
implicitWidth: 100
|
implicitWidth: 100
|
||||||
implicitHeight: 300
|
implicitHeight: 300
|
||||||
title: "Images"
|
title: "Images"
|
||||||
|
property int currentIndex: 0
|
||||||
|
readonly property variant viewpoints: cameraInit.attribute('viewpoints').value
|
||||||
|
signal filesDropped(var drop)
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
GridView {
|
GridView {
|
||||||
id: grid
|
id: grid
|
||||||
|
@ -31,102 +36,46 @@ Panel {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
cellWidth: thumbnailSizeSlider.value
|
|
||||||
cellHeight: cellWidth
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
ScrollBar.vertical: ScrollBar {}
|
||||||
keyNavigationEnabled: true
|
|
||||||
highlightFollowsCurrentItem: true
|
|
||||||
focus: true
|
focus: true
|
||||||
clip: true
|
clip: true
|
||||||
|
cellWidth: thumbnailSizeSlider.value
|
||||||
|
cellHeight: cellWidth
|
||||||
|
highlightFollowsCurrentItem: true
|
||||||
|
keyNavigationEnabled: true
|
||||||
|
model: root.viewpoints
|
||||||
|
|
||||||
|
// Keyboard shortcut to change current image group
|
||||||
delegate: Item {
|
Keys.priority: Keys.BeforeItem
|
||||||
id: imageDelegate
|
Keys.onPressed: {
|
||||||
|
if(event.modifiers & Qt.AltModifier)
|
||||||
readonly property bool isCurrentItem: grid.currentIndex == index
|
{
|
||||||
readonly property alias source: _viewpoint.source
|
event.accepted = true
|
||||||
readonly property alias metadata: _viewpoint.metadata
|
if(event.key == Qt.Key_Right)
|
||||||
|
root.currentIndex = Math.min(root.cameraInits.count - 1, root.currentIndex + 1)
|
||||||
// retrieve viewpoints inner data
|
else if(event.key == Qt.Key_Left)
|
||||||
QtObject {
|
root.currentIndex = Math.max(0, root.currentIndex - 1)
|
||||||
id: _viewpoint
|
|
||||||
readonly property string source: object.value.get("path").value
|
|
||||||
readonly property var metadata: JSON.parse(object.value.get("metadata").value)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: ImageDelegate {
|
||||||
|
viewpoint: object.value
|
||||||
width: grid.cellWidth
|
width: grid.cellWidth
|
||||||
height: grid.cellHeight
|
height: grid.cellHeight
|
||||||
|
|
||||||
MouseArea {
|
isCurrentItem: grid.currentIndex == index
|
||||||
id: imageMA
|
onPressed: {
|
||||||
anchors.fill: parent
|
grid.currentIndex = index
|
||||||
anchors.margins: 6
|
if(mouse.button == Qt.LeftButton)
|
||||||
hoverEnabled: true
|
grid.forceActiveFocus()
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
|
||||||
onPressed: {
|
|
||||||
grid.currentIndex = index
|
|
||||||
if(mouse.button == Qt.RightButton)
|
|
||||||
imageMenu.popup()
|
|
||||||
else
|
|
||||||
grid.forceActiveFocus()
|
|
||||||
}
|
|
||||||
Menu {
|
|
||||||
id: imageMenu
|
|
||||||
MenuItem {
|
|
||||||
text: "Show Containing Folder"
|
|
||||||
onClicked: {
|
|
||||||
Qt.openUrlExternally(Filepath.dirname(imageDelegate.source))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Remove"
|
|
||||||
enabled: !root.readOnly
|
|
||||||
onClicked: removeImageRequest(object)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
// Image thumbnail and background
|
|
||||||
Rectangle {
|
|
||||||
id: imageBackground
|
|
||||||
color: Qt.darker(palette.base, 1.15)
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
border.color: grid.currentIndex == index ? palette.highlight : Qt.darker(palette.highlight)
|
|
||||||
border.width: imageMA.containsMouse || imageDelegate.isCurrentItem ? 2 : 0
|
|
||||||
Image {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 4
|
|
||||||
source: imageDelegate.source
|
|
||||||
sourceSize: Qt.size(100, 100)
|
|
||||||
asynchronous: true
|
|
||||||
autoTransform: true
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Image basename
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
padding: 2
|
|
||||||
font.pointSize: 8
|
|
||||||
elide: Text.ElideMiddle
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
text: Filepath.basename(imageDelegate.source)
|
|
||||||
background: Rectangle {
|
|
||||||
color: imageDelegate.isCurrentItem ? palette.highlight : "transparent"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explanatory placeholder when no image has been added yet
|
// Explanatory placeholder when no image has been added yet
|
||||||
Column {
|
Column {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: model.count == 0
|
visible: grid.model.count == 0
|
||||||
spacing: 4
|
spacing: 4
|
||||||
Label {
|
Label {
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
@ -145,7 +94,7 @@ Panel {
|
||||||
enabled: !root.readOnly
|
enabled: !root.readOnly
|
||||||
// TODO: onEntered: call specific method to filter files based on extension
|
// TODO: onEntered: call specific method to filter files based on extension
|
||||||
onDropped: {
|
onDropped: {
|
||||||
_reconstruction.handleFilesDrop(drop)
|
root.filesDropped(drop)
|
||||||
}
|
}
|
||||||
// DropArea overlay
|
// DropArea overlay
|
||||||
Rectangle {
|
Rectangle {
|
||||||
|
@ -156,6 +105,40 @@ Panel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillHeight: false
|
||||||
|
visible: root.cameraInits.count > 1
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
text: MaterialIcons.navigate_before
|
||||||
|
font.family: MaterialIcons.fontFamily
|
||||||
|
ToolTip.text: "Previous Group (Alt+Left)"
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
enabled: nodesCB.currentIndex > 0
|
||||||
|
onClicked: nodesCB.decrementCurrentIndex()
|
||||||
|
}
|
||||||
|
Label { text: "Group " }
|
||||||
|
ComboBox {
|
||||||
|
id: nodesCB
|
||||||
|
model: root.cameraInits.count
|
||||||
|
implicitWidth: 40
|
||||||
|
currentIndex: root.currentIndex
|
||||||
|
onActivated: root.currentIndex = currentIndex
|
||||||
|
|
||||||
|
}
|
||||||
|
Label { text: "/ " + (root.cameraInits.count - 1) }
|
||||||
|
ToolButton {
|
||||||
|
text: MaterialIcons.navigate_next
|
||||||
|
font.family: MaterialIcons.fontFamily
|
||||||
|
ToolTip.text: "Next Group (Alt+Right)"
|
||||||
|
ToolTip.visible: hovered
|
||||||
|
enabled: root.currentIndex < root.cameraInits.count - 1
|
||||||
|
onClicked: nodesCB.incrementCurrentIndex()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footerContent: RowLayout {
|
footerContent: RowLayout {
|
||||||
|
@ -164,7 +147,7 @@ Panel {
|
||||||
// Image count
|
// Image count
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: model.count + " image" + (model.count > 1 ? "s" : "")
|
text: grid.model.count + " image" + (grid.model.count > 1 ? "s" : "")
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property variant reconstruction: _reconstruction
|
property variant reconstruction: _reconstruction
|
||||||
readonly property variant viewpoints: _reconstruction.viewpoints
|
readonly property variant cameraInits: _reconstruction.cameraInits
|
||||||
readonly property string meshFile: _reconstruction.meshFile
|
readonly property string meshFile: _reconstruction.meshFile
|
||||||
property bool readOnly: false
|
property bool readOnly: false
|
||||||
|
|
||||||
|
@ -45,8 +45,12 @@ Item {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.minimumWidth: defaultCellSize
|
Layout.minimumWidth: defaultCellSize
|
||||||
model: viewpoints
|
cameraInits: root.cameraInits
|
||||||
|
cameraInit: _reconstruction.cameraInit
|
||||||
|
currentIndex: reconstruction.cameraInitIndex
|
||||||
|
onCurrentIndexChanged: reconstruction.cameraInitIndex = currentIndex
|
||||||
onRemoveImageRequest: reconstruction.removeAttribute(attribute)
|
onRemoveImageRequest: reconstruction.removeAttribute(attribute)
|
||||||
|
onFilesDropped: reconstruction.handleFilesDrop(drop, cameraInit)
|
||||||
}
|
}
|
||||||
|
|
||||||
Panel {
|
Panel {
|
||||||
|
|
|
@ -5,6 +5,7 @@ from threading import Thread
|
||||||
from PySide2.QtCore import QObject, Slot, Property, Signal
|
from PySide2.QtCore import QObject, Slot, Property, Signal
|
||||||
|
|
||||||
from meshroom import multiview
|
from meshroom import multiview
|
||||||
|
from meshroom.common.qt import QObjectListModel
|
||||||
from meshroom.core import graph
|
from meshroom.core import graph
|
||||||
from meshroom.ui.graph import UIGraph
|
from meshroom.ui.graph import UIGraph
|
||||||
|
|
||||||
|
@ -19,7 +20,9 @@ class Reconstruction(UIGraph):
|
||||||
def __init__(self, graphFilepath='', parent=None):
|
def __init__(self, graphFilepath='', parent=None):
|
||||||
super(Reconstruction, self).__init__(graphFilepath, parent)
|
super(Reconstruction, self).__init__(graphFilepath, parent)
|
||||||
self._buildIntrinsicsThread = None
|
self._buildIntrinsicsThread = None
|
||||||
|
self._buildingIntrinsics = False
|
||||||
self._cameraInit = None
|
self._cameraInit = None
|
||||||
|
self._cameraInits = QObjectListModel(parent=self)
|
||||||
self._endChunk = None
|
self._endChunk = None
|
||||||
self._meshFile = ''
|
self._meshFile = ''
|
||||||
self.intrinsicsBuilt.connect(self.onIntrinsicsAvailable)
|
self.intrinsicsBuilt.connect(self.onIntrinsicsAvailable)
|
||||||
|
@ -38,7 +41,7 @@ class Reconstruction(UIGraph):
|
||||||
""" React to the change of the internal graph. """
|
""" React to the change of the internal graph. """
|
||||||
self._endChunk = None
|
self._endChunk = None
|
||||||
self.setMeshFile('')
|
self.setMeshFile('')
|
||||||
self.updateCameraInit()
|
self.updateCameraInits()
|
||||||
if not self._graph:
|
if not self._graph:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -51,7 +54,7 @@ class Reconstruction(UIGraph):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self._endChunk = None
|
self._endChunk = None
|
||||||
# TODO: listen specifically for cameraInit creation/deletion
|
# TODO: listen specifically for cameraInit creation/deletion
|
||||||
self._graph.nodes.countChanged.connect(self.updateCameraInit)
|
self._graph.nodes.countChanged.connect(self.updateCameraInits)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def runAsync(func, args=(), kwargs=None):
|
def runAsync(func, args=(), kwargs=None):
|
||||||
|
@ -64,12 +67,11 @@ class Reconstruction(UIGraph):
|
||||||
# TODO: handle multiple Viewpoints models
|
# TODO: handle multiple Viewpoints models
|
||||||
return self._cameraInit.viewpoints.value if self._cameraInit else None
|
return self._cameraInit.viewpoints.value if self._cameraInit else None
|
||||||
|
|
||||||
def updateCameraInit(self):
|
def updateCameraInits(self):
|
||||||
""" Update internal CameraInit node (Viewpoints model owner) based on graph content. """
|
cameraInits = self._graph.nodesByType("CameraInit", sortedByIndex=True)
|
||||||
# TODO: handle multiple CameraInit nodes
|
if set(self._cameraInits.objectList()) == set(cameraInits):
|
||||||
if self._cameraInit in self._graph.nodes:
|
|
||||||
return
|
return
|
||||||
cameraInits = self._graph.findNodeCandidates("CameraInit")
|
self._cameraInits.setObjectList(cameraInits)
|
||||||
self.setCameraInit(cameraInits[0] if cameraInits else None)
|
self.setCameraInit(cameraInits[0] if cameraInits else None)
|
||||||
|
|
||||||
def setCameraInit(self, cameraInit):
|
def setCameraInit(self, cameraInit):
|
||||||
|
@ -78,7 +80,15 @@ class Reconstruction(UIGraph):
|
||||||
if self._cameraInit == cameraInit:
|
if self._cameraInit == cameraInit:
|
||||||
return
|
return
|
||||||
self._cameraInit = cameraInit
|
self._cameraInit = cameraInit
|
||||||
self.viewpointsChanged.emit()
|
self.cameraInitChanged.emit()
|
||||||
|
|
||||||
|
def getCameraInitIndex(self):
|
||||||
|
if not self._cameraInit:
|
||||||
|
return -1
|
||||||
|
return self._cameraInits.indexOf(self._cameraInit)
|
||||||
|
|
||||||
|
def setCameraInitIndex(self, idx):
|
||||||
|
self.setCameraInit(self._cameraInits[idx])
|
||||||
|
|
||||||
def updateMeshFile(self):
|
def updateMeshFile(self):
|
||||||
if self._endChunk and self._endChunk.status.status == graph.Status.SUCCESS:
|
if self._endChunk and self._endChunk.status.status == graph.Status.SUCCESS:
|
||||||
|
@ -92,22 +102,22 @@ class Reconstruction(UIGraph):
|
||||||
self._meshFile = mf
|
self._meshFile = mf
|
||||||
self.meshFileChanged.emit()
|
self.meshFileChanged.emit()
|
||||||
|
|
||||||
@Slot(QObject)
|
@Slot(QObject, graph.Node)
|
||||||
def handleFilesDrop(self, drop):
|
def handleFilesDrop(self, drop, cameraInit):
|
||||||
""" 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 ?).
|
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.
|
||||||
"""
|
"""
|
||||||
self.importImagesFromUrls(drop.property("urls"))
|
self.importImages(self.getImageFilesFromDrop(drop), cameraInit)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def isImageFile(filepath):
|
def isImageFile(filepath):
|
||||||
""" Return whether filepath is a path to an image file supported by Meshroom. """
|
""" Return whether filepath is a path to an image file supported by Meshroom. """
|
||||||
return os.path.splitext(filepath)[1].lower() in Reconstruction.imageExtensions
|
return os.path.splitext(filepath)[1].lower() in Reconstruction.imageExtensions
|
||||||
|
|
||||||
@Slot(QObject)
|
@staticmethod
|
||||||
def importImagesFromUrls(self, urls):
|
def getImageFilesFromDrop(drop):
|
||||||
""" Add the given list of images (as QUrl) to the Reconstruction. """
|
urls = drop.property("urls")
|
||||||
# Build the list of images paths
|
# Build the list of images paths
|
||||||
images = []
|
images = []
|
||||||
for url in urls:
|
for url in urls:
|
||||||
|
@ -117,43 +127,52 @@ class Reconstruction(UIGraph):
|
||||||
else:
|
else:
|
||||||
files = [localFile]
|
files = [localFile]
|
||||||
images.extend([f for f in files if Reconstruction.isImageFile(f)])
|
images.extend([f for f in files if Reconstruction.isImageFile(f)])
|
||||||
if not images:
|
return images
|
||||||
return
|
|
||||||
# Start the process of updating views and intrinsics
|
|
||||||
self._buildIntrinsicsThread = self.runAsync(self.buildIntrinsics, args=(images,))
|
|
||||||
|
|
||||||
def buildIntrinsics(self, additionalViews):
|
def importImages(self, images, cameraInit):
|
||||||
|
""" Add the given list of images to the Reconstruction. """
|
||||||
|
# Start the process of updating views and intrinsics
|
||||||
|
self._buildIntrinsicsThread = self.runAsync(self.buildIntrinsics, args=(cameraInit, images,))
|
||||||
|
|
||||||
|
def buildIntrinsics(self, cameraInit, additionalViews):
|
||||||
"""
|
"""
|
||||||
Build up-to-date intrinsics and views based on already loaded + additional images.
|
Build up-to-date intrinsics and views based on already loaded + additional images.
|
||||||
Does not modify the graph, can be called outside the main thread.
|
Does not modify the graph, can be called outside the main thread.
|
||||||
Emits intrinsicBuilt(views, intrinsics) when done.
|
Emits intrinsicBuilt(views, intrinsics) when done.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.buildingIntrinsicsChanged.emit()
|
self.setBuildingIntrinsics(True)
|
||||||
# Retrieve the list of updated viewpoints and intrinsics
|
# Retrieve the list of updated viewpoints and intrinsics
|
||||||
views, intrinsics = self._cameraInit.nodeDesc.buildIntrinsics(self._cameraInit, additionalViews)
|
views, intrinsics = cameraInit.nodeDesc.buildIntrinsics(cameraInit, additionalViews)
|
||||||
self.intrinsicsBuilt.emit(views, intrinsics)
|
self.intrinsicsBuilt.emit(cameraInit, views, intrinsics)
|
||||||
return views, intrinsics
|
return views, intrinsics
|
||||||
except Exception as e:
|
except Exception:
|
||||||
logging.error("Error while building intrinsics : {}".format(e))
|
import traceback
|
||||||
|
logging.error("Error while building intrinsics : {}".format(traceback.format_exc()))
|
||||||
finally:
|
finally:
|
||||||
self.buildingIntrinsicsChanged.emit()
|
self.setBuildingIntrinsics(False)
|
||||||
|
|
||||||
def onIntrinsicsAvailable(self, views, intrinsics):
|
def onIntrinsicsAvailable(self, cameraInit, views, intrinsics):
|
||||||
""" Update CameraInit with given views and intrinsics. """
|
""" Update CameraInit with given views and intrinsics. """
|
||||||
with self.groupedGraphModification("Add Images"):
|
with self.groupedGraphModification("Add Images"):
|
||||||
self.setAttribute(self._cameraInit.viewpoints, views)
|
self.setAttribute(cameraInit.viewpoints, views)
|
||||||
self.setAttribute(self._cameraInit.intrinsics, intrinsics)
|
self.setAttribute(cameraInit.intrinsics, intrinsics)
|
||||||
|
self.setCameraInit(cameraInit)
|
||||||
|
|
||||||
def isBuildingIntrinsics(self):
|
def setBuildingIntrinsics(self, value):
|
||||||
""" Whether intrinsics are being built """
|
if self._buildingIntrinsics == value:
|
||||||
return self._buildIntrinsicsThread and self._buildIntrinsicsThread.isAlive()
|
return
|
||||||
|
self._buildingIntrinsics = value
|
||||||
|
self.buildingIntrinsicsChanged.emit()
|
||||||
|
|
||||||
viewpointsChanged = Signal()
|
cameraInitChanged = Signal()
|
||||||
viewpoints = Property(QObject, getViewpoints, notify=viewpointsChanged)
|
cameraInit = Property(QObject, lambda self: self._cameraInit, notify=cameraInitChanged)
|
||||||
intrinsicsBuilt = Signal(list, list)
|
cameraInitIndex = Property(int, getCameraInitIndex, setCameraInitIndex, notify=cameraInitChanged)
|
||||||
|
viewpoints = Property(QObject, getViewpoints, notify=cameraInitChanged)
|
||||||
|
cameraInits = Property(QObject, lambda self: self._cameraInits, constant=True)
|
||||||
|
intrinsicsBuilt = Signal(QObject, list, list)
|
||||||
buildingIntrinsicsChanged = Signal()
|
buildingIntrinsicsChanged = Signal()
|
||||||
buildingIntrinsics = Property(bool, isBuildingIntrinsics, notify=buildingIntrinsicsChanged)
|
buildingIntrinsics = Property(bool, lambda self: self._buildingIntrinsics, notify=buildingIntrinsicsChanged)
|
||||||
meshFileChanged = Signal()
|
meshFileChanged = Signal()
|
||||||
meshFile = Property(str, lambda self: self._meshFile, notify=meshFileChanged)
|
meshFile = Property(str, lambda self: self._meshFile, notify=meshFileChanged)
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue