Meshroom/meshroom/ui/qml/ImageGallery/ImageGallery.qml
Candice Bentéjac 5e61a1da5a [ui] ImageGallery: Force index to -1 before setting it if there's a tempCameraInit
When there is a temporary CameraInit, it means that either the "Visualize
HDR images" or "Preprocessed images" options are enabled.

If several CameraInit groups are available, and if the currently selected
image in the GridView is the first one (index = 0), there is a possibility,
depending on the input images, that the first images in two different
groups are not identical but have the same view ID. If that happens, there
will be no update of the Viewer2D, as the selectedViewId property will not
have been modified.

By setting the selectedViewId property to -1 when there is a temporary
CameraInit and the current index in the GridView is 0, we trigger an
update of the viewer even when there is no apparent change in the view ID.
2023-03-13 15:57:24 +01:00

854 lines
34 KiB
QML

import QtQuick 2.14
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.3
import MaterialIcons 2.2
import QtQml.Models 2.2
import Qt.labs.qmlmodels 1.0
import Controls 1.0
import Utils 1.0
/**
* ImageGallery displays as a grid of Images a model containing Viewpoints objects.
* It manages a model of multiple CameraInit nodes as individual groups.
*/
Panel {
id: root
property variant cameraInits
property variant cameraInit
property int cameraInitIndex
property variant tempCameraInit
readonly property alias currentItem: grid.currentItem
readonly property string currentItemSource: grid.currentItem ? grid.currentItem.source : ""
readonly property var currentItemMetadata: grid.currentItem ? grid.currentItem.metadata : undefined
readonly property int centerViewId: (_reconstruction && _reconstruction.sfmTransform) ? parseInt(_reconstruction.sfmTransform.attribute("transformation").value) : 0
readonly property alias galleryGrid: grid
property int defaultCellSize: 160
property bool readOnly: false
signal removeImageRequest(var attribute)
signal filesDropped(var drop, var augmentSfm)
title: "Image Gallery"
implicitWidth: (root.defaultCellSize + 2) * 2
QtObject {
id: m
property variant currentCameraInit: _reconstruction && _reconstruction.tempCameraInit ? _reconstruction.tempCameraInit : root.cameraInit
property variant viewpoints: currentCameraInit ? currentCameraInit.attribute('viewpoints').value : undefined
property variant intrinsics: currentCameraInit ? currentCameraInit.attribute('intrinsics').value : undefined
property bool readOnly: root.readOnly || displayHDR.checked
onViewpointsChanged: {
ThumbnailCache.clearRequests();
}
}
property variant parsedIntrinsic
property int numberOfIntrinsics : m.intrinsics ? m.intrinsics.count : 0
onNumberOfIntrinsicsChanged: {
parseIntr()
}
onCameraInitIndexChanged: {
parseIntr()
}
function changeCurrentIndex(newIndex) {
_reconstruction.cameraInitIndex = newIndex
}
function populate_model()
{
intrinsicModel.clear()
for (var intr in parsedIntrinsic) {
intrinsicModel.appendRow(parsedIntrinsic[intr])
}
}
function parseIntr(){
parsedIntrinsic = []
if(!m.intrinsics)
{
return
}
//Loop through all intrinsics
for(var i = 0; i < m.intrinsics.count; ++i){
var intrinsic = {}
//Loop through all attributes
for(var j=0; j < m.intrinsics.at(i).value.count; ++j){
var currentAttribute = m.intrinsics.at(i).value.at(j)
if(currentAttribute.type === "GroupAttribute"){
for(var k=0; k < currentAttribute.value.count; ++k){
intrinsic[currentAttribute.name + "." + currentAttribute.value.at(k).name] = currentAttribute.value.at(k)
}
}
else if(currentAttribute.type === "ListAttribute"){
// not needed for now
}
else{
intrinsic[currentAttribute.name] = currentAttribute
}
}
// Table Model needs to contain an entry for each column.
// In case of old file formats, some intrinsic keys that we display may not exist in the model.
// So, here we create an empty entry to enforce that the key exists in the model.
for(var n = 0; n < intrinsicModel.columnNames.length; ++n)
{
var name = intrinsicModel.columnNames[n]
if(!(name in intrinsic)) {
intrinsic[name] = {}
}
}
parsedIntrinsic[i] = intrinsic
}
populate_model()
}
headerBar: RowLayout {
SearchBar {
id: searchBar
width: 150
}
MaterialToolButton {
text: MaterialIcons.more_vert
font.pointSize: 11
padding: 2
checkable: true
checked: galleryMenu.visible
onClicked: galleryMenu.open()
Menu {
id: galleryMenu
y: parent.height
x: -width + parent.width
MenuItem {
text: "Edit Sensor Database..."
onTriggered: {
sensorDBDialog.open()
}
}
Menu {
title: "Advanced"
Action {
id: displayViewIdsAction
text: "Display View IDs"
checkable: true
}
}
}
}
}
SensorDBDialog {
id: sensorDBDialog
sensorDatabase: cameraInit ? Filepath.stringToUrl(cameraInit.attribute("sensorDatabase").evalValue) : ""
readOnly: _reconstruction ? _reconstruction.computing : false
onUpdateIntrinsicsRequest: _reconstruction.rebuildIntrinsics(cameraInit)
}
ColumnLayout {
anchors.fill: parent
spacing: 4
GridView {
id: grid
Layout.fillWidth: true
Layout.fillHeight: true
visible: !intrinsicsFilterButton.checked
ScrollBar.vertical: ScrollBar {
minimumSize: 0.05
active : !intrinsicsFilterButton.checked
visible: !intrinsicsFilterButton.checked
}
focus: true
clip: true
cellWidth: thumbnailSizeSlider.value
cellHeight: cellWidth
highlightFollowsCurrentItem: true
keyNavigationEnabled: true
property bool updateSelectedViewFromGrid: true
// Update grid current item when selected view changes
Connections {
target: _reconstruction
onSelectedViewIdChanged: {
if (_reconstruction.selectedViewId > -1) {
grid.updateCurrentIndexFromSelectionViewId()
}
}
}
function makeCurrentItemVisible()
{
grid.positionViewAtIndex(grid.currentIndex, GridView.Visible)
}
function updateCurrentIndexFromSelectionViewId()
{
var idx = grid.model.find(_reconstruction.selectedViewId, "viewId")
if (idx >= 0 && grid.currentIndex != idx) {
grid.currentIndex = idx
}
}
onCurrentItemChanged: {
if (grid.updateSelectedViewFromGrid && grid.currentItem) {
// If tempCameraInit is set and the first image in the GridView is selected, there has been a change of the CameraInit group and the viewId might be the same
// Forcing the index to -1 before re-setting it will always cause a refresh on the Viewer2D's side, even if the viewId has not changed
if (tempCameraInit !== null && grid.currentIndex == 0)
_reconstruction.selectedViewId = -1
_reconstruction.selectedViewId = grid.currentItem.viewpoint.get("viewId").value
}
}
// Update grid item when corresponding thumbnail is computed
Connections {
target: ThumbnailCache
function onThumbnailCreated(imgSource, callerID) {
let item = grid.itemAtIndex(callerID); // item is an ImageDelegate
if (item && item.source == imgSource) {
item.updateThumbnail();
return;
}
// fallback in case the ImageDelegate cellID changed
for (let idx = 0; idx < grid.count; idx++) {
item = grid.itemAtIndex(idx);
if (item && item.source == imgSource) {
item.updateThumbnail();
}
}
}
}
model: SortFilterDelegateModel {
id: sortedModel
model: m.viewpoints
sortRole: "path.basename"
filters: displayViewIdsAction.checked ? filtersWithViewIds : filtersBasic
property var filtersBasic: [
{role: "path", value: searchBar.text},
{role: "viewId.isReconstructed", value: reconstructionFilter}
]
property var filtersWithViewIds: [
[
{role: "path", value: searchBar.text},
{role: "viewId.asString", value: searchBar.text}
],
{role: "viewId.isReconstructed", value: reconstructionFilter}
]
property var reconstructionFilter: undefined
// override modelData to return basename of viewpoint's path for sorting
function modelData(item, roleName_) {
var roleNameAndCmd = roleName_.split(".");
var roleName = roleName_;
var cmd = "";
if(roleNameAndCmd.length >= 2)
{
roleName = roleNameAndCmd[0];
cmd = roleNameAndCmd[1];
}
if(cmd == "isReconstructed")
return _reconstruction.isReconstructed(item.model.object);
var value = item.model.object.childAttribute(roleName).value;
if(cmd == "basename")
return Filepath.basename(value);
if (cmd == "asString")
return value.toString();
return value
}
delegate: ImageDelegate {
id: imageDelegate
viewpoint: object.value
cellID: DelegateModel.filteredIndex
width: grid.cellWidth
height: grid.cellHeight
readOnly: m.readOnly
displayViewId: displayViewIdsAction.checked
visible: !intrinsicsFilterButton.checked
isCurrentItem: GridView.isCurrentItem
onPressed: {
grid.currentIndex = DelegateModel.filteredIndex
}
function sendRemoveRequest()
{
if(!readOnly)
removeImageRequest(object)
}
onRemoveRequest: sendRemoveRequest()
Keys.onDeletePressed: sendRemoveRequest()
RowLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: 2
spacing: 2
property bool valid: Qt.isQtObject(object) // object can be evaluated to null at some point during creation/deletion
property bool inViews: valid && _reconstruction && _reconstruction.sfmReport && _reconstruction.isInViews(object)
// Camera Initialization indicator
IntrinsicsIndicator {
intrinsic: parent.valid && _reconstruction ? _reconstruction.getIntrinsic(object) : null
metadata: imageDelegate.metadata
}
// Rig indicator
Loader {
id: rigIndicator
property int rigId: parent.valid ? object.childAttribute("rigId").value : -1
active: rigId >= 0
sourceComponent: ImageBadge {
property int rigSubPoseId: model.object.childAttribute("subPoseId").value
text: MaterialIcons.link
ToolTip.text: "<b>Rig: Initialized</b><br>" +
"Rig ID: " + rigIndicator.rigId + " <br>" +
"SubPose: " + rigSubPoseId
}
}
// Center of SfMTransform
Loader {
id: sfmTransformIndicator
active: viewpoint && (viewpoint.get("viewId").value == centerViewId)
sourceComponent: ImageBadge {
text: MaterialIcons.gamepad
ToolTip.text: "Camera used to define the center of the scene."
}
}
Item { Layout.fillWidth: true }
// Reconstruction status indicator
Loader {
active: parent.inViews
visible: active
sourceComponent: ImageBadge {
property bool reconstructed: _reconstruction.sfmReport && _reconstruction.isReconstructed(model.object)
text: reconstructed ? MaterialIcons.videocam : MaterialIcons.videocam_off
color: reconstructed ? Colors.green : Colors.red
ToolTip.text: "<b>Camera: " + (reconstructed ? "" : "Not ") + "Reconstructed</b>"
}
}
}
}
}
// Keyboard shortcut to change current image group
Keys.priority: Keys.BeforeItem
Keys.onPressed: {
if(event.modifiers & Qt.AltModifier)
{
if(event.key == Qt.Key_Right)
{
_reconstruction.cameraInitIndex = Math.min(root.cameraInits.count - 1, root.cameraInitIndex + 1)
event.accepted = true
}
else if(event.key == Qt.Key_Left)
{
_reconstruction.cameraInitIndex = Math.max(0, root.cameraInitIndex - 1)
event.accepted = true
}
}
else
{
if(event.key == Qt.Key_Right)
{
grid.moveCurrentIndexRight()
event.accepted = true
}
else if(event.key == Qt.Key_Left)
{
grid.moveCurrentIndexLeft()
event.accepted = true
}
else if(event.key == Qt.Key_Up)
{
grid.moveCurrentIndexUp()
event.accepted = true
}
else if(event.key == Qt.Key_Down)
{
grid.moveCurrentIndexDown()
event.accepted = true
}
else if (event.key == Qt.Key_Tab)
{
searchBar.forceActiveFocus()
event.accepted = true
}
}
}
// Explanatory placeholder when no image has been added yet
Column {
id: dropImagePlaceholder
anchors.centerIn: parent
visible: (m.viewpoints ? m.viewpoints.count == 0 : true) && !intrinsicsFilterButton.checked
spacing: 4
Label {
anchors.horizontalCenter: parent.horizontalCenter
text: MaterialIcons.photo_library
font.pointSize: 24
font.family: MaterialIcons.fontFamily
}
Label {
text: "Drop Image Files / Folders"
}
}
// Placeholder when the filtered images list is empty
Column {
id: noImageImagePlaceholder
anchors.centerIn: parent
visible: (m.viewpoints ? m.viewpoints.count != 0 : false) && !dropImagePlaceholder.visible && grid.model.count == 0 && !intrinsicsFilterButton.checked
spacing: 4
Label {
anchors.horizontalCenter: parent.horizontalCenter
text: MaterialIcons.filter_none
font.pointSize: 24
font.family: MaterialIcons.fontFamily
}
Label {
text: "No images in this filtered view"
}
}
DropArea {
id: dropArea
anchors.fill: parent
enabled: !m.readOnly && !intrinsicsFilterButton.checked
keys: ["text/uri-list"]
// TODO: onEntered: call specific method to filter files based on extension
onDropped: {
var augmentSfm = augmentArea.hovered
root.filesDropped(drop, augmentSfm)
}
// Background opacifier
Rectangle {
visible: dropArea.containsDrag
anchors.fill: parent
color: root.palette.window
opacity: 0.8
}
ColumnLayout {
anchors.fill: parent
visible: dropArea.containsDrag
spacing: 1
Label {
id: addArea
property bool hovered: dropArea.drag.y < height
Layout.fillWidth: true
Layout.fillHeight: true
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: "Add Images"
font.bold: true
background: Rectangle {
color: parent.hovered ? parent.palette.highlight : parent.palette.window
opacity: 0.8
border.color: parent.palette.highlight
}
}
// DropArea overlay
Label {
id: augmentArea
property bool hovered: visible && dropArea.drag.y >= y
Layout.fillWidth: true
Layout.preferredHeight: parent.height * 0.3
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
text: "Augment Reconstruction"
font.bold: true
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
visible: m.viewpoints ? m.viewpoints.count > 0 : false
background: Rectangle {
color: parent.hovered ? palette.highlight : palette.window
opacity: 0.8
border.color: parent.palette.highlight
}
}
}
}
MouseArea {
anchors.fill: parent
onPressed: {
if(mouse.button == Qt.LeftButton)
grid.forceActiveFocus()
mouse.accepted = false
}
}
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
visible: intrinsicsFilterButton.checked
clip: true
TableView {
id : intrinsicTable
visible: intrinsicsFilterButton.checked
anchors.fill: parent
boundsMovement : Flickable.StopAtBounds
//Provide width for column
//Note no size provided for the last column (bool comp) so it uses its automated size
columnWidthProvider: function (column) { return intrinsicModel.columnWidths[column] }
model: intrinsicModel
delegate: IntrinsicDisplayDelegate { attribute: model.display }
ScrollBar.horizontal: ScrollBar { id: sb }
ScrollBar.vertical : ScrollBar { id: sbv }
}
TableModel {
id : intrinsicModel
// Hardcoded default width per column
property var columnWidths: [105, 75, 75, 75, 125, 60, 60, 45, 45, 200, 60, 60]
property var columnNames: [
"intrinsicId",
"initialFocalLength",
"focalLength",
"type",
"width",
"height",
"sensorWidth",
"sensorHeight",
"serialNumber",
"principalPoint.x",
"principalPoint.y",
"locked"]
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[0]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[1]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[2]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[3]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[4]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[5]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[6]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[7]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[8]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[9]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[10]]} }
TableModelColumn { display: function(modelIndex){return parsedIntrinsic[modelIndex.row][intrinsicModel.columnNames[11]]} }
//https://doc.qt.io/qt-5/qml-qt-labs-qmlmodels-tablemodel.html#appendRow-method
}
//CODE FOR HEADERS
//UNCOMMENT WHEN COMPATIBLE WITH THE RIGHT QT VERSION
// HorizontalHeaderView {
// id: horizontalHeader
// syncView: tableView
// anchors.left: tableView.left
// }
}
RowLayout {
Layout.fillHeight: false
visible: root.cameraInits ? root.cameraInits.count > 1 : false
Layout.alignment: Qt.AlignHCenter
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 { id: groupLabel; text: "Group " }
ComboBox {
id: nodesCB
model: {
// Create an array from 1 to cameraInits.count for the
// display of group indices (real indices still are from
// 0 to cameraInits.count - 1)
var l = [];
if (root.cameraInits) {
for (var i = 1; i <= root.cameraInits.count; i++) {
l.push(i);
}
}
return l;
}
implicitWidth: 40
currentIndex: root.cameraInitIndex
onActivated: root.changeCurrentIndex(currentIndex)
}
Label { text: "/ " + (root.cameraInits ? root.cameraInits.count : "Unknown") }
ToolButton {
text: MaterialIcons.navigate_next
font.family: MaterialIcons.fontFamily
ToolTip.text: "Next Group (Alt+Right)"
ToolTip.visible: hovered
enabled: root.cameraInits ? nodesCB.currentIndex < root.cameraInits.count - 1 : false
onClicked: nodesCB.incrementCurrentIndex()
}
}
}
footerContent: RowLayout {
// Images count
id: footer
function resetButtons(){
inputImagesFilterButton.checked = false
estimatedCamerasFilterButton.checked = false
nonEstimatedCamerasFilterButton.checked = false
}
MaterialToolLabelButton {
id : inputImagesFilterButton
Layout.minimumWidth: childrenRect.width
ToolTip.text: grid.model.count + " Input Images"
iconText: MaterialIcons.image
label: (m.viewpoints ? m.viewpoints.count : 0)
padding: 3
checkable: true
checked: true
onCheckedChanged: {
if (checked) {
sortedModel.reconstructionFilter = undefined;
estimatedCamerasFilterButton.checked = false;
nonEstimatedCamerasFilterButton.checked = false;
intrinsicsFilterButton.checked = false;
} else {
if (estimatedCamerasFilterButton.checked === false && nonEstimatedCamerasFilterButton.checked === false && intrinsicsFilterButton.checked === false)
inputImagesFilterButton.checked = true
}
}
}
// Estimated cameras count
MaterialToolLabelButton {
id : estimatedCamerasFilterButton
Layout.minimumWidth: childrenRect.width
ToolTip.text: label + " Estimated Cameras"
iconText: MaterialIcons.videocam
label: _reconstruction && _reconstruction.nbCameras ? _reconstruction.nbCameras.toString() : "-"
padding: 3
enabled: _reconstruction ? _reconstruction.cameraInit && _reconstruction.nbCameras : false
checkable: true
checked: false
onCheckedChanged: {
if (checked) {
sortedModel.reconstructionFilter = true;
inputImagesFilterButton.checked = false;
nonEstimatedCamerasFilterButton.checked = false;
intrinsicsFilterButton.checked = false;
} else {
if (inputImagesFilterButton.checked === false && nonEstimatedCamerasFilterButton.checked === false && intrinsicsFilterButton.checked === false)
inputImagesFilterButton.checked = true
}
}
onEnabledChanged:{
if(!enabled) {
if(checked) inputImagesFilterButton.checked = true;
checked = false
}
}
}
// Non estimated cameras count
MaterialToolLabelButton {
id : nonEstimatedCamerasFilterButton
Layout.minimumWidth: childrenRect.width
ToolTip.text: label + " Non Estimated Cameras"
iconText: MaterialIcons.videocam_off
label: _reconstruction && _reconstruction.nbCameras ? ((m.viewpoints ? m.viewpoints.count : 0) - _reconstruction.nbCameras.toString()).toString() : "-"
padding: 3
enabled: _reconstruction ? _reconstruction.cameraInit && _reconstruction.nbCameras : false
checkable: true
checked: false
onCheckedChanged: {
if (checked) {
sortedModel.reconstructionFilter = false;
inputImagesFilterButton.checked = false;
estimatedCamerasFilterButton.checked = false;
intrinsicsFilterButton.checked = false;
} else {
if (inputImagesFilterButton.checked === false && estimatedCamerasFilterButton.checked === false && intrinsicsFilterButton.checked === false)
inputImagesFilterButton.checked = true
}
}
onEnabledChanged:{
if(!enabled) {
if(checked) inputImagesFilterButton.checked = true;
checked = false
}
}
}
MaterialToolLabelButton {
id : intrinsicsFilterButton
Layout.minimumWidth: childrenRect.width
ToolTip.text: label + " Number of intrinsics"
iconText: MaterialIcons.camera
label: _reconstruction ? (m.intrinsics ? m.intrinsics.count : 0) : "0"
padding: 3
enabled: m.intrinsics ? m.intrinsics.count > 0 : false
checkable: true
checked: false
onCheckedChanged: {
if (checked) {
inputImagesFilterButton.checked = false
estimatedCamerasFilterButton.checked = false
nonEstimatedCamerasFilterButton.checked = false
} else {
if (inputImagesFilterButton.checked === false && estimatedCamerasFilterButton.checked === false && nonEstimatedCamerasFilterButton.checked === false)
inputImagesFilterButton.checked = true
}
}
onEnabledChanged:{
if(!enabled) {
if(checked) inputImagesFilterButton.checked = true;
checked = false
}
}
}
Item { Layout.fillHeight: true; Layout.fillWidth: true }
MaterialToolLabelButton {
id: displayHDR
Layout.minimumWidth: childrenRect.width
property var activeNode: _reconstruction ? _reconstruction.activeNodes.get("LdrToHdrMerge").node : null
ToolTip.text: "Visualize HDR images: " + (activeNode ? activeNode.label : "No Node")
iconText: MaterialIcons.filter
label: activeNode ? activeNode.attribute("nbBrackets").value : ""
visible: activeNode
enabled: activeNode && activeNode.isComputed
property string nodeID: activeNode ? (activeNode.label + activeNode.isComputed) : ""
onNodeIDChanged: {
if(checked) {
open();
}
}
onEnabledChanged: {
// Reset the toggle to avoid getting stuck
// with the HDR node checked but disabled.
if(checked) {
checked = false;
close();
}
}
checkable: true
checked: false
onClicked: {
if(checked) {
open();
} else {
close();
}
}
function open() {
if(imageProcessing.checked)
imageProcessing.checked = false;
_reconstruction.setupTempCameraInit(activeNode, "outSfMData");
}
function close() {
_reconstruction.clearTempCameraInit();
}
}
MaterialToolButton {
id: imageProcessing
Layout.minimumWidth: childrenRect.width
property var activeNode: _reconstruction ? _reconstruction.activeNodes.get("ImageProcessing").node : null
font.pointSize: 15
padding: 0
ToolTip.text: "Preprocessed Images: " + (activeNode ? activeNode.label : "No Node")
text: MaterialIcons.wallpaper
visible: activeNode && activeNode.attribute("outSfMData").value
enabled: activeNode && activeNode.isComputed
property string nodeID: activeNode ? (activeNode.label + activeNode.isComputed) : ""
onNodeIDChanged: {
if(checked) {
open();
}
}
onEnabledChanged: {
// Reset the toggle to avoid getting stuck
// with the HDR node checked but disabled.
if(checked) {
checked = false;
close();
}
}
checkable: true
checked: false
onClicked: {
if(checked) {
open();
} else {
close();
}
}
function open() {
if(displayHDR.checked)
displayHDR.checked = false;
_reconstruction.setupTempCameraInit(activeNode, "outSfMData");
}
function close() {
_reconstruction.clearTempCameraInit();
}
}
Item { Layout.fillHeight: true; width: 1 }
// Thumbnail size icon and slider
MaterialToolButton {
Layout.minimumWidth: childrenRect.width
text: MaterialIcons.photo_size_select_large
ToolTip.text: "Thumbnails Scale"
padding: 0
anchors.margins: 0
font.pointSize: 11
onClicked: { thumbnailSizeSlider.value = defaultCellSize; }
}
Slider {
id: thumbnailSizeSlider
from: 70
value: defaultCellSize
to: 250
implicitWidth: 70
}
}
}